Decouple CachedTicket from CachedFile

This commit is contained in:
Raphael Michel
2016-12-21 18:37:12 +01:00
parent ad60dadee4
commit 77e917345c
7 changed files with 111 additions and 48 deletions

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-21 17:20
from __future__ import unicode_literals
from django.db import migrations, models
import pretix.base.models.orders
def invalidate_ticket_cache(apps, schema_editor):
CachedTicket = apps.get_model('pretixbase', 'CachedTicket')
for ct in CachedTicket.objects.all():
try:
if ct.cachedfile:
ct.cachedfile.delete()
if ct.cachedfile.file:
ct.cachedfile.file.delete(False)
except models.Model.DoesNotExist:
pass
ct.delete()
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0050_orderposition_positionid'),
]
operations = [
migrations.RunPython(
invalidate_ticket_cache, migrations.RunPython.noop
),
migrations.RemoveField(
model_name='cachedticket',
name='cachedfile',
),
migrations.AddField(
model_name='cachedticket',
name='extension',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='cachedticket',
name='file',
field=models.FileField(blank=True, null=True, upload_to=pretix.base.models.orders.cachedticket_name),
),
migrations.AddField(
model_name='cachedticket',
name='type',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
]

View File

@@ -15,7 +15,7 @@ from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ..decimal import round_decimal from ..decimal import round_decimal
from .base import CachedFile, LoggedModel from .base import LoggedModel
from .event import Event from .event import Event
from .items import Item, ItemVariation, Question, QuestionOption, Quota from .items import Item, ItemVariation, Question, QuestionOption, Quota
@@ -565,16 +565,28 @@ class InvoiceAddress(models.Model):
vat_id = models.CharField(max_length=255, blank=True, verbose_name=_('VAT ID')) vat_id = models.CharField(max_length=255, blank=True, verbose_name=_('VAT ID'))
def cachedticket_name(instance, filename: str) -> str:
secret = get_random_string(length=16, allowed_chars=string.ascii_letters + string.digits)
return 'tickets/{org}/{ev}/{code}-{no}-{prov}-{secret}.pdf'.format(
org=instance.order_position.order.event.organizer.slug,
ev=instance.order_position.order.event.slug,
prov=instance.provider,
no=instance.order_position.positionid,
code=instance.order_position.order.code,
secret=secret
)
class CachedTicket(models.Model): class CachedTicket(models.Model):
order_position = models.ForeignKey(OrderPosition, on_delete=models.CASCADE) order_position = models.ForeignKey(OrderPosition, on_delete=models.CASCADE)
cachedfile = models.ForeignKey(CachedFile, on_delete=models.CASCADE, null=True)
provider = models.CharField(max_length=255) provider = models.CharField(max_length=255)
type = models.CharField(max_length=255)
extension = models.CharField(max_length=255)
file = models.FileField(null=True, blank=True, upload_to=cachedticket_name)
@receiver(post_delete, sender=CachedTicket) @receiver(post_delete, sender=CachedTicket)
def cached_file_delete(sender, instance, **kwargs): def cachedticket_delete(sender, instance, **kwargs):
try: if instance.file:
if instance.cachedfile: # Pass false so FileField doesn't save the model.
instance.cachedfile.delete() instance.file.delete(False)
except CachedFile.DoesNotExist:
pass

View File

@@ -1,13 +1,11 @@
from datetime import timedelta import os
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import CachedTicket, Event, Order, OrderPosition
CachedFile, CachedTicket, Event, Order, OrderPosition, cachedfile_name,
)
from pretix.base.services.async import ProfiledTask from pretix.base.services.async import ProfiledTask
from pretix.base.signals import register_ticket_outputs from pretix.base.signals import register_ticket_outputs
from pretix.celery import app from pretix.celery import app
@@ -17,23 +15,21 @@ from pretix.helpers.database import rolledback_transaction
@app.task(base=ProfiledTask) @app.task(base=ProfiledTask)
def generate(order_position: str, provider: str): def generate(order_position: str, provider: str):
order_position = OrderPosition.objects.select_related('order', 'order__event').get(id=order_position) order_position = OrderPosition.objects.select_related('order', 'order__event').get(id=order_position)
ct = CachedTicket.objects.get_or_create(order_position=order_position, provider=provider)[0] try:
if not ct.cachedfile: ct = CachedTicket.objects.get(order_position=order_position, provider=provider)
cf = CachedFile() except CachedTicket.DoesNotExist:
cf.date = now() ct = CachedTicket(order_position=order_position, provider=provider)
cf.expires = order_position.order.event.date_from + timedelta(days=30)
cf.save()
ct.cachedfile = cf
ct.save()
with language(order_position.order.locale): with language(order_position.order.locale):
responses = register_ticket_outputs.send(order_position.order.event) responses = register_ticket_outputs.send(order_position.order.event)
for receiver, response in responses: for receiver, response in responses:
prov = response(order_position.order.event) prov = response(order_position.order.event)
if prov.identifier == provider: if prov.identifier == provider:
ct.cachedfile.filename, ct.cachedfile.type, data = prov.generate(order_position) filename, ct.type, data = prov.generate(order_position)
ct.cachedfile.file.save(cachedfile_name(ct.cachedfile, ct.cachedfile.filename), ContentFile(data)) path, ext = os.path.splitext(filename)
ct.cachedfile.save() ct.extension = ext
ct.save()
ct.file.save(filename, ContentFile(data))
class DummyRollbackException(Exception): class DummyRollbackException(Exception):

