shop pokus

This commit is contained in:
Martin Quarda 2024-10-13 08:01:39 +02:00
parent 2c2699de1f
commit 6763e773a6
5 changed files with 234 additions and 107 deletions

View File

@ -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,
),
]

View File

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth.models import User as DjangoUser
from django.core.exceptions import ValidationError
ALKATOR_CHOICES = ( ALKATOR_CHOICES = (
(1, "Alkátor"), (1, "Alkátor"),
@ -56,23 +57,81 @@ class Profile(models.Model):
return f"<Profile {self.user.email} {self.first_name} {self.last_name}>" return f"<Profile {self.user.email} {self.first_name} {self.last_name}>"
class Racer(models.Model): class Product(models.Model):
profile = models.ForeignKey(Profile, related_name='racers', on_delete=models.CASCADE) 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) first_name = models.CharField(max_length=120)
last_name = models.CharField(max_length=120) last_name = models.CharField(max_length=120)
email = models.EmailField(max_length=120, null=True, blank=True) email = models.EmailField(max_length=120, null=True, blank=True)
team = models.CharField(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) phone = models.CharField(max_length=120, null=True, blank=True)
date_of_birth = models.DateField(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) duration = models.DurationField(null=True, blank=True)
starting_number = models.IntegerField(null=True, blank=True) starting_number = models.IntegerField(null=True, blank=True)
alkator_category = models.IntegerField(choices=ALKATOR_CHOICES, default=1) alkator_category = models.IntegerField(choices=ALKATOR_CHOICES, default=1)
alkator_class = models.IntegerField(choices=ALKATOR_CLASSES) alkator_class = models.IntegerField(choices=ALKATOR_CLASSES)
trans_id = models.CharField(null=True, blank=True, max_length=120)
price = models.IntegerField(default=690) def clean(self):
paid = models.BooleanField(default=False) super(self).clean()
invoice_id = models.IntegerField(null=True, blank=True, unique=True) 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): def __str__(self):
return f"<Racer {self.email} {self.first_name} {self.last_name} {self.team}>" return f"<Racer {self.email} {self.first_name} {self.last_name} {self.team}>"

View File

@ -21,7 +21,7 @@
<tr> <tr>
<td class="tg-7zrl" colspan="6"></td> <td class="tg-7zrl" colspan="6"></td>
<td class="tg-7zrl">Číslo faktury</td> <td class="tg-7zrl">Číslo faktury</td>
<td class="tg-8d8j" colspan="2">{{racer.invoice_id}}</td> <td class="tg-8d8j" colspan="2">{{invoice.invoice_id}}</td>
</tr> </tr>
<tr> <tr>
<td class="tg-uzm7"><span style="background-color:#BFFE8D">Dodavatel</span></td> <td class="tg-uzm7"><span style="background-color:#BFFE8D">Dodavatel</span></td>
@ -166,7 +166,7 @@
<td class="tg-7zrl" colspan="2">131-2219860207/0100</td> <td class="tg-7zrl" colspan="2">131-2219860207/0100</td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Datum vystavení</span></td> <td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Datum vystavení</span></td>
<td class="tg-2b7s" colspan="2">{{racer.register_date|date:"d/m/Y"}}</td> <td class="tg-2b7s" colspan="2">{{invoice.paid_date|date:"d/m/Y"}}</td>
</tr> </tr>
<tr> <tr>
<td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Variabilní symbol</span></td> <td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Variabilní symbol</span></td>
@ -210,61 +210,19 @@
<td class="tg-2b7s" colspan="2">Cena za MJ</td> <td class="tg-2b7s" colspan="2">Cena za MJ</td>
<td class="tg-2b7s" colspan="2">Cena</td> <td class="tg-2b7s" colspan="2">Cena</td>
</tr> </tr>
{% for product in products %}
<tr> <tr>
<td class="tg-71pv">Startovné do závodu - ALKATOR</td> <td class="tg-71pv">{{product.product.name}}</td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-2b7s">1</td> <td class="tg-2b7s">1</td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-2b7s">{{racer.price}},00 Kč</td> <td class="tg-2b7s">{{product.price}},00 Kč</td>
<td class="tg-7zrl"></td>
<td class="tg-2b7s">{{racer.price}},00 Kč</td>
</tr>
<tr>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
</tr>
<tr>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
</tr>
<tr>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
</tr>
<tr>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-2b7s">{{product.price}},00 Kč</td>
</tr> </tr>
{% endfor %}
<tr> <tr>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
@ -272,7 +230,7 @@
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>
<td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Celkem</span></td> <td class="tg-hvob" colspan="2"><span style="background-color:#A8DEF1">Celkem</span></td>
<td class="tg-kbc9" colspan="2"><span style="font-weight:bold;color:#FFF;background-color:#66B3ED">{{racer.price}},00 Kč</span></td> <td class="tg-kbc9" colspan="2"><span style="font-weight:bold;color:#FFF;background-color:#66B3ED">{{invoice.total_price}},00 Kč</span></td>
</tr> </tr>
<tr> <tr>
<td class="tg-7zrl"></td> <td class="tg-7zrl"></td>

View File

