From 802a7b3a2447c2cda9010df3b35be21aec766296 Mon Sep 17 00:00:00 2001 From: Kara Engelhardt Date: Wed, 15 Jul 2026 12:18:52 +0200 Subject: [PATCH] WIP --- .../components/preview/fieldgroup-preview.vue | 36 ++++ .../components/preview/pass-preview.vue | 38 ++++ .../placeholder-fieldgroup-preview.vue | 78 +++++++ .../wallet/components/preview/row-preview.vue | 14 ++ .../static/pretixplugins/wallet/helpers.ts | 32 +++ src/pretix/plugins/wallet/styles/google.py | 204 +++++++++++++++++- src/pretix/plugins/wallet/ticketoutput.py | 48 ++++- 7 files changed, 446 insertions(+), 4 deletions(-) create mode 100644 src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/fieldgroup-preview.vue create mode 100644 src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/pass-preview.vue create mode 100644 src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/placeholder-fieldgroup-preview.vue create mode 100644 src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/row-preview.vue create mode 100644 src/pretix/plugins/wallet/static/pretixplugins/wallet/helpers.ts diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/fieldgroup-preview.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/fieldgroup-preview.vue new file mode 100644 index 0000000000..effcfb96c1 --- /dev/null +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/fieldgroup-preview.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/pass-preview.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/pass-preview.vue new file mode 100644 index 0000000000..dfdf7258ea --- /dev/null +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/pass-preview.vue @@ -0,0 +1,38 @@ + + + + diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/placeholder-fieldgroup-preview.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/placeholder-fieldgroup-preview.vue new file mode 100644 index 0000000000..ef020737f2 --- /dev/null +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/placeholder-fieldgroup-preview.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/row-preview.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/row-preview.vue new file mode 100644 index 0000000000..ccb1d3a1f1 --- /dev/null +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/row-preview.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/helpers.ts b/src/pretix/plugins/wallet/static/pretixplugins/wallet/helpers.ts new file mode 100644 index 0000000000..dbd7f66691 --- /dev/null +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/helpers.ts @@ -0,0 +1,32 @@ +export function i18nstringLocalize(s: I18nString): string { + if (typeof s === 'string') { + return s + } + if (s === null) { + return null + } + + var locale = document.body.attributes['data-pretixlocale'].value + var short_locale = locale.split('-')[0] + if (locale in s) + return s[locale] + + if (short_locale in s) + return s[short_locale] + + for (const k of Object.keys(s)) { + if (k.split('-')[0] === short_locale && s[k]) { + return s[k] + } + } + + if (s['en']) + return s['en'] + + for (const k of Object.keys(s)) { + if (s[k]) { + return s[k] + } + } +} + diff --git a/src/pretix/plugins/wallet/styles/google.py b/src/pretix/plugins/wallet/styles/google.py index 76d7e076f9..8d31226df5 100644 --- a/src/pretix/plugins/wallet/styles/google.py +++ b/src/pretix/plugins/wallet/styles/google.py @@ -1,10 +1,210 @@ -from .base import FieldGroupDisplay, PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform -from django.utils.translation import gettext_lazy as _ +from pretix.base.models import Event, OrderPosition + +from .base import FieldGroupDisplay, PassLayout, PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform +from django.utils.translation import gettext_lazy as _, gettext +import json + +from walletobjects import ButtonJWT, EventTicketClass, EventTicketObject +from walletobjects.comms import Comms +from walletobjects.constants import ( + Barcode, ClassType, ConfirmationCode, DoorsOpen, + MultipleDevicesAndHoldersAllowedStatus, ObjectState, ObjectType, + ReviewStatus, Seat, +) +from pretix.base.settings import GlobalSettingsObject +import uuid +from pretix.multidomain.urlreverse import eventreverse_absolute +from django.utils import translation + +def _get_instance_uuid(): + gs = GlobalSettingsObject() + if not gs.settings.wallet_google_instance_uuid: + gs.settings.wallet_google_instance_uuid = str(uuid.uuid4()) + return gs.settings.get("wallet_google_instance_uuid") + +def get_class_id(event: Event): + instance_uuid = _get_instance_uuid() + issuer_id = event.settings.get('wallet_google_issuer_id') + return "%s.pretix-%s-%s-%s" % (issuer_id, instance_uuid, event.organizer.slug, event.slug) + + +def get_object_id(op: OrderPosition): + instance_uuid = _get_instance_uuid() + issuer_id = op.order.event.settings.get('wallet_google_issuer_id') + + return "%s.pretix-%s-%s-%s-%s-%s" % (issuer_id, instance_uuid, + op.order.event.organizer.slug, op.order.event.slug, + op.order.code, op.positionid) + +def get_translated_dict(string, locales): + translated = {} + + for locale in locales: + translation.activate(locale) + translated[locale] = gettext(string) + translation.deactivate() + + return translated + + +def get_translated_string(string, locale): + translation.activate(locale) + translated = gettext(string) + translation.deactivate() + + return translated class GooglePlatform(WalletPlatform): identifier = "google" name = _("Google") + @classmethod + def generate(cls, layout: PassLayout, op: OrderPosition): + class_object = cls._generate_class(op.order.event) + + # ticket_object = cls._get_object(op) + + # if not ticket_object: + # return False + + # generated_jwt = self._comms().sign_jwt( + # ButtonJWT( + # origins=[django_settings.SITE_URL], + # issuer=self._comms().client_email, + # event_ticket_objects=[ticket_object], + # skinny=True + # ) + # ) + + # if generated_jwt: + # return 'googlepaypass', 'text/uri-list', 'https://pay.google.com/gp/v/save/%s' % generated_jwt + # else: + # return False + + data = {"class": class_object} + return 'pass.json', 'application/json', json.dumps(data) + # from ..views import get_layout_variables + + # order = op.order + # event = order.event + # filename = "{}-{}.pkpass".format(order.event.slug, order.code) + + # ticket = str(op.item.name) + # if op.variation: + # ticket += " - " + str(op.variation) + + # serialNumber = "%s-%s-%s-%d" % ( + # order.event.organizer.slug, + # order.event.slug, + # order.code, + # op.pk, + # ) + + # context = { + # "placeholders": get_layout_variables(op.order.event), + # "evaluation_context": [op, order, order.event], + # "ca_certificate": order.event.settings.wallet_apple_ca_certificate.read(), + # "certificate": order.event.settings.wallet_apple_certificate.read(), + # "key": order.event.settings.wallet_apple_key.read(), + # "password": order.event.settings.wallet_apple_key_password, + # "description": _("Ticket for {event} ({product})").format( # TODO: i18n + # event=event.name, product=ticket + # ), + # "organizationName": event.organizer.name, + # "passTypeIdentifier": order.event.settings.wallet_apple_pass_type_id, + # "teamIdentifier": order.event.settings.wallet_apple_team_id, + # "serialNumber": serialNumber, + # "locales": event.settings.locales, + # } + + # data = layout.generate(context) + # return filename, "application/vnd.apple.pkpass", data + + @classmethod + def _generate_class(cls, event: Event): + class_name = get_class_id(event) + + output_class = EventTicketClass( + event.organizer.name, + class_name, + MultipleDevicesAndHoldersAllowedStatus.multipleHolders, # TODO: Make configurable + event.name, + ReviewStatus.draft, # TODO: ReviewStatus.underReview, + event.settings.locale + ) + + output_class.homepage_uri( + eventreverse_absolute(event, 'presale:event.index'), + get_translated_string('Website', event.settings.locale), + get_translated_dict('Website', event.settings.locales) + ) + + # output_class.callback_url(eventreverse_absolute(event.organizer, 'plugins:pretix_googlepaypasses:webhook')) + + # if (event.settings.get('ticketoutput_googlepaypasses_latitude') + # and event.settings.get('ticketoutput_googlepaypasses_longitude')): + # output_class.locations( + # event.settings.get('ticketoutput_googlepaypasses_latitude'), + # event.settings.get('ticketoutput_googlepaypasses_longitude') + # ) + # elif event.geo_lat and event.geo_lon: + # output_class.locations( + # event.geo_lat, + # event.geo_lon + # ) + + output_class.country_code(event.settings.locale) + + output_class.hide_barcode(False) + + # if event.settings.get('ticketoutput_googlepaypasses_hero'): + # output_class.hero_image( + # urljoin(django_settings.SITE_URL, event.settings.get('ticketoutput_googlepaypasses_hero').url), + # str(event.name), + # event.name, + # ) + + # output_class.hex_background_color(event.settings.get('primary_color')) + # output_class.event_id('pretix-%s-%s-%s' % (gs.settings.get('update_check_id'), event.organizer.id, event.id)) + + # if event.settings.get('ticketoutput_googlepaypasses_logo'): + # output_class.logo( + # urljoin(django_settings.SITE_URL, event.settings.get('ticketoutput_googlepaypasses_logo').url), + # str(event.name), + # event.name, + # ) + + # if event.location: + # name = {} + # address = {} + + # for key, value in event.location.data.items(): + # lines = value.splitlines() + # name[key] = lines[0] + # # We must provide at least one address line each for the name and address - no way around it. + # if len(lines) > 1: + # address[key] = '\n'.join(value.splitlines()[1:]) + # else: + # address[key] = lines[0] + + # output_class.venue(name, address) + + # if event.date_from and event.date_to and event.date_admission: + # output_class.date_time( + # DoorsOpen.doorsOpen, + # event.date_admission.isoformat(), + # event.date_from.isoformat(), + # event.date_to.isoformat(), + # ) + + # output_class.confirmation_code_label(ConfirmationCode.orderNumber) + + # if event.seating_plan_id is not None: + # output_class.seat_label(Seat.seat) + + # return self._comms().put_item(ClassType.eventTicketClass, class_name, output_class) + return output_class._eventTicketClass + class GoogleWalletStyle(PassStyle): platform = GooglePlatform diff --git a/src/pretix/plugins/wallet/ticketoutput.py b/src/pretix/plugins/wallet/ticketoutput.py index 4bc3937b0b..5d413421d5 100644 --- a/src/pretix/plugins/wallet/ticketoutput.py +++ b/src/pretix/plugins/wallet/ticketoutput.py @@ -71,7 +71,9 @@ class WalletOutput(BaseTicketOutput): wallet_layout = op.item.walletlayout else: wallet_layout = op.event.wallet_layouts.get(default=True) - platform_layout = get_object_or_404(wallet_layout.platform_layouts, platform=self.platform.identifier) + platform_layout = get_object_or_404( + wallet_layout.platform_layouts, platform=self.platform.identifier + ) return self.platform.generate(platform_layout.pass_layout, op) @@ -81,6 +83,48 @@ class GoogleWalletTicketOutput(WalletOutput): download_button_text = "Add to Google Wallet" platform = GooglePlatform + def get_global_settings(sender, **kwargs): + return OrderedDict( + [ + ( + "wallet_google_issuer_id", + forms.CharField( + label=_("Google Wallet Issuer/Merchant ID"), + help_text=_( + "After getting accepted by Google into the Google Pay API for Passes program, " + "your Issuer ID can be found in the Merchant center at " + "https://wallet.google.com/merchant/walletobjects/" + ), + required=False, + ), + ), + ( + "wallet_google_credentials", + forms.FileField( + label=_("Google Wallet Service Account Credentials"), + help_text=_( + "Please paste the contents of the JSON credentials file " + "of the service account you tied to your Google Pay API " + "for Passes Issuer ID" + ), + required=False, + # validators=[validate_json_credentials] + ), + ), + ( + "wallet_google_instance_uuid", + forms.CharField( + label=_("Google Wallet Pass Instance UUID"), + help_text=_( + "Instance-specific part to be added to the wallet ids" + ), + required=False, + ), + ), + + ] + ) + class AppleWalletTicketOutput(WalletOutput): identifier = "wallet_apple" @@ -129,7 +173,7 @@ class AppleWalletTicketOutput(WalletOutput): label=_("Apple Wallet Pass secret key"), required=False, validators=[validate_rsa_privkey], - widget=ClearableBasenameFileInput + widget=ClearableBasenameFileInput, ), ), (