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.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"<Profile {self.user.email} {self.first_name} {self.last_name}>"
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"<Racer {self.email} {self.first_name} {self.last_name} {self.team}>"

View File

@ -21,7 +21,7 @@
<tr>
<td class="tg-7zrl" colspan="6"></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>
<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"></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>
<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</td>
</tr>
{% for product in products %}
<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-2b7s">1</td>
<td class="tg-7zrl"></td>
<td class="tg-2b7s">{{racer.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-2b7s">{{product.price}},00 Kč</td>
<td class="tg-7zrl"></td>
<td class="tg-2b7s">{{product.price}},00 Kč</td>
</tr>
{% endfor %}
<tr>
<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-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>
<td class="tg-7zrl"></td>

View File

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

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