From bea270f8eb65d96ea817e07b2df5cbb1eb6f78f0 Mon Sep 17 00:00:00 2001 From: Stephan Telling Date: Thu, 17 Aug 2017 17:51:24 +0200 Subject: [PATCH] add new tickets app --- src/tickets/__init__.py | 0 src/tickets/admin.py | 38 ++++++++ src/tickets/apps.py | 5 + src/tickets/migrations/0001_initial.py | 96 +++++++++++++++++++ src/tickets/migrations/__init__.py | 0 src/tickets/models.py | 128 +++++++++++++++++++++++++ src/tickets/pdf.py | 0 src/tickets/templates/pdf/ticket.html | 32 +++++++ src/tickets/views.py | 3 + 9 files changed, 302 insertions(+) create mode 100644 src/tickets/__init__.py create mode 100644 src/tickets/admin.py create mode 100644 src/tickets/apps.py create mode 100644 src/tickets/migrations/0001_initial.py create mode 100644 src/tickets/migrations/__init__.py create mode 100644 src/tickets/models.py create mode 100644 src/tickets/pdf.py create mode 100644 src/tickets/templates/pdf/ticket.html create mode 100644 src/tickets/views.py diff --git a/src/tickets/__init__.py b/src/tickets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tickets/admin.py b/src/tickets/admin.py new file mode 100644 index 00000000..2939c751 --- /dev/null +++ b/src/tickets/admin.py @@ -0,0 +1,38 @@ +from django.contrib import admin + +from .models import ( + TicketType, + SponsorTicket, + DiscountTicket, + ShopTicket +) + + +class BaseTicketAdmin(admin.ModelAdmin): + actions = ['generate_pdf'] + exclude = ['qrcode_base64'] + + def generate_pdf(self, request, queryset): + for ticket in queryset.all(): + ticket.generate_pdf() + generate_pdf.description = 'Generate PDF for the ticket' + + +@admin.register(TicketType) +class TicketTypeAdmin(admin.ModelAdmin): + pass + + +@admin.register(SponsorTicket) +class SponsorTicketAdmin(BaseTicketAdmin): + pass + + +@admin.register(DiscountTicket) +class DiscountTicketAdmin(BaseTicketAdmin): + pass + + +@admin.register(ShopTicket) +class ShopTicketAdmin(BaseTicketAdmin): + pass diff --git a/src/tickets/apps.py b/src/tickets/apps.py new file mode 100644 index 00000000..3ea742ac --- /dev/null +++ b/src/tickets/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TicketsConfig(AppConfig): + name = 'tickets' diff --git a/src/tickets/migrations/0001_initial.py b/src/tickets/migrations/0001_initial.py new file mode 100644 index 00000000..bd4e36b5 --- /dev/null +++ b/src/tickets/migrations/0001_initial.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-08-17 14:22 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('shop', '0047_auto_20170522_1942'), + ('camps', '0022_camp_colour'), + ('sponsors', '0006_auto_20170715_1110'), + ] + + operations = [ + migrations.CreateModel( + name='BaseTicket', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('qrcode_base64', models.TextField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='TicketType', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('name', models.TextField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DiscountTicket', + fields=[ + ('baseticket_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tickets.BaseTicket')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('price', models.IntegerField(help_text='Price of the discounted ticket (in DKK, including VAT).')), + ], + options={ + 'abstract': False, + }, + bases=('tickets.baseticket', models.Model), + ), + migrations.CreateModel( + name='ShopTicket', + fields=[ + ('baseticket_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tickets.BaseTicket')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('name', models.CharField(blank=True, help_text='Name of the person this ticket belongs to. This can be different from the buying user.', max_length=100, null=True)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('checked_in', models.BooleanField(default=False)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shoptickets', to='shop.Order')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.Product')), + ], + options={ + 'abstract': False, + }, + bases=('tickets.baseticket', models.Model), + ), + migrations.CreateModel( + name='SponsorTicket', + fields=[ + ('baseticket_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='tickets.BaseTicket')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('sponsor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sponsors.Sponsor')), + ], + options={ + 'abstract': False, + }, + bases=('tickets.baseticket', models.Model), + ), + migrations.AddField( + model_name='baseticket', + name='camp', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='camps.Camp'), + ), + migrations.AddField( + model_name='baseticket', + name='ticket_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType'), + ), + ] diff --git a/src/tickets/migrations/__init__.py b/src/tickets/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tickets/models.py b/src/tickets/models.py new file mode 100644 index 00000000..923d9ff7 --- /dev/null +++ b/src/tickets/models.py @@ -0,0 +1,128 @@ +import io +import logging +import hashlib +import base64 +import qrcode + +from django.db import models +from django.conf import settings +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from utils.models import ( + UUIDModel, + CreatedUpdatedModel +) +from shop.pdf import generate_pdf_letter + +logger = logging.getLogger("bornhack.%s" % __name__) + + +# TicketType can be full week, one day. etc. +class TicketType(CreatedUpdatedModel, UUIDModel): + name = models.TextField() + + def __str__(self): + return '{}'.format(self.name) + + +class BaseTicket(models.Model): + qrcode_base64 = models.TextField(null=True, blank=True) + ticket_type = models.ForeignKey('TicketType') + camp = models.ForeignKey('camps.Camp') + + def save(self, **kwargs): + super(BaseTicket, self).save(**kwargs) + self.qrcode_base64 = self.get_qr_code() + super(BaseTicket, self).save(**kwargs) + + def _get_token(self): + return hashlib.sha256( + '{_id}{secret_key}'.format( + _id=self.pk, + secret_key=settings.SECRET_KEY, + ).encode('utf-8') + ).hexdigest() + + def get_qr_code(self): + qr = qrcode.make( + self._get_token(), + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_H + ).resize((250, 250)) + file_like = io.BytesIO() + qr.save(file_like, format='png') + qrcode_base64 = base64.b64encode(file_like.getvalue()) + return qrcode_base64 + + def get_qr_code_url(self): + return 'data:image/png;base64,{}'.format(self.qrcode_base64) + + def generate_pdf(self): + generate_pdf_letter( + filename='ticket_{}.pdf'.format(self.pk), + formatdict={'ticket': self}, + template='pdf/ticket.html' + ) + + +class SponsorTicket(BaseTicket, CreatedUpdatedModel, UUIDModel): + sponsor = models.ForeignKey('sponsors.Sponsor') + + def __str__(self): + return 'SponsorTicket: {}'.format(self.pk) + + +class DiscountTicket(BaseTicket, CreatedUpdatedModel, UUIDModel): + price = models.IntegerField( + help_text=_('Price of the discounted ticket (in DKK, including VAT).') + ) + + def __str__(self): + return 'DiscountTicket: {}'.format(self.pk) + + +class ShopTicket(BaseTicket, CreatedUpdatedModel, UUIDModel): + order = models.ForeignKey('shop.Order', related_name='shoptickets') + product = models.ForeignKey('shop.Product') + + name = models.CharField( + max_length=100, + help_text=( + 'Name of the person this ticket belongs to. ' + 'This can be different from the buying user.' + ), + null=True, + blank=True, + ) + + email = models.EmailField( + null=True, + blank=True, + ) + + checked_in = models.BooleanField(default=False) + + # overwrite the _get_token method because old tickets use the user_id + def _get_token(self): + return hashlib.sha256( + '{_id}{user_id}{secret_key}'.format( + _id=self.pk, + user_id=self.order.user.pk, + secret_key=settings.SECRET_KEY, + ).encode('utf-8') + ).hexdigest() + + def __str__(self): + return 'Ticket {user} {product}'.format( + user=self.order.user, + product=self.product + ) + + def save(self, **kwargs): + super(ShopTicket, self).save(**kwargs) + self.qrcode_base64 = self.get_qr_code() + super(ShopTicket, self).save(**kwargs) + + def get_absolute_url(self): + return str(reverse_lazy('shop:ticket_detail', kwargs={'pk': self.pk})) diff --git a/src/tickets/pdf.py b/src/tickets/pdf.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tickets/templates/pdf/ticket.html b/src/tickets/templates/pdf/ticket.html new file mode 100644 index 00000000..556c7d4a --- /dev/null +++ b/src/tickets/templates/pdf/ticket.html @@ -0,0 +1,32 @@ +{% load static from staticfiles %} + + + + + + + +
  +

+ {{ ticket.created|date:"b jS, Y" }}
+

+
+
+

{{ ticket.camp.title }} Ticket

+

Type: {{ ticket.ticket_type }}

+ +{% if ticket.name %} +

Participant: {{ ticket.name }}

+
+{% elif ticket.order.user.email %} +

Participant: {{ ticket.order.user.email }}

+
+{% elif ticket.sponsor %} +

Sponsor: {{ ticket.sponsor.name }}

+ +{% endif %} + +
+ +

Ticket #{{ ticket.pk }}

+
diff --git a/src/tickets/views.py b/src/tickets/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/src/tickets/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.