reformat everything
This commit is contained in:
parent
98b1705d81
commit
1bd01f9652
25 changed files with 276 additions and 121 deletions
|
@ -27,14 +27,18 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
(
|
||||
'password', models.CharField(
|
||||
max_length=128, verbose_name='password',
|
||||
'password',
|
||||
models.CharField(
|
||||
max_length=128,
|
||||
verbose_name='password',
|
||||
),
|
||||
),
|
||||
(
|
||||
'last_login',
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name='last login',
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='last login',
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -63,19 +67,25 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'first_name',
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name='first name',
|
||||
blank=True,
|
||||
max_length=150,
|
||||
verbose_name='first name',
|
||||
),
|
||||
),
|
||||
(
|
||||
'last_name',
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name='last name',
|
||||
blank=True,
|
||||
max_length=150,
|
||||
verbose_name='last name',
|
||||
),
|
||||
),
|
||||
(
|
||||
'email',
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name='email address',
|
||||
blank=True,
|
||||
max_length=254,
|
||||
verbose_name='email address',
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -97,7 +107,8 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'date_joined',
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name='date joined',
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name='date joined',
|
||||
),
|
||||
),
|
||||
(
|
||||
|
|
|
@ -13,7 +13,8 @@ class Migration(migrations.Migration):
|
|||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(
|
||||
max_length=254, verbose_name='email address',
|
||||
max_length=254,
|
||||
verbose_name='email address',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
|
|
@ -18,14 +18,18 @@ class Migration(migrations.Migration):
|
|||
model_name='user',
|
||||
name='customers',
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name='+', to='subjects.subject',
|
||||
blank=True,
|
||||
related_name='+',
|
||||
to='subjects.subject',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='suppliers',
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name='+', to='subjects.subject',
|
||||
blank=True,
|
||||
related_name='+',
|
||||
to='subjects.subject',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -30,7 +30,9 @@ class Migration(migrations.Migration):
|
|||
model_name='user',
|
||||
name='customers',
|
||||
field=models.ManyToManyField(
|
||||
blank=True, to='subjects.subject', verbose_name='customers',
|
||||
blank=True,
|
||||
to='subjects.subject',
|
||||
verbose_name='customers',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -13,15 +13,21 @@ class User(AbstractUser):
|
|||
email = models.EmailField(_('email address'))
|
||||
|
||||
supplier = models.ForeignKey(
|
||||
Subject, models.PROTECT, _('supplier'), blank=True, null=True,
|
||||
Subject,
|
||||
models.PROTECT,
|
||||
_('supplier'),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
customers = models.ManyToManyField(
|
||||
Subject, blank=True, verbose_name=_('customers'),
|
||||
Subject,
|
||||
blank=True,
|
||||
verbose_name=_('customers'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("user")
|
||||
verbose_name_plural = _("users")
|
||||
verbose_name = _('user')
|
||||
verbose_name_plural = _('users')
|
||||
|
||||
def _get_m2m_ids(self, field: str):
|
||||
return list(getattr(self, field).values_list('id', flat=True))
|
||||
|
|
|
@ -22,8 +22,8 @@ def auth_login(req: HttpRequest) -> HttpResponse:
|
|||
login(req, user)
|
||||
|
||||
user.email_user(
|
||||
subject="You have just logged in!",
|
||||
message="You have just logged in",
|
||||
subject='You have just logged in!',
|
||||
message='You have just logged in',
|
||||
)
|
||||
|
||||
redirect_url = getattr(req.GET, 'next', settings.LOGIN_REDIRECT_URL)
|
||||
|
|
|
@ -24,7 +24,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
SECRET_KEY = env(
|
||||
'SECRET_KEY', default='django-insecure-+l+g3)q1zz2bz7=mz4ys6lhu5uj+=ucj34flm^clo4vb3(wmdp',
|
||||
'SECRET_KEY',
|
||||
default='django-insecure-+l+g3)q1zz2bz7=mz4ys6lhu5uj+=ucj34flm^clo4vb3(wmdp',
|
||||
)
|
||||
|
||||
DEBUG = env.bool('DEBUG', default=True)
|
||||
|
@ -156,32 +157,32 @@ CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
|||
|
||||
# Template to PDF rendering
|
||||
GOTENBERG_API_URL = env.str('GOTENBERG_API_URL', 'http://gotenberg:3000')
|
||||
INTERNAL_API_URL = env.str('INTERNAL_API_URL', "http://backend:8000")
|
||||
INTERNAL_API_URL = env.str('INTERNAL_API_URL', 'http://backend:8000')
|
||||
|
||||
# Dramatiq
|
||||
DRAMATIQ_BROKER = {
|
||||
"BROKER": "dramatiq.brokers.redis.RedisBroker",
|
||||
"OPTIONS": {
|
||||
"url": "redis://redis:6379",
|
||||
'BROKER': 'dramatiq.brokers.redis.RedisBroker',
|
||||
'OPTIONS': {
|
||||
'url': 'redis://redis:6379',
|
||||
},
|
||||
"MIDDLEWARE": [
|
||||
"dramatiq.middleware.AgeLimit",
|
||||
"dramatiq.middleware.TimeLimit",
|
||||
"dramatiq.middleware.Callbacks",
|
||||
"dramatiq.middleware.Retries",
|
||||
"django_dramatiq.middleware.DbConnectionsMiddleware",
|
||||
"django_dramatiq.middleware.AdminMiddleware",
|
||||
'MIDDLEWARE': [
|
||||
'dramatiq.middleware.AgeLimit',
|
||||
'dramatiq.middleware.TimeLimit',
|
||||
'dramatiq.middleware.Callbacks',
|
||||
'dramatiq.middleware.Retries',
|
||||
'django_dramatiq.middleware.DbConnectionsMiddleware',
|
||||
'django_dramatiq.middleware.AdminMiddleware',
|
||||
],
|
||||
}
|
||||
|
||||
# Email config
|
||||
EMAIL_BACKEND = 'post_office.EmailBackend'
|
||||
|
||||
EMAIL_HOST = env.str("EMAIL_HOST", "smtp.seznam.cz")
|
||||
EMAIL_PORT = env.int("EMAIL_PORT", 465)
|
||||
EMAIL_HOST_USER = env.str("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
|
||||
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", True)
|
||||
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False)
|
||||
EMAIL_HOST = env.str('EMAIL_HOST', 'smtp.seznam.cz')
|
||||
EMAIL_PORT = env.int('EMAIL_PORT', 465)
|
||||
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD')
|
||||
EMAIL_USE_SSL = env.bool('EMAIL_USE_SSL', True)
|
||||
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', False)
|
||||
|
||||
DEFAULT_FROM_EMAIL = env.str("EMAIL_DEFAULT_FROM", "Facturio <no-reply@kropcloud.net>")
|
||||
DEFAULT_FROM_EMAIL = env.str('EMAIL_DEFAULT_FROM', 'Facturio <no-reply@kropcloud.net>')
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.contrib import admin
|
|||
|
||||
from . import models
|
||||
|
||||
|
||||
class InvoiceFileInline(admin.TabularInline):
|
||||
model = models.InvoiceFile
|
||||
extra = 0
|
||||
|
@ -11,6 +12,7 @@ class InvoiceFileInline(admin.TabularInline):
|
|||
]
|
||||
can_delete = False
|
||||
|
||||
|
||||
class InvoiceItemInline(admin.TabularInline):
|
||||
model = models.InvoiceItem
|
||||
extra = 0
|
||||
|
|
|
@ -10,7 +10,7 @@ from subjects import models as subject_models
|
|||
class InvoiceItemForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = models.InvoiceItem
|
||||
fields = ["amount", "amount_unit", "description", "price_for_amount"]
|
||||
fields = ['amount', 'amount_unit', 'description', 'price_for_amount']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -19,9 +19,14 @@ class InvoiceItemForm(forms.ModelForm):
|
|||
|
||||
|
||||
InvoiceItemFormSet = forms.inlineformset_factory(
|
||||
models.Invoice, models.InvoiceItem, form=InvoiceItemForm,
|
||||
fields=["description", "amount", "amount_unit", "price_for_amount"],
|
||||
extra=3, can_delete=False, validate_min=True, min_num=1,
|
||||
models.Invoice,
|
||||
models.InvoiceItem,
|
||||
form=InvoiceItemForm,
|
||||
fields=['description', 'amount', 'amount_unit', 'price_for_amount'],
|
||||
extra=3,
|
||||
can_delete=False,
|
||||
validate_min=True,
|
||||
min_num=1,
|
||||
)
|
||||
|
||||
|
||||
|
@ -39,12 +44,12 @@ class CreateInvoiceForm(forms.ModelForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self._user.supplier:
|
||||
self.fields["supplier"].queryset = subject_models.Subject.objects.filter(
|
||||
self.fields['supplier'].queryset = subject_models.Subject.objects.filter(
|
||||
id=self._user.supplier.id,
|
||||
)
|
||||
else:
|
||||
self.fields["supplier"].queryset = subject_models.Subject.objects.none()
|
||||
self.fields["customer"].queryset = self._user.customers
|
||||
self.fields['supplier'].queryset = subject_models.Subject.objects.none()
|
||||
self.fields['customer'].queryset = self._user.customers
|
||||
|
||||
self.helper = helper.FormHelper()
|
||||
self.helper.form_method = 'post'
|
||||
|
|
|
@ -88,25 +88,31 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'amount',
|
||||
models.DecimalField(
|
||||
decimal_places=3, max_digits=8, verbose_name='Amount',
|
||||
decimal_places=3,
|
||||
max_digits=8,
|
||||
verbose_name='Amount',
|
||||
),
|
||||
),
|
||||
(
|
||||
'amount_unit',
|
||||
models.CharField(
|
||||
max_length=32, verbose_name='Amount unit',
|
||||
max_length=32,
|
||||
verbose_name='Amount unit',
|
||||
),
|
||||
),
|
||||
(
|
||||
'description',
|
||||
models.CharField(
|
||||
max_length=64, verbose_name='Description',
|
||||
max_length=64,
|
||||
verbose_name='Description',
|
||||
),
|
||||
),
|
||||
(
|
||||
'price_for_amount',
|
||||
models.DecimalField(
|
||||
decimal_places=3, max_digits=8, verbose_name='Price for amount',
|
||||
decimal_places=3,
|
||||
max_digits=8,
|
||||
verbose_name='Price for amount',
|
||||
),
|
||||
),
|
||||
(
|
||||
|
|
|
@ -5,17 +5,17 @@ from django.db import models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("invoices", "0001_initial"),
|
||||
('invoices', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="invoice",
|
||||
name="invoice_date",
|
||||
model_name='invoice',
|
||||
name='invoice_date',
|
||||
field=models.DateField(
|
||||
default=django.utils.timezone.now, verbose_name="Invoice date",
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name='Invoice date',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoices', '0002_alter_invoice_invoice_date'),
|
||||
('subjects', '0005_alter_subjectdata_subject'),
|
||||
|
@ -15,21 +14,41 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='subjects.subject', verbose_name='Customer'),
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='+',
|
||||
to='subjects.subject',
|
||||
verbose_name='Customer',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='customer_data',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='subjects.subjectdata', verbose_name='Customer data'),
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='+',
|
||||
to='subjects.subjectdata',
|
||||
verbose_name='Customer data',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='subjects.subject', verbose_name='Supplier'),
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='+',
|
||||
to='subjects.subject',
|
||||
verbose_name='Supplier',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='supplier_data',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='subjects.subjectdata', verbose_name='Supplier data'),
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='+',
|
||||
to='subjects.subjectdata',
|
||||
verbose_name='Supplier data',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,19 +5,47 @@ from django.db import models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoices', '0003_alter_invoice_customer_alter_invoice_customer_data_and_more'),
|
||||
(
|
||||
'invoices',
|
||||
'0003_alter_invoice_customer_alter_invoice_customer_data_and_more',
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InvoiceFile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(blank=True, null=True, upload_to='invoice-files', verbose_name='File')),
|
||||
('created_date', models.DateTimeField(auto_now=True, verbose_name='Created Date')),
|
||||
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='invoices.invoice')),
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'file',
|
||||
models.FileField(
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to='invoice-files',
|
||||
verbose_name='File',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created_date',
|
||||
models.DateTimeField(auto_now=True, verbose_name='Created Date'),
|
||||
),
|
||||
(
|
||||
'invoice',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='files',
|
||||
to='invoices.invoice',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invoice File',
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoices', '0004_invoicefile'),
|
||||
]
|
||||
|
@ -13,6 +12,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='invoicefile',
|
||||
name='file',
|
||||
field=models.FileField(blank=True, null=True, upload_to='invoice-files/%Y/%m/%d/', verbose_name='File'),
|
||||
field=models.FileField(
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to='invoice-files/%Y/%m/%d/',
|
||||
verbose_name='File',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -17,17 +17,29 @@ class Invoice(models.Model):
|
|||
|
||||
user = models.ForeignKey(UserModel, models.CASCADE)
|
||||
supplier = models.ForeignKey(
|
||||
subject_models.Subject, on_delete=models.CASCADE, related_name='+', verbose_name=_('Supplier'),
|
||||
subject_models.Subject,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='+',
|
||||
verbose_name=_('Supplier'),
|
||||
)
|
||||
supplier_data = models.ForeignKey(
|
||||
subject_models.SubjectData, on_delete=models.CASCADE, related_name='+', verbose_name=_('Supplier data'),
|
||||
subject_models.SubjectData,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='+',
|
||||
verbose_name=_('Supplier data'),
|
||||
)
|
||||
|
||||
customer = models.ForeignKey(
|
||||
subject_models.Subject, on_delete=models.CASCADE, related_name='+', verbose_name=_('Customer'),
|
||||
subject_models.Subject,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='+',
|
||||
verbose_name=_('Customer'),
|
||||
)
|
||||
customer_data = models.ForeignKey(
|
||||
subject_models.SubjectData, on_delete=models.CASCADE, related_name='+', verbose_name=_('Customer data'),
|
||||
subject_models.SubjectData,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='+',
|
||||
verbose_name=_('Customer data'),
|
||||
)
|
||||
invoice_date = models.DateField(_('Invoice date'), default=timezone.now)
|
||||
due_date = models.DateField(_('Due date'))
|
||||
|
@ -42,7 +54,7 @@ class Invoice(models.Model):
|
|||
return total
|
||||
|
||||
def custom_id(self) -> str:
|
||||
return f"{self.invoice_date.year}-{self.id:04}"
|
||||
return f'{self.invoice_date.year}-{self.id:04}'
|
||||
|
||||
|
||||
class InvoiceItem(models.Model):
|
||||
|
@ -51,13 +63,17 @@ class InvoiceItem(models.Model):
|
|||
verbose_name_plural = _('Invoice Items')
|
||||
|
||||
invoice = models.ForeignKey(
|
||||
Invoice, on_delete=models.CASCADE, related_name='items',
|
||||
Invoice,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='items',
|
||||
)
|
||||
amount = models.DecimalField(_('Amount'), decimal_places=3, max_digits=8)
|
||||
amount_unit = models.CharField(_('Amount unit'), max_length=32)
|
||||
description = models.CharField(_('Description'), max_length=64)
|
||||
price_for_amount = models.DecimalField(
|
||||
_('Price for amount'), decimal_places=3, max_digits=8,
|
||||
_('Price for amount'),
|
||||
decimal_places=3,
|
||||
max_digits=8,
|
||||
)
|
||||
|
||||
def total_price(self) -> decimal.Decimal:
|
||||
|
@ -73,11 +89,13 @@ class InvoiceFile(models.Model):
|
|||
verbose_name_plural = _('Invoice Files')
|
||||
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='files')
|
||||
file = models.FileField(_('File'), upload_to='invoice-files/%Y/%m/%d/', null=True, blank=True)
|
||||
file = models.FileField(
|
||||
_('File'), upload_to='invoice-files/%Y/%m/%d/', null=True, blank=True,
|
||||
)
|
||||
created_date = models.DateTimeField(_('Created Date'), auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.file.name
|
||||
|
||||
def get_file_name(self, ext: str) -> str:
|
||||
return f"{self.invoice.id}-{self.created_date.strftime('%H%M%S')}.{ext}"
|
||||
return f'{self.invoice.id}-{self.created_date.strftime("%H%M%S")}.{ext}'
|
||||
|
|
|
@ -10,11 +10,13 @@ from invoices.models import Invoice
|
|||
@dramatiq.actor()
|
||||
def generate_pdf(invoice_id: int):
|
||||
invoice = Invoice.objects.get(pk=invoice_id)
|
||||
print_url = f"{settings.INTERNAL_API_URL}{reverse('invoices:print_invoice', kwargs=dict(invoice_id=invoice.id))}"
|
||||
print_url = f'{settings.INTERNAL_API_URL}{reverse("invoices:print_invoice", kwargs=dict(invoice_id=invoice.id))}'
|
||||
|
||||
invoice_file = invoice.files.create()
|
||||
|
||||
with GotenbergClient(settings.GOTENBERG_API_URL) as client:
|
||||
with client.chromium.url_to_pdf() as task:
|
||||
res = task.url(print_url).run()
|
||||
invoice_file.file.save(invoice_file.get_file_name('pdf'), ContentFile(res.content))
|
||||
invoice_file.file.save(
|
||||
invoice_file.get_file_name('pdf'), ContentFile(res.content),
|
||||
)
|
||||
|
|
|
@ -21,25 +21,36 @@ def home(req: HttpRequest) -> HttpResponse:
|
|||
with transaction.atomic():
|
||||
invoice = form.save()
|
||||
formset = forms.InvoiceItemFormSet(
|
||||
data=req.POST, instance=invoice,
|
||||
data=req.POST,
|
||||
instance=invoice,
|
||||
)
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
tasks.generate_pdf.send(
|
||||
invoice.id,
|
||||
)
|
||||
return redirect(reverse('invoices:invoice', kwargs=dict(invoice_id=invoice.id)))
|
||||
return redirect(
|
||||
reverse('invoices:invoice', kwargs=dict(invoice_id=invoice.id)),
|
||||
)
|
||||
else:
|
||||
transaction.set_rollback(True)
|
||||
else:
|
||||
formset = forms.InvoiceItemFormSet(data=req.POST)
|
||||
|
||||
return render(req, 'invoices/index.html', dict(form=form, formset=formset, invoices=user_invoices))
|
||||
return render(
|
||||
req,
|
||||
'invoices/index.html',
|
||||
dict(form=form, formset=formset, invoices=user_invoices),
|
||||
)
|
||||
|
||||
elif req.method == 'GET':
|
||||
form = forms.CreateInvoiceForm(current_user=req.user)
|
||||
formset = forms.InvoiceItemFormSet()
|
||||
return render(req, 'invoices/index.html', dict(form=form, formset=formset, invoices=user_invoices))
|
||||
return render(
|
||||
req,
|
||||
'invoices/index.html',
|
||||
dict(form=form, formset=formset, invoices=user_invoices),
|
||||
)
|
||||
|
||||
return HttpResponse(status=405)
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class SelectSubjectForm(forms.ModelForm):
|
|||
self._user = kwargs.pop('current_user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields["supplier"].queryset = models.Subject.objects.exclude(
|
||||
self.fields['supplier'].queryset = models.Subject.objects.exclude(
|
||||
id__in=self._user.get_customers(),
|
||||
)
|
||||
|
||||
|
|
|
@ -25,7 +25,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'vat_id',
|
||||
models.CharField(
|
||||
blank=True, max_length=12, null=True, verbose_name='vat_id',
|
||||
blank=True,
|
||||
max_length=12,
|
||||
null=True,
|
||||
verbose_name='vat_id',
|
||||
),
|
||||
),
|
||||
('street', models.CharField(max_length=64, verbose_name='street')),
|
||||
|
@ -34,7 +37,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'city_part',
|
||||
models.CharField(
|
||||
blank=True, max_length=64, null=True, verbose_name='city_part',
|
||||
blank=True,
|
||||
max_length=64,
|
||||
null=True,
|
||||
verbose_name='city_part',
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -18,14 +18,20 @@ class Migration(migrations.Migration):
|
|||
model_name='subject',
|
||||
name='city_part',
|
||||
field=models.CharField(
|
||||
blank=True, max_length=64, null=True, verbose_name='City part',
|
||||
blank=True,
|
||||
max_length=64,
|
||||
null=True,
|
||||
verbose_name='City part',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subject',
|
||||
name='id',
|
||||
field=models.CharField(
|
||||
max_length=8, primary_key=True, serialize=False, verbose_name='CIN',
|
||||
max_length=8,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='CIN',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
@ -42,7 +48,10 @@ class Migration(migrations.Migration):
|
|||
model_name='subject',
|
||||
name='vat_id',
|
||||
field=models.CharField(
|
||||
blank=True, max_length=12, null=True, verbose_name='VAT ID',
|
||||
blank=True,
|
||||
max_length=12,
|
||||
null=True,
|
||||
verbose_name='VAT ID',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
|
|
@ -49,13 +49,17 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'city_part',
|
||||
models.CharField(
|
||||
blank=True, max_length=64, null=True, verbose_name='City part',
|
||||
blank=True,
|
||||
max_length=64,
|
||||
null=True,
|
||||
verbose_name='City part',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created_date',
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name='Created date',
|
||||
auto_now_add=True,
|
||||
verbose_name='Created date',
|
||||
),
|
||||
),
|
||||
(
|
||||
|
|
|
@ -5,19 +5,18 @@ from django.db import models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("subjects", "0004_remove_subject_city_remove_subject_city_part_and_more"),
|
||||
('subjects', '0004_remove_subject_city_remove_subject_city_part_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="subjectdata",
|
||||
name="subject",
|
||||
model_name='subjectdata',
|
||||
name='subject',
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="subject_data",
|
||||
to="subjects.subject",
|
||||
related_name='subject_data',
|
||||
to='subjects.subject',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -9,13 +9,20 @@ class Subject(models.Model):
|
|||
|
||||
id = models.CharField(_('CIN'), max_length=8, primary_key=True)
|
||||
vat_id = models.CharField(
|
||||
_('VAT ID'), max_length=12, null=True, blank=True,
|
||||
_('VAT ID'),
|
||||
max_length=12,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def get_latest_data(self):
|
||||
return self.subject_data.filter(subject=self).order_by(
|
||||
'-created_date',
|
||||
).first()
|
||||
return (
|
||||
self.subject_data.filter(subject=self)
|
||||
.order_by(
|
||||
'-created_date',
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.id} - {self.get_latest_data().name}'
|
||||
|
@ -27,14 +34,19 @@ class SubjectData(models.Model):
|
|||
verbose_name_plural = _('Subject Datas')
|
||||
|
||||
subject = models.ForeignKey(
|
||||
Subject, on_delete=models.CASCADE, related_name='subject_data',
|
||||
Subject,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='subject_data',
|
||||
)
|
||||
name = models.CharField(_('Name'), max_length=128)
|
||||
street = models.CharField(_('Street'), max_length=64)
|
||||
zip_code = models.CharField(_('Zip Code'), max_length=6)
|
||||
city = models.CharField(_('City'), max_length=64)
|
||||
city_part = models.CharField(
|
||||
_('City part'), max_length=64, null=True, blank=True,
|
||||
_('City part'),
|
||||
max_length=64,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
created_date = models.DateTimeField(_('Created date'), auto_now_add=True)
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ def build_address(street: str, zip_code: int | str, city: str, city_part: str) -
|
|||
def main_page(req: HttpRequest) -> HttpResponse:
|
||||
if req.method == 'POST':
|
||||
select_subject_form = forms.SelectSubjectForm(
|
||||
data=req.POST, instance=req.user, current_user=req.user,
|
||||
data=req.POST,
|
||||
instance=req.user,
|
||||
current_user=req.user,
|
||||
)
|
||||
if select_subject_form.is_valid():
|
||||
select_subject_form.save()
|
||||
|
@ -29,7 +31,8 @@ def main_page(req: HttpRequest) -> HttpResponse:
|
|||
id=req.user.supplier.id if req.user.supplier else None,
|
||||
)
|
||||
select_subject_form = forms.SelectSubjectForm(
|
||||
instance=req.user, current_user=req.user,
|
||||
instance=req.user,
|
||||
current_user=req.user,
|
||||
)
|
||||
return render(
|
||||
req,
|
||||
|
@ -54,7 +57,8 @@ def create_subject(req: HttpRequest) -> HttpResponse:
|
|||
ares_legal_data = ares_data['legal']
|
||||
|
||||
subject = models.Subject.objects.create(
|
||||
id=ares_legal_data['company_id'], vat_id=ares_legal_data['company_vat_id'],
|
||||
id=ares_legal_data['company_id'],
|
||||
vat_id=ares_legal_data['company_vat_id'],
|
||||
)
|
||||
models.SubjectData.objects.create(
|
||||
subject=subject,
|
||||
|
|
|
@ -15,18 +15,17 @@ from django_dramatiq.models import Task
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_JOB_KWARGS = {
|
||||
"max_instances": 1,
|
||||
"replace_existing": True,
|
||||
'max_instances': 1,
|
||||
'replace_existing': True,
|
||||
}
|
||||
|
||||
DAY_SEC = 24*60*60
|
||||
DAY_SEC = 24 * 60 * 60
|
||||
|
||||
PERIODIC_JOBS = [
|
||||
{
|
||||
"task": "utils.tasks:send_queued_mail_task.send",
|
||||
"trigger": IntervalTrigger(seconds=30),
|
||||
'task': 'utils.tasks:send_queued_mail_task.send',
|
||||
'trigger': IntervalTrigger(seconds=30),
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
@ -48,21 +47,23 @@ def delete_old_job_executions(max_age: int = 7 * DAY_SEC) -> None:
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Runs APScheduler." # noqa: A003
|
||||
help = 'Runs APScheduler.' # noqa: A003
|
||||
scheduler: BlockingScheduler = None
|
||||
|
||||
def prepare_scheduler(self):
|
||||
self.stdout.write(self.style.NOTICE("Preparing scheduler"))
|
||||
self.stdout.write(self.style.NOTICE('Preparing scheduler'))
|
||||
self.scheduler = BlockingScheduler(timezone=settings.TIME_ZONE)
|
||||
self.scheduler.add_jobstore(DjangoJobStore(), "default")
|
||||
self.scheduler.add_jobstore(DjangoJobStore(), 'default')
|
||||
|
||||
def add_jobs(self):
|
||||
self.scheduler.add_job(
|
||||
delete_old_job_executions,
|
||||
trigger=CronTrigger(
|
||||
day_of_week="mon", hour="00", minute="00",
|
||||
day_of_week='mon',
|
||||
hour='00',
|
||||
minute='00',
|
||||
), # Midnight on Monday, before the start of the next work week.
|
||||
id="delete_old_job_executions",
|
||||
id='delete_old_job_executions',
|
||||
max_instances=1,
|
||||
replace_existing=True,
|
||||
)
|
||||
|
@ -70,19 +71,19 @@ class Command(BaseCommand):
|
|||
|
||||
for job in PERIODIC_JOBS:
|
||||
kwargs = DEFAULT_JOB_KWARGS | deepcopy(job)
|
||||
task = kwargs.pop("task")
|
||||
task = kwargs.pop('task')
|
||||
|
||||
if "id" not in kwargs:
|
||||
kwargs["id"] = task
|
||||
if 'id' not in kwargs:
|
||||
kwargs['id'] = task
|
||||
|
||||
self.scheduler.add_job(task, **kwargs)
|
||||
|
||||
self.stdout.write(f"Added job: {task}")
|
||||
self.stdout.write(f'Added job: {task}')
|
||||
|
||||
def handle_shutdown(self, *args, **kwargs): # noqa: ARG001
|
||||
self.stdout.write(self.style.NOTICE("Stopping scheduler..."))
|
||||
self.stdout.write(self.style.NOTICE('Stopping scheduler...'))
|
||||
self.scheduler.shutdown()
|
||||
self.stdout.write(self.style.NOTICE("Scheduler shut down successfully!"))
|
||||
self.stdout.write(self.style.NOTICE('Scheduler shut down successfully!'))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.prepare_scheduler()
|
||||
|
@ -91,7 +92,7 @@ class Command(BaseCommand):
|
|||
signal.signal(signal.SIGTERM, self.handle_shutdown)
|
||||
|
||||
try:
|
||||
self.stdout.write(self.style.NOTICE("Starting scheduler..."))
|
||||
self.stdout.write(self.style.NOTICE('Starting scheduler...'))
|
||||
self.scheduler.start()
|
||||
except KeyboardInterrupt:
|
||||
self.handle_shutdown()
|
||||
|
|
Reference in a new issue