@ -19,7 +19,7 @@ import PIL.Image
import random import random
from collections import OrderedDict 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 from alkator.settings import COMGATE_MERCHANT, COMGATE_SECRET, COMGATE_TEST
@ -116,15 +116,15 @@ def register_racer(request):
except User.DoesNotExist: except User.DoesNotExist:
invoice_id = invoice_id + 1 invoice_id = invoice_id + 1
if date.today() >= date(2024, 9, 21):
price = 79000
else:
price = 69000
profile = request.user.profile profile = request.user.profile
user = request.user user = request.user
product = Product.objects.get(id=1)
price = product.price
racer = Racer( racer = Racer(
product=product,
quantity=1,
profile = profile, profile = profile,
first_name = request.POST['first_name'], first_name = request.POST['first_name'],
last_name = request.POST['last_name'], last_name = request.POST['last_name'],
@ -133,15 +133,22 @@ def register_racer(request):
phone = request.POST['phone'], phone = request.POST['phone'],
date_of_birth = dob, date_of_birth = dob,
alkator_class = ALKATOR_CLASS, alkator_class = ALKATOR_CLASS,
price = price // 100, price=price,
invoice_id = invoice_id, )
invoice = Invoice(
invoice_id=invoice_id,
user=user,
items=[racer],
total_price=price,
address=profile.address,
) )
racer.save() racer.save()
invoice.save()
payment_data = { payment_data = {
'merchant': COMGATE_MERCHANT, 'merchant': COMGATE_MERCHANT,
'test': 'true' if COMGATE_TEST else 'false', 'test': 'true' if COMGATE_TEST else 'false',
'price': price, 'price': price * 100,
'curr': 'CZK', 'curr': 'CZK',
'method': 'ALL', 'method': 'ALL',
'label': 'Startovné na závod Alkátor Race Dolní Čermná 2025', 'label': 'Startovné na závod Alkátor Race Dolní Čermná 2025',
@ -157,10 +164,11 @@ def register_racer(request):
if result['code'][0] != '0': if result['code'][0] != '0':
racer.delete() 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') 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] invoice.trans_id = result['transId'][0]
racer.save() invoice.save()
return HttpResponse('{"success":"", "redirect":"' + result['redirect'][0] + '"}', content_type='application/json') 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, "last_name": user.profile.last_name,
"address": user.profile.address, "address": user.profile.address,
"racers": [{ "racers": [{
"invoice_id": racer.invoice_id, "invoice_id": racer.invoice.invoice_id,
"first_name": racer.first_name, "first_name": racer.first_name,
"last_name": racer.last_name, "last_name": racer.last_name,
"email": racer.email, "email": racer.email,
"phone": racer.phone, "phone": racer.phone,
"team": racer.team, "team": racer.team,
"date_of_birth": racer.date_of_birth.strftime("%Y-%m-%d"), "date_of_birth": racer.date_of_birth.strftime("%Y-%m-%d"),
"paid": racer.paid, "paid": racer.invoice.paid,
} for racer in racers] } for racer in racers]
}), content_type='application/json') }), content_type='application/json')
@ -231,13 +239,14 @@ def payment_result(request):
if not secret_match or test != COMGATE_TEST: if not secret_match or test != COMGATE_TEST:
return HttpResponse(status=400) return HttpResponse(status=400)
try: try:
racer = Racer.objects.get(invoice_id=ref_id) invoice = Invoice.objects.get(invoice_id=ref_id)
except Racer.DoesNotExist: except Invoice.DoesNotExist:
mail_admins('Chyba s platbou!', f'invoice_id={ref_id}&paid={paid}') mail_admins('Chyba s platbou!', f'invoice_id={ref_id}&paid={paid}')
return HttpResponse(status=404) return HttpResponse(status=404)
if paid == 'PAID': if paid == 'PAID':
racer.paid = True invoice.paid = True
racer.save() invoice.save()
racer = Racer.objects.get(invoice=invoice)
mail = EmailMessage( mail = EmailMessage(
subject=f"úspěšná registrace do závodu Alkátor race Dolní Čermná ({racer.first_name} {racer.last_name})", 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, body=f"""Zdravím tě Alkátore,
@ -265,14 +274,15 @@ web: https://alkator.cz""",
cc=[] cc=[]
) )
user = racer.profile.user user = invoice.user
template = TemplateResponse( template = TemplateResponse(
None, None,
'invoice.html', 'invoice.html',
{ {
'user': user, 'user': user,
'racer': racer, 'invoice': invoice,
'products': InvoiceProduct.filter(invoice=invoice),
'paid_date': racer.register_date + timedelta(days=1), 'paid_date': racer.register_date + timedelta(days=1),
} }
) )
@ -282,7 +292,8 @@ web: https://alkator.cz""",
attach = open(pdf_name, 'rb') attach = open(pdf_name, 'rb')
mail.attach('faktura.pdf', attach.read(), 'application/pdf') 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: elif paid == 'CANCELLED' and not user.paid:
racer.delete() racer.delete()
return HttpResponse(status=200) return HttpResponse(status=200)
@ -362,12 +373,14 @@ def upload_files(request):
@staff_member_required @staff_member_required
def invoice(request): 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( return TemplateResponse(
None, None,
'invoice.html', 'invoice.html',
{ {
'user': user, '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),
} }
) )

View File

@ -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()