cleanup; ticket cache invalidation

This commit is contained in:
Kara Engelhardt committed 2026-11-09 12:57:55 +01:00
1 parent 664ecacc4b
commit f54b41bc24
23 files changed
+370 -139

No files matched your search

+9 -5
View File
@@ -37,9 +37,9 @@ from pretix.base.models import (
CachedCombinedTicket, CachedFile, CachedTicket, Event, InvoiceAddress,
Order, OrderPosition,
)
from pretix.base.services.tasks import EventTask, ProfiledTask
from pretix.base.services.tasks import ProfiledTask, TransactionAwareProfiledEventTask
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import register_ticket_outputs
from pretix.base.signals import register_ticket_outputs, invalidate_ticket_cache
from pretix.celery_app import app
from pretix.helpers.database import rolledback_transaction
@@ -250,8 +250,8 @@ def get_tickets_for_order(order, base_position=None):
return tickets
@app.task(base=EventTask, acks_late=True)
def invalidate_cache(event: Event, item: int=None, provider: str=None, order: int=None, **kwargs):
@app.task(base=TransactionAwareProfiledEventTask, acks_late=True)
def invalidate_cache(event: Event, item: int | None=None, provider: str | None=None, order: int | None=None, **kwargs):
qs = CachedTicket.objects.filter(order_position__order__event=event)
qsc = CachedCombinedTicket.objects.filter(order__event=event)
@@ -271,6 +271,9 @@ def invalidate_cache(event: Event, item: int=None, provider: str=None, order: in
for ct in qsc:
ct.delete()
invalidate_ticket_cache.send(event, item=item, provider=provider, order=order)
def get_download_buttons(event, show_disabled):
buttons = []
@@ -293,6 +296,7 @@ def get_download_buttons(event, show_disabled):
})
return buttons
def get_wallet_buttons(event):
# TODO(wallet): implement this
from pretix.plugins.wallet.platforms import AVAILABLE_PLATFORMS
@@ -301,4 +305,4 @@ def get_wallet_buttons(event):
platform = p(event)
attr, cont = platform.render_download_button()
buttons.append({"identifier": platform.identifier, "attributes": attr, "content": cont})
return buttons
return buttons
+9
View File
@@ -1218,3 +1218,12 @@ This signal is sent out each time the information for a Device is modified.
Both the original and updated versions of the Device are included to allow
receivers to see what has been updated.
"""
invalidate_ticket_cache = EventPluginSignal()
"""
Arguments: ``item: int | None``, ``provider: str | None``, ``order: int | None``
This signal is sent out when the ticket cache is invalidated.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
+6 -28
View File
@@ -54,7 +54,6 @@ from django.db.models import (
Count, Exists, F, IntegerField, Max, OuterRef, Prefetch, ProtectedError, Q,
QuerySet, Subquery, Sum,
)
from uuid import UUID
from django.forms import formset_factory
from django.http import (
FileResponse, Http404, HttpResponseNotAllowed, HttpResponseRedirect,
@@ -661,18 +660,7 @@ class OrderDownload(AsyncAction, OrderView):
permission = 'event.orders:read'
def get_success_url(self, value):
if isinstance(value, UUID):
cf = CachedFile.objects.filter(pk=value).first()
else:
cf = None
if not cf or not cf.allowed_for_session(self.request):
return self.get_self_url()
if cf.type == 'text/uri-list':
return cf.file.file.read().decode()
else:
return reverse('cachedfile.download', kwargs={"id":str(value)})
return self.get_self_url()
def get_error_url(self):
return self.get_order_url()
@@ -711,19 +699,12 @@ class OrderDownload(AsyncAction, OrderView):
if 'position' in kwargs and not self.order_position.generate_ticket:
return self.error(_('Ticket download is not enabled for this product.'))
if self.output.is_cacheable and (ct := self.get_last_ct()):
if ct := self.get_last_ct():
return self.success(ct)
if not self.output.is_cacheable:
cf = CachedFile(web_download=True, date=now(), expires=now() + timedelta(hours=2))
cf.bind_to_session(request)
cf.save()
else:
cf = None
return self.do('orderposition' if 'position' in kwargs else 'order',
self.order_position.pk if 'position' in kwargs else self.order.pk,
self.output.identifier, cf.pk if cf else None)
self.output.identifier)
def get_success_message(self, value):
return ""
@@ -753,12 +734,9 @@ class OrderDownload(AsyncAction, OrderView):
return redirect(self.get_success_url(value))
def get_last_ct(self):
if self.output.is_cacheable:
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
else:
ct = None
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
if not ct or not ct.file:
return None
return ct
@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0312_alter_customer_locale_alter_devicelastseen_device_and_more",
"0311_fix_unshredded_invoices",
),
("wallet", "0003_walletlayoutfilesetting"),
]
@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0312_alter_customer_locale_alter_devicelastseen_device_and_more",
"0311_fix_unshredded_invoices",
),
("wallet", "0005_rename_type_googlewalletinstance_resource_and_more"),
]
@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0312_alter_customer_locale_alter_devicelastseen_device_and_more",
"0311_fix_unshredded_invoices",
),
("wallet", "0009_googlewalletinstance_usages_and_more"),
]
@@ -0,0 +1,48 @@
# Generated by Django 5.2.17 on 2026-11-02 14:57
import django.db.models.deletion
import pretix.plugins.wallet.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0311_fix_unshredded_invoices",
),
("wallet", "0013_applewalletpass_manifest_and_more"),
]
operations = [
migrations.CreateModel(
name="CombinedAppleWalletPass",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("provider", models.CharField(max_length=255)),
("type", models.CharField(max_length=255)),
("extension", models.CharField(max_length=255)),
(
"file",
models.FileField(
max_length=255,
null=True,
),
),
("created", models.DateTimeField(auto_now_add=True)),
(
"order",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to="pretixbase.order",
),
),
],
),
]
@@ -0,0 +1,25 @@
# Generated by Django 5.2.17 on 2026-11-02 15:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("wallet", "0014_combinedapplewalletpass"),
]
operations = [
migrations.RemoveField(
model_name="combinedapplewalletpass",
name="extension",
),
migrations.RemoveField(
model_name="combinedapplewalletpass",
name="provider",
),
migrations.RemoveField(
model_name="combinedapplewalletpass",
name="type",
),
]
@@ -0,0 +1,27 @@
# Generated by Django 5.2.17 on 2026-11-02 16:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"pretixbase",
"0311_fix_unshredded_invoices",
),
("wallet", "0015_remove_combinedapplewalletpass_extension_and_more"),
]
operations = [
migrations.AddField(
model_name="googlewalletinstance",
name="order_positon",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="pretixbase.orderposition",
),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.17 on 2026-11-02 16:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("wallet", "0016_googlewalletinstance_order_positon"),
]
operations = [
migrations.RenameField(
model_name="googlewalletinstance",
old_name="order_positon",
new_name="order_position",
),
]
+9 -3
View File
@@ -19,17 +19,16 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import tempfile
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q, constraints
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from django_scopes import ScopedManager
from pretix.base.models import LoggedModel, OrderPosition
from pretix.base.models.event import Event
from pretix.base.models.orders import Order
class WalletLayout(LoggedModel):
@@ -142,8 +141,15 @@ class AppleWalletPass(models.Model):
]
class CombinedAppleWalletPass(models.Model):
order = models.OneToOneField(Order, on_delete=models.CASCADE)
file = models.FileField(null=True, blank=True, max_length=255)
created = models.DateTimeField(auto_now_add=True)
class GoogleWalletInstance(models.Model):
event = models.ForeignKey("pretixbase.Event", on_delete=models.CASCADE)
order_position = models.ForeignKey(OrderPosition, on_delete=models.CASCADE, null=True)
resource = models.CharField()
identifier = models.CharField()
data = models.JSONField()
@@ -2,6 +2,7 @@ import hashlib
import io
import json
import logging
import tempfile
import zipfile
import cryptography
@@ -11,13 +12,17 @@ import cryptography.x509
from django.utils.encoding import force_bytes
from django.utils.translation import gettext as _
from pretix.base.models.orders import Order
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.plugins.wallet.i18n import (
MaybeTranslatedString,
)
from ..base import (
WalletPlatform,
)
from ...models import CombinedAppleWalletPass
from django.core.files.base import ContentFile
from django import forms
from django.utils.translation import gettext as _
@@ -94,26 +99,44 @@ class ApplePlatform(WalletPlatform):
]
)
# TODO: generate_ops
# def generate_order(self, order: Order):
# with tempfile.TemporaryFile() as d:
# with zipfile.ZipFile(d, "w") as zipf:
# for pos in order.positions_with_tickets:
# fname, __, content = self.generate(pos)
# zipf.writestr(
# "{}-{}{}".format(
# order.code, pos.positionid, os.path.splitext(fname)[1]
# ),
# content,
# )
def generate(self, position, layout=None) -> str:
if not layout:
layout = self.get_layout(position).pass_layout
pass_obj = layout.generate(position)
# d.seek(0)
# return (
# "{}-{}.pkpasses".format(order.code, self.identifier),
# "application/vnd.apple.pkpasses",
# d.read(),
# )
return eventreverse_absolute(
self.event,
"plugins:wallet:apple_download",
kwargs={"order": position.order.code, "secret": position.order.secret, "position": position.pk},
)
def generate_order(self, order: Order):
with tempfile.TemporaryFile() as d:
with zipfile.ZipFile(d, "w") as zipf:
for pos in order.positions_with_tickets:
pass_obj = self.get_layout(pos).pass_layout.generate(pos)
zipf.writestr(
"{}-{}.pkpass".format(order.code, pos.positionid),
pass_obj.content.read(),
)
d.seek(0)
data = d.read()
if hasattr(order, "combinedapplewalletpass"):
order.combinedapplewalletpass.delete()
ct = CombinedAppleWalletPass.objects.create(
order=order,
file=None,
)
ct.file.save("{}-{}.pkpasses".format(order.code, self.identifier), ContentFile(data))
return eventreverse_absolute(
self.event,
"plugins:wallet:apple_download_combined",
kwargs={"order": order.code, "secret": order.secret},
)
class StringResource:
entries: dict[str, MaybeTranslatedString]
@@ -5,6 +5,7 @@ from typing import Any
from django.core.files.base import ContentFile
from django.utils.translation import gettext as _
from django.db import transaction
from django.urls import reverse
from pretix.base.models import OrderPosition
from pretix.helpers import OF_SELF
@@ -238,7 +239,7 @@ class AppleWalletStyle(PassStyle):
pass_obj.manifest = manifest
pass_obj.save()
return filename, "application/vnd.apple.pkpass", result
return pass_obj
class AppleWalletEventTicket(AppleWalletStyle):
+4 -3
View File
@@ -10,6 +10,7 @@ from i18nfield.strings import LazyI18nString
from pretix.api.helpers import handle_file_upload
from pretix.base.models import OrderPosition
from pretix.base.models.orders import Order
from pretix.plugins.wallet.models import WalletPlatformLayout
from ..i18n import MaybeTranslatedString, localizable_to_dict
@@ -66,10 +67,10 @@ class WalletPlatform:
layout = self.get_layout(position).pass_layout
return layout.generate(position)
def generate_ops(self, ops: Iterable[OrderPosition]) -> str:
def generate_order(self, order: Order) -> str:
"""
This method should generate the wallet passes for all positions in `ops` and return a
url the user should be sent to to retrieve them (e.g. via download or by redirecting
This method should generate the wallet passes for all positions in `order` and return
a url the user should be sent to to retrieve them (e.g. via download or by redirecting
to the wallet provider)
.. note:: You should cache the generated files where possible and only regenerate
@@ -14,6 +14,7 @@ from google.auth import crypt, jwt
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
from pretix.base.models.orders import OrderPosition
from pretix.plugins.wallet.i18n import (
MaybeTranslatedString, localizable_to_dict,
)
@@ -156,6 +157,7 @@ class BaseGoogleWalletObject:
locales: set[str]
default_locale: str
order_position = None
def _localized_string(self, string: MaybeTranslatedString):
return localized_string(string, self.locales, self.default_locale)
@@ -235,7 +237,7 @@ class EventTicketClass(BaseGoogleWalletObject):
}
def security_animation(self, security_animation: AnimationType):
self.data["securityAnimation"] = security_animation
self.data["securityAnimation"] = {"animationType": security_animation}
def doors_open(
self,
@@ -300,10 +302,12 @@ class EventTicketObject(BaseGoogleWalletObject):
state: State,
locales: set[str],
default_locale: str,
order_position: OrderPosition
):
self.id = object_id
self.locales = locales
self.default_locale = default_locale
self.order_position = order_position
self.data = {
"id": object_id,
@@ -10,6 +10,7 @@ from django.utils.translation import gettext as _
from django import forms
from pretix.base.models import Event, OrderPosition
from pretix.base.models.orders import Order
from pretix.base.settings import GlobalSettingsObject
from pretix.plugins.wallet.models import GoogleWalletInstance
from ..base import (
@@ -165,7 +166,7 @@ class GooglePlatform(WalletPlatform):
event=self.event,
resource=object.resource,
identifier=object.id,
defaults={"data": object.data},
defaults={"data": object.data, "order_position": object.order_position},
)
if created or instance.data != object.data:
result = self.comms.put_item(object.resource, object.id, object.data)
@@ -207,10 +208,10 @@ class GooglePlatform(WalletPlatform):
return self._get_jwt([ticket_object])
def generate_ops(self, ops: Iterable[OrderPosition]):
def generate_order(self, order: Order):
class_objects = []
ticket_objects = []
for position in ops:
for position in order.positions_with_tickets:
class_object, ticket_object = self.get_layout(
position
).pass_layout.generate(position)
@@ -200,6 +200,7 @@ class GoogleWalletEventTicketStyle(PassStyle):
State.ACTIVE,
self.event.settings.locales,
self.event.settings.locale,
order_position=op
)
fields = self.get_pass_fields(op)
+75 -6
View File
@@ -22,11 +22,11 @@
from django.dispatch import receiver
from pretix.base.models.event import Event
from pretix.base.signals import (
register_global_settings, register_ticket_outputs,
order_canceled,
order_changed,
order_paid,
register_global_settings,
register_ticket_outputs,
invalidate_ticket_cache,
periodic_task,
)
from pretix.control.signals import item_forms
@@ -35,11 +35,19 @@ from .forms import WalletLayoutItemForm
from .models import WalletLayoutItem
from .ticketoutput import WalletSettingsHolder
from .platforms import AVAILABLE_PLATFORMS
from .tasks import send_update_apns
from .models import AppleWalletPass, CombinedAppleWalletPass
from .tasks import get_pass
from .models import GoogleWalletInstance
from .platforms.google.google_wallet import Resource
@receiver(register_ticket_outputs, dispatch_uid="output_wallet")
def register_output(sender, **kwargs):
return WalletSettingsHolder
for platform in AVAILABLE_PLATFORMS:
if hasattr(platform, "get_global_settings"):
register_global_settings.connect(
@@ -66,7 +74,68 @@ def control_item_forms(sender, request, item, **kwargs):
return forms
@receiver(periodic_task, dispatch_uid="wallet_periodic")
def periodic_tasks(sender, **kwargs):
from .tasks import send_update_apns
send_update_apns.apply_async()
# TODO: move to platform?
send_update_apns.apply_async()
# TODO: move to platform?
@receiver(invalidate_ticket_cache, dispatch_uid="invalidate_ticket_cache_wallet_apple")
def invalidate_apple(
sender: Event, item: int | None, provider: str | None, order: int | None, **kwargs
):
if provider and provider != "wallet":
return
pass_qs = AppleWalletPass.objects.filter(order_position__order__event=sender)
passes_qs = CombinedAppleWalletPass.objects.filter(order__event=sender)
if item:
pass_qs = pass_qs.filter(order_position__item_id=item)
passes_qs = passes_qs.filter(order__order_position__item_id=item)
if order:
pass_qs = pass_qs.filter(order_position__order_id=order)
passes_qs = passes_qs.filter(order_id=order)
for pass_obj in pass_qs:
get_pass.apply_async(
kwargs={
"model": "orderposition",
"pk": pass_obj.order_position_id,
"platform_identifier": "apple",
}
)
for passes_obj in passes_qs:
passes_obj.delete()
@receiver(invalidate_ticket_cache, dispatch_uid="invalidate_ticket_cache_wallet_google")
def invalidate_google(
sender: Event, item: int | None, provider: str | None, order: int | None, **kwargs
):
if provider and provider != "wallet":
return
inst_qs = GoogleWalletInstance.objects.filter(event=sender, resource=Resource.EVENT_TICKET_OBJECT).exclude(usages=0, order_position__isnull=True)
if item:
inst_qs = inst_qs.filter(order_position__item_id=item)
if order:
inst_qs = inst_qs.filter(order_position__order_id=order)
for inst in inst_qs:
if inst.order_position_id:
get_pass.apply_async(
kwargs={
"model": "orderposition",
"pk": inst.order_position_id,
"platform_identifier": "google",
}
)
+18 -7
View File
@@ -12,6 +12,7 @@ from pretix.plugins.wallet.platforms.base import WalletPlatform
from .models import AppleWalletPass, AppleWalletDevice
from django_scopes import scopes_disabled
@app.task
@scopes_disabled()
def send_update_apns():
@@ -58,16 +59,20 @@ def send_update_apns():
certfile.write(ca_cert)
certfile.flush()
ctx.load_cert_chain(
certfile=certfile.name, password=event.settings.wallet_apple_key_password
certfile=certfile.name,
password=event.settings.wallet_apple_key_password,
)
client = httpx.Client(verify=ctx, http1=False, http2=True)
for device in devices_to_push:
device.last_push = now()
client.post(f"https://api.push.apple.com/3/device/{device.push_token}", json={})
client.post(
f"https://api.push.apple.com/3/device/{device.push_token}", json={}
)
device.save()
def get_platform(event, platform) -> WalletPlatform | None:
for p in AVAILABLE_PLATFORMS:
if p.identifier == platform:
@@ -76,11 +81,17 @@ def get_platform(event, platform) -> WalletPlatform | None:
@app.task(base=ProfiledTask)
def get_pass(model: str, pk: int, platform_identifier: str):
if model == 'order':
order = Order.objects.select_related('event').get(id=pk)
if model == "order":
order = Order.objects.select_related("event").get(id=pk)
platform = get_platform(order.event, platform_identifier)
return platform.generate_ops(order.positions_with_tickets)
elif model == 'orderposition':
position = OrderPosition.objects.select_related('order', 'order__event').get(id=pk)
if not platform:
return
return platform.generate_order(order)
elif model == "orderposition":
position = OrderPosition.objects.select_related("order", "order__event").get(
id=pk
)
platform = get_platform(position.order.event, platform_identifier)
if not platform:
return
return platform.generate(position)
+12 -1
View File
@@ -31,6 +31,8 @@ from .views.apple import (
DeviceRegistrationView,
GetPassView,
LogView,
DownloadPassView,
DownloadCombinedPassView
)
from .views.control import (
LayoutCreateView,
@@ -108,12 +110,21 @@ event_patterns = [
CallbackView.as_view(),
name="google_webhook",
),
# TODO(wallet): actually provide these views
re_path(
r"^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<position>[0-9]+)/(?P<platform>[^/]+)$",
PresaleWalletGetPass.as_view(),
name="presale_event.order.wallet.get",
),
re_path(
r"^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/wallet/apple/download/(?P<position>[0-9]+)/$",
DownloadPassView.as_view(),
name="apple_download",
),
re_path(
r"^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/wallet/apple/download/$",
DownloadCombinedPassView.as_view(),
name="apple_download_combined",
),
re_path(
r"^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<platform>[^/]+)$",
PresaleWalletGetPass.as_view(),
+35 -2
View File
@@ -1,10 +1,14 @@
import datetime
from functools import cached_property
import logging
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import AnonymousUser
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.views.decorators.http import condition
from django.http.response import Http404
from django.views import View
from rest_framework import exceptions, permissions, serializers
from rest_framework.authentication import (
BaseAuthentication, get_authorization_header,
@@ -12,8 +16,11 @@ from rest_framework.authentication import (
from rest_framework.response import Response
from rest_framework.views import APIView
from pretix.base.models.orders import OrderPosition
from pretix.helpers.http import ChunkBasedFileResponse
from pretix.plugins.wallet.models import AppleWalletDevice, AppleWalletPass
from pretix.plugins.wallet.models import AppleWalletDevice, AppleWalletPass, CombinedAppleWalletPass
from pretix.presale.views import EventViewMixin
from pretix.presale.views.order import OrderDetailMixin
logger = logging.getLogger("pretix.plugins.wallet.apple")
@@ -166,5 +173,31 @@ class LogView(APIView):
def post(self, request, *args, **kwargs):
serializer = LogEntrySerializer(data=request.data)
serializer.is_valid(raise_exception=True)
logger.debug("Received apple wallet log messages: %s", serializer.data["logs"])
logger.info("Received apple wallet log messages: %s", serializer.data["logs"])
return Response({})
class DownloadPassView(EventViewMixin, OrderDetailMixin, View):
@cached_property
def order_position(self):
try:
return self.order.positions.get(pk=self.kwargs.get('position'))
except OrderPosition.DoesNotExist:
return None
def get(self, request, *args, **kwargs):
if not self.order_position:
return Http404()
pass_obj = get_object_or_404(AppleWalletPass, order_position=self.order_position)
resp = ChunkBasedFileResponse(pass_obj.content.file, content_type="application/vnd.apple.pkpass")
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(pass_obj.serial_number + ".pkpass").encode('ascii', 'ignore')
return resp
class DownloadCombinedPassView(EventViewMixin, OrderDetailMixin, View):
def get(self, request, *args, **kwargs):
pass_obj = get_object_or_404(CombinedAppleWalletPass, order=self.order)
resp = ChunkBasedFileResponse(pass_obj.file.file, content_type="application/vnd.apple.pkpasses")
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(self.order.code + ".pkpasses").encode('ascii', 'ignore')
return resp
+9 -24
View File
@@ -1,41 +1,25 @@
from django.http import Http404
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
View,
)
from pretix.base.models.orders import OrderPosition
from pretix.base.services.orders import OrderError
from pretix.base.views.tasks import AsyncAction
from django.db.models import Q
from django.http import (
Http404,
)
from django.http import Http404
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import View
from pretix.base.models import (
OrderPosition,
)
from pretix.base.services.orders import (
OrderError,
)
from pretix.base.models import OrderPosition
from pretix.base.services.orders import OrderError
from pretix.base.views.tasks import AsyncAction
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.views import (
EventViewMixin,
from pretix.presale.views import EventViewMixin
from pretix.presale.views.order import (
OrderDetailMixin, OrderPositionDetailMixin,
)
from pretix.presale.views.order import OrderDetailMixin, OrderPositionDetailMixin
from .control import OrderDownloadMixin
class PresaleOrderDownloadMixin(OrderDownloadMixin):
known_errortypes = ['OrderError']
@@ -86,6 +70,7 @@ class PresaleWalletGetPass(PresaleOrderDownloadMixin, EventViewMixin, OrderDetai
except OrderPosition.DoesNotExist:
return None
@method_decorator(xframe_options_exempt, 'dispatch')
class PresaleWalletGetPassPosition(PresaleOrderDownloadMixin, EventViewMixin, OrderPositionDetailMixin, AsyncAction, View):
def get_error_url(self):
@@ -105,4 +90,4 @@ class PresaleWalletGetPassPosition(PresaleOrderDownloadMixin, EventViewMixin, Or
)
)
except OrderPosition.DoesNotExist:
return None
return None
+10 -34
View File
@@ -43,8 +43,6 @@ import re
from collections import Counter, OrderedDict, defaultdict
from decimal import Decimal
from urllib.parse import quote, urlencode
from datetime import timedelta
from uuid import UUID
from django import forms
from django.conf import settings
@@ -64,14 +62,12 @@ from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView, TemplateView, View
from pretixeu.views import reverse
from pretix.base.auth import has_event_access_permission
from pretix.base.models import (
CachedTicket, Checkin, GiftCard, Invoice, Order, OrderPosition, Quota,
TaxRule,
)
from pretix.base.models.base import CachedFile
from pretix.base.models.orders import (
CachedCombinedTicket, InvoiceAddress, OrderFee, OrderPayment, OrderRefund,
QuestionAnswer,
@@ -1208,32 +1204,15 @@ class OrderDownloadMixin:
):
return self.error(OrderError(_('Please click the link we sent you via email to download your tickets.')))
if self.output.is_cacheable and (ct := self.get_last_ct()):
if ct := self.get_last_ct():
return self.success(ct)
if not self.output.is_cacheable:
cf = CachedFile(web_download=True, date=now(), expires=now() + timedelta(hours=2))
cf.save()
else:
cf = None
return self.do('orderposition' if 'position' in kwargs else 'order',
self.order_position.pk if 'position' in kwargs else self.order.pk,
self.output.identifier, cf.pk if cf else None)
self.output.identifier)
def get_success_url(self, value):
if isinstance(value, UUID):
cf = CachedFile.objects.filter(pk=value).first()
else:
cf = None
if not cf or not cf.allowed_for_session(self.request):
return self.get_self_url()
if cf.type == 'text/uri-list':
return cf.file.file.read().decode()
else:
return reverse('cachedfile.download', kwargs={"id":str(value)})
return self.get_self_url()
def get_success_message(self, value):
return ""
@@ -1275,17 +1254,14 @@ class OrderDownloadMixin:
return redirect(self.get_success_url(value))
def get_last_ct(self):
if self.output.is_cacheable:
if 'position' in self.kwargs:
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
else:
ct = CachedCombinedTicket.objects.filter(
order=self.order, provider=self.output.identifier, file__isnull=False
).last()
if 'position' in self.kwargs:
ct = CachedTicket.objects.filter(
order_position=self.order_position, provider=self.output.identifier, file__isnull=False
).last()
else:
ct = None
ct = CachedCombinedTicket.objects.filter(
order=self.order, provider=self.output.identifier, file__isnull=False
).last()
if not ct or not ct.file:
return None
return ct