View File

@@ -32,7 +32,8 @@ class BaseTicketOutput:
def generate(self, order: OrderPosition) -> Tuple[str, str, str]: def generate(self, order: OrderPosition) -> Tuple[str, str, str]:
""" """
This method should generate the download file and return a tuple consisting of a This method should generate the download file and return a tuple consisting of a
filename, a file type and file content. filename, a file type and file content. The extension will be taken from the filename
which is otherwise ignored.
""" """
raise NotImplementedError() raise NotImplementedError()

View File

@@ -1,5 +1,3 @@
import os
from django.http import FileResponse, Http404, HttpRequest, HttpResponse from django.http import FileResponse, Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property from django.utils.functional import cached_property
@@ -23,8 +21,7 @@ class DownloadView(TemplateView):
return HttpResponse('1' if self.object.file else '0') return HttpResponse('1' if self.object.file else '0')
elif self.object.file: elif self.object.file:
resp = FileResponse(self.object.file.file, content_type=self.object.type) resp = FileResponse(self.object.file.file, content_type=self.object.type)
_, ext = os.path.splitext(self.object.filename) resp['Content-Disposition'] = 'attachment; filename="{}"'.format(self.object.filename)
resp['Content-Disposition'] = 'attachment; filename="{}{}"'.format(self.object.id, ext)
return resp return resp
else: else:
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@@ -159,6 +159,7 @@
{% for line in items.positions %} {% for line in items.positions %}
<div class="row-fluid product-row"> <div class="row-fluid product-row">
<div class="col-md-4 col-xs-6"> <div class="col-md-4 col-xs-6">
{{ line.positionid }}
<strong>{{ line.item.name }}</strong> <strong>{{ line.item.name }}</strong>
{% if line.variation %} {% if line.variation %}
{{ line.variation }} {{ line.variation }}

View File

@@ -1,19 +1,14 @@
from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.db.models import Sum from django.db.models import Sum
from django.http import FileResponse, Http404 from django.http import FileResponse, Http404, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext, ugettext_lazy as _ from django.utils.translation import gettext, ugettext_lazy as _
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from pretix.base.models import ( from pretix.base.models import CachedTicket, Invoice, Order, OrderPosition
CachedFile, CachedTicket, Invoice, Order, OrderPosition,
)
from pretix.base.models.orders import InvoiceAddress from pretix.base.models.orders import InvoiceAddress
from pretix.base.services.invoices import ( from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified, generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
@@ -513,18 +508,25 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
messages.error(request, _('Ticket download is not (yet) enabled.')) messages.error(request, _('Ticket download is not (yet) enabled.'))
return redirect(self.get_order_url()) return redirect(self.get_order_url())
ct = CachedTicket.objects.get_or_create( try:
order_position=self.order_position, provider=self.output.identifier ct = CachedTicket.objects.get(
)[0] order_position=self.order_position, provider=self.output.identifier
if not ct.cachedfile: )
cf = CachedFile() except CachedTicket.DoesNotExist:
cf.date = now() ct = None
cf.expires = self.request.event.date_from + timedelta(days=30)
cf.save() if 'ajax' in request.GET:
ct.cachedfile = cf return HttpResponse('1' if ct and ct.file else '0')
ct.save() elif not ct or not ct.file:
generate.apply_async(args=(self.order_position.id, self.output.identifier)) generate.apply_async(args=(self.order_position.id, self.output.identifier))
return redirect(reverse('cachedfile.download', kwargs={'id': ct.cachedfile.id})) return render(request, "pretixbase/cachedfiles/pending.html", {})
else:
resp = FileResponse(ct.file.file, content_type='application/pdf')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
self.output.identifier, ct.extension
)
return resp
class InvoiceDownload(EventViewMixin, OrderDetailMixin, View): class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):