From 6763e773a6eabdf18706239646cf969951756d44 Mon Sep 17 00:00:00 2001 From: Martin Quarda Date: Sun, 13 Oct 2024 08:01:39 +0200 Subject: [PATCH] shop pokus --- ...nvoice_product_remove_racer_id_and_more.py | 125 ++++++++++++++++++ alkatorapi/models.py | 73 +++++++++- alkatorapi/templates/invoice.html | 58 ++------ alkatorapi/views.py | 57 +++++--- get_invoices.py | 28 ---- 5 files changed, 234 insertions(+), 107 deletions(-) create mode 100644 alkatorapi/migrations/0019_cart_invoice_product_remove_racer_id_and_more.py delete mode 100644 get_invoices.py diff --git a/alkatorapi/migrations/0019_cart_invoice_product_remove_racer_id_and_more.py b/alkatorapi/migrations/0019_cart_invoice_product_remove_racer_id_and_more.py new file mode 100644 index 0000000..6739779 --- /dev/null +++ b/alkatorapi/migrations/0019_cart_invoice_product_remove_racer_id_and_more.py @@ -0,0 +1,125 @@ +# Generated by Django 4.2.12 on 2024-10-13 05:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('alkatorapi', '0018_profile_phone'), + ] + + operations = [ + migrations.CreateModel( + name='Cart', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='Invoice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('invoice_id', models.IntegerField(unique=True)), + ('total_price', models.IntegerField()), + ('paid_date', models.DateTimeField(auto_now=True)), + ('paid', models.BooleanField(default=False)), + ('trans_id', models.CharField(blank=True, max_length=120, null=True)), + ('address', models.CharField(blank=True, max_length=255, null=True)), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=120)), + ('description', models.TextField()), + ('img', models.CharField(max_length=120)), + ('price', models.IntegerField()), + ('quantity', models.IntegerField()), + ], + ), + migrations.RemoveField( + model_name='racer', + name='id', + ), + migrations.RemoveField( + model_name='racer', + name='invoice_id', + ), + migrations.RemoveField( + model_name='racer', + name='paid', + ), + migrations.RemoveField( + model_name='racer', + name='price', + ), + migrations.RemoveField( + model_name='racer', + name='register_date', + ), + migrations.RemoveField( + model_name='racer', + name='trans_id', + ), + migrations.AlterField( + model_name='racer', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='racers', to='alkatorapi.profile'), + ), + migrations.CreateModel( + name='InvoiceProduct', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.IntegerField()), + ('price', models.IntegerField(default=1)), + ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='alkatorapi.invoice')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='alkatorapi.product')), + ], + options={ + 'unique_together': {('product', 'invoice')}, + }, + ), + migrations.AddField( + model_name='invoice', + name='items', + field=models.ManyToManyField(through='alkatorapi.InvoiceProduct', to='alkatorapi.product'), + ), + migrations.AddField( + model_name='invoice', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='CartProduct', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.IntegerField()), + ('cart', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='alkatorapi.cart')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='alkatorapi.product')), + ], + options={ + 'unique_together': {('product', 'cart')}, + }, + ), + migrations.AddField( + model_name='cart', + name='items', + field=models.ManyToManyField(through='alkatorapi.CartProduct', to='alkatorapi.product'), + ), + migrations.AddField( + model_name='cart', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='racer', + name='invoiceproduct_ptr', + field=models.OneToOneField(auto_created=True, default=1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='alkatorapi.invoiceproduct'), + preserve_default=False, + ), + ] diff --git a/alkatorapi/models.py b/alkatorapi/models.py index ffbc379..ac07add 100644 --- a/alkatorapi/models.py +++ b/alkatorapi/models.py @@ -1,6 +1,7 @@ from django.db import models from django.contrib import admin from django.contrib.auth.models import User as DjangoUser +from django.core.exceptions import ValidationError ALKATOR_CHOICES = ( (1, "Alkátor"), @@ -56,23 +57,81 @@ class Profile(models.Model): return f"" -class Racer(models.Model): - profile = models.ForeignKey(Profile, related_name='racers', on_delete=models.CASCADE) +class Product(models.Model): + name = models.CharField(max_length=120) + description = models.TextField() + img = models.CharField(max_length=120) + price = models.IntegerField() + quantity = models.IntegerField() + + +class Cart(models.Model): + user = models.ForeignKey(DjangoUser, on_delete=models.RESTRICT) + items = models.ManyToManyField(Product, through='CartProduct') + + +class CartProduct(models.Model): + product = models.ForeignKey(Product, on_delete=models.RESTRICT) + cart = models.ForeignKey('Cart', on_delete=models.RESTRICT) + class Meta: + unique_together = ('product', 'cart') + quantity = models.IntegerField() + + def clean(self): + data = self.cleaned_data + if data['quantity'] <= 0: + raise ValidationError("Počet předmětů v košíku musí být kladný!") + + +class Invoice(models.Model): + invoice_id = models.IntegerField(unique=True) + user = models.ForeignKey(DjangoUser, on_delete=models.RESTRICT) + items = models.ManyToManyField(Product, through='InvoiceProduct') + total_price = models.IntegerField() + paid_date = models.DateTimeField(auto_now=True) + paid = models.BooleanField(default=False) + trans_id = models.CharField(null=True, blank=True, max_length=120) + address = models.CharField(max_length=255, null=True, blank=True) + + def calculate_total_price(self): + total_price = 0 + for item in InvoiceProduct.objects.filter(invoice=self): + total_price += item.price * item.quantity + return total_price + + +class InvoiceProduct(models.Model): + product = models.ForeignKey(Product, on_delete=models.RESTRICT) + invoice = models.ForeignKey(Invoice, on_delete=models.RESTRICT) + class Meta: + unique_together = ('product', 'invoice') + quantity = models.IntegerField() + price = models.IntegerField(default=1) + + def clean(self): + data = self.cleaned_data + if data['quantity'] <= 0: + raise ValidationError("Počet předmětů ve faktuře musí být kladný!") + + +class Racer(InvoiceProduct): + profile = models.ForeignKey(Profile, related_name='racers', on_delete=models.RESTRICT) first_name = models.CharField(max_length=120) last_name = models.CharField(max_length=120) email = models.EmailField(max_length=120, null=True, blank=True) team = models.CharField(max_length=120, null=True, blank=True) phone = models.CharField(max_length=120, null=True, blank=True) date_of_birth = models.DateField(null=True, blank=True) - register_date = models.DateTimeField(auto_now=True) duration = models.DurationField(null=True, blank=True) starting_number = models.IntegerField(null=True, blank=True) alkator_category = models.IntegerField(choices=ALKATOR_CHOICES, default=1) alkator_class = models.IntegerField(choices=ALKATOR_CLASSES) - trans_id = models.CharField(null=True, blank=True, max_length=120) - price = models.IntegerField(default=690) - paid = models.BooleanField(default=False) - invoice_id = models.IntegerField(null=True, blank=True, unique=True) + + def clean(self): + super(self).clean() + data = self.cleaned_data + if data['quantity'] != 1: + raise ValidationError("Počet přihlášek v jedné objednávce musí být právě 1!") def __str__(self): return f"" diff --git a/alkatorapi/templates/invoice.html b/alkatorapi/templates/invoice.html index b0d3256..076ce51 100644 --- a/alkatorapi/templates/invoice.html +++ b/alkatorapi/templates/invoice.html @@ -21,7 +21,7 @@ Číslo faktury - {{racer.invoice_id}} + {{invoice.invoice_id}} Dodavatel @@ -166,7 +166,7 @@ 131-2219860207/0100 Datum vystavení - {{racer.register_date|date:"d/m/Y"}} + {{invoice.paid_date|date:"d/m/Y"}} Variabilní symbol @@ -210,61 +210,19 @@ Cena za MJ Cena + {% for product in products %} - Startovné do závodu - ALKATOR + {{product.product.name}} 1 - {{racer.price}},00 Kč - - {{racer.price}},00 Kč - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {{product.price}},00 Kč + {{product.price}},00 Kč + {% endfor %} @@ -272,7 +230,7 @@ Celkem - {{racer.price}},00 Kč + {{invoice.total_price}},00 Kč diff --git a/alkatorapi/views.py b/alkatorapi/views.py index bc540ac..d298dd9 100644 --- a/alkatorapi/views.py +++ b/alkatorapi/views.py @@ -19,7 +19,7 @@ import PIL.Image import random from collections import OrderedDict -from .models import User, ALKATOR_CHOICES_DICT, ALKATOR_CLASSES, Profile, Racer +from .models import User, ALKATOR_CHOICES_DICT, ALKATOR_CLASSES, Profile, Racer, Invoice, Product from alkator.settings import COMGATE_MERCHANT, COMGATE_SECRET, COMGATE_TEST @@ -116,15 +116,15 @@ def register_racer(request): except User.DoesNotExist: invoice_id = invoice_id + 1 - if date.today() >= date(2024, 9, 21): - price = 79000 - else: - price = 69000 - profile = request.user.profile user = request.user + product = Product.objects.get(id=1) + price = product.price + racer = Racer( + product=product, + quantity=1, profile = profile, first_name = request.POST['first_name'], last_name = request.POST['last_name'], @@ -133,15 +133,22 @@ def register_racer(request): phone = request.POST['phone'], date_of_birth = dob, alkator_class = ALKATOR_CLASS, - price = price // 100, - invoice_id = invoice_id, + price=price, + ) + invoice = Invoice( + invoice_id=invoice_id, + user=user, + items=[racer], + total_price=price, + address=profile.address, ) racer.save() + invoice.save() payment_data = { 'merchant': COMGATE_MERCHANT, 'test': 'true' if COMGATE_TEST else 'false', - 'price': price, + 'price': price * 100, 'curr': 'CZK', 'method': 'ALL', 'label': 'Startovné na závod Alkátor Race Dolní Čermná 2025', @@ -157,10 +164,11 @@ def register_racer(request): if result['code'][0] != '0': racer.delete() + invoice.delete() return HttpResponse('{"reason":"Chyba na straně platební brány: ' + result['message'][0] + ', zkuste prosím registraci později."}', status=400, content_type='application/json') - racer.trans_id = result['transId'][0] - racer.save() + invoice.trans_id = result['transId'][0] + invoice.save() return HttpResponse('{"success":"", "redirect":"' + result['redirect'][0] + '"}', content_type='application/json') @@ -177,14 +185,14 @@ def login_status(request): "last_name": user.profile.last_name, "address": user.profile.address, "racers": [{ - "invoice_id": racer.invoice_id, + "invoice_id": racer.invoice.invoice_id, "first_name": racer.first_name, "last_name": racer.last_name, "email": racer.email, "phone": racer.phone, "team": racer.team, "date_of_birth": racer.date_of_birth.strftime("%Y-%m-%d"), - "paid": racer.paid, + "paid": racer.invoice.paid, } for racer in racers] }), content_type='application/json') @@ -231,13 +239,14 @@ def payment_result(request): if not secret_match or test != COMGATE_TEST: return HttpResponse(status=400) try: - racer = Racer.objects.get(invoice_id=ref_id) - except Racer.DoesNotExist: + invoice = Invoice.objects.get(invoice_id=ref_id) + except Invoice.DoesNotExist: mail_admins('Chyba s platbou!', f'invoice_id={ref_id}&paid={paid}') return HttpResponse(status=404) if paid == 'PAID': - racer.paid = True - racer.save() + invoice.paid = True + invoice.save() + racer = Racer.objects.get(invoice=invoice) mail = EmailMessage( subject=f"úspěšná registrace do závodu Alkátor race Dolní Čermná ({racer.first_name} {racer.last_name})", body=f"""Zdravím tě Alkátore, @@ -265,14 +274,15 @@ web: https://alkator.cz""", cc=[] ) - user = racer.profile.user + user = invoice.user template = TemplateResponse( None, 'invoice.html', { 'user': user, - 'racer': racer, + 'invoice': invoice, + 'products': InvoiceProduct.filter(invoice=invoice), 'paid_date': racer.register_date + timedelta(days=1), } ) @@ -282,7 +292,8 @@ web: https://alkator.cz""", attach = open(pdf_name, 'rb') mail.attach('faktura.pdf', attach.read(), 'application/pdf') - mail.send() + if not mail.send(): + return HttpResponse(status=500) elif paid == 'CANCELLED' and not user.paid: racer.delete() return HttpResponse(status=200) @@ -362,12 +373,14 @@ def upload_files(request): @staff_member_required def invoice(request): - user = User.objects.get(invoice_id=request.GET['invoice_id']) + user = Invoice.objects.get(invoice_id=request.GET['invoice_id']) return TemplateResponse( None, 'invoice.html', { 'user': user, - 'paid_date': user.register_date + timedelta(days=1), + 'invoice': invoice, + 'products': InvoiceProduct.filter(invoice=invoice), + 'paid_date': racer.register_date + timedelta(days=1), } ) diff --git a/get_invoices.py b/get_invoices.py deleted file mode 100644 index 9b1736c..0000000 --- a/get_invoices.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import date, datetime, timedelta -from django.template.response import TemplateResponse -from alkatorapi.models import User -from weasyprint import HTML -from pypdf import PdfWriter -import asyncio - -def html_to_pdf(html_content, output_path): - HTML(string=html_content).write_pdf(output_path) - -merger = PdfWriter() - -for user in User.objects.all().order_by('last_name', 'first_name'): - template = TemplateResponse( - None, - 'invoice.html', - { - 'user': user, - 'paid_date': user.register_date + timedelta(days=1), - } - ) - template.render() - pdf_name = f"{user.last_name}_{user.first_name}.pdf" - html_to_pdf(template.content, pdf_name) - merger.append(pdf_name) - -merger.write('result.pdf') -merger.close()