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 ..decimal import round_decimal
from .base import CachedFile, LoggedModel
from .base import LoggedModel
from .event import Event
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'))
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):
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)
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)
def cached_file_delete(sender, instance, **kwargs):
try:
if instance.cachedfile:
instance.cachedfile.delete()
except CachedFile.DoesNotExist:
pass
def cachedticket_delete(sender, instance, **kwargs):
if instance.file:
# Pass false so FileField doesn't save the model.
instance.file.delete(False)

View File

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

View File

@@ -32,7 +32,8 @@ class BaseTicketOutput:
def generate(self, order: OrderPosition) -> Tuple[str, str, str]:
"""
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()

View File

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

View File

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

View File

@@ -1,19 +1,14 @@
from datetime import timedelta
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Sum
from django.http import FileResponse, Http404
from django.shortcuts import redirect
from django.http import FileResponse, Http404, HttpResponse
from django.shortcuts import redirect, render
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext, ugettext_lazy as _
from django.views.generic import TemplateView, View
from pretix.base.models import (
CachedFile, CachedTicket, Invoice, Order, OrderPosition,
)
from pretix.base.models import CachedTicket, Invoice, Order, OrderPosition
from pretix.base.models.orders import InvoiceAddress
from pretix.base.services.invoices import (
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.'))
return redirect(self.get_order_url())
ct = CachedTicket.objects.get_or_create(
order_position=self.order_position, provider=self.output.identifier
)[0]
if not ct.cachedfile:
cf = CachedFile()
cf.date = now()
cf.expires = self.request.event.date_from + timedelta(days=30)
cf.save()
ct.cachedfile = cf
ct.save()
generate.apply_async(args=(self.order_position.id, self.output.identifier))
return redirect(reverse('cachedfile.download', kwargs={'id': ct.cachedfile.id}))
try:
ct = CachedTicket.objects.get(
order_position=self.order_position, provider=self.output.identifier
)
except CachedTicket.DoesNotExist:
ct = None
if 'ajax' in request.GET:
return HttpResponse('1' if ct and ct.file else '0')
elif not ct or not ct.file:
generate.apply_async(args=(self.order_position.id, self.output.identifier))
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):