diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/text-content.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/text-content.vue index 19004d7247..cd4b7b7005 100644 --- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/text-content.vue +++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/text-content.vue @@ -21,7 +21,7 @@ const selection = computed({ get() { if (entry.value.type === 'placeholder') { return entry.value.content - } else if (entry.value.type === 'text') { + } else if (entry.value.type === 'custom') { return "other" } else { throw new Error(`Unknown entry type "${entry.value.type}"`); @@ -29,7 +29,7 @@ const selection = computed({ }, set(newValue) { if (newValue == "other") { - entry.value.type = "text" + entry.value.type = "custom" entry.value.content = {}; } else { entry.value.type = "placeholder" @@ -42,7 +42,7 @@ const textContent = computed({ get() { if (entry.value.type === 'placeholder') { return "" - } else if (entry.value.type === 'text') { + } else if (entry.value.type === 'custom') { return entry.value.content } else { throw new Error(`Unknown entry type "${entry.value.type}"`); diff --git a/src/pretix/plugins/wallet/styles/apple.py b/src/pretix/plugins/wallet/styles/apple.py index b9e60d17a8..da8c178805 100644 --- a/src/pretix/plugins/wallet/styles/apple.py +++ b/src/pretix/plugins/wallet/styles/apple.py @@ -111,10 +111,10 @@ class SignedZipFile: class AppleWalletStyle(PassStyle): platform = ApplePlatform - def pass_content(self, layout, context, strings): + def pass_content(self, fields, strings): raise NotImplementedError() - def generate_pass_json(self, layout, context, strings): + def generate_pass_json(self, fields, context, strings): def add_from_context(key): value = context.get(key) if not value: @@ -128,7 +128,7 @@ class AppleWalletStyle(PassStyle): "passTypeIdentifier": add_from_context("passTypeIdentifier"), "teamIdentifier": add_from_context("teamIdentifier"), "serialNumber": add_from_context("serialNumber"), - **self.pass_content(layout, context, strings), + **self.pass_content(fields, strings), } return pass_json @@ -136,6 +136,9 @@ class AppleWalletStyle(PassStyle): for key in ["ca_certificate", "certificate", "key", "password", "locales"]: if key not in context: raise ValueError(f"{key} missing from context") + + fields = self.get_pass_fields(layout, context) + pkpass = SignedZipFile( context["ca_certificate"], context["certificate"], @@ -144,12 +147,23 @@ class AppleWalletStyle(PassStyle): ) strings = StringResource(locales=context['locales']) - pass_json = self.generate_pass_json(layout, context, strings) + pass_json = self.generate_pass_json(fields, context, strings) print(pass_json) - pkpass.add_file( - "icon.png", open(finders.find("pretix_passbook/icon.png"), "rb").read() - ) + if fields['logo']: + logo = fields['logo'][0]['value'] + else: + logo = open(finders.find("pretix_passbook/logo.png"), "rb") + if fields['icon']: + icon = fields['icon'][0]['value'] + else: + icon = open(finders.find("pretix_passbook/icon.png"), "rb") + + pkpass.add_file("icon.png", icon.read()) + pkpass.add_file("logo.png", logo.read()) + + for lang, content in strings.generate().items(): + pkpass.add_file(f"{lang}.lproj/pass.strings", content) pkpass.add_file("pass.json", json.dumps(pass_json)) return pkpass.finish() @@ -158,6 +172,18 @@ class AppleWalletEventTicket(AppleWalletStyle): identifier = "event_1" name = _("Event Ticket Layout 1") fieldgroups = [ + ImageFieldGroup( + identifier="icon", + name=_("Icon"), + min_entries=0, + max_entries=1, + labels=False, + default_entries=[ + PlaceholderFieldEntry( + content="poweredby", + ) + ], + ), ImageFieldGroup( identifier="logo", name=_("Logo"), @@ -194,47 +220,6 @@ class AppleWalletEventTicket(AppleWalletStyle): ] # preview_image = "apple/event_ticket.svg" - def get_pass_fields(self, layout, context): - fields = {} - for group in self.fieldgroups: - if isinstance(group, PredefinedFieldGroup): - pass - elif isinstance(group, PlaceholderFieldGroup): - group_fields = [] - if group.identifier in layout["fieldgroups"]: - for field in layout["fieldgroups"][group.identifier]["entries"]: - field_entry = {} - if group.labels: - field_entry["label"] = LazyI18nString(field["label"]) - if field["type"] == FieldEntryType.PLACEHOLDER.value: - placeholder = ( - context.get("placeholders") - .get(group.content_type.value, {}) - .get(field["content"]) - ) - if placeholder: - placeholder_value = placeholder["evaluate"]( - *context.get("evaluation_context", []) - ) - if placeholder_value: - field_entry["value"] = placeholder_value - elif field["type"] == FieldEntryType.TEXT.value: - placeholder_value = LazyI18nString(field["content"]) - elif field["type"] == FieldEntryType.IMAGE.value: - raise NotImplementedError( - "Image placeholders not implemented" - ) - if "value" in field_entry and field_entry["value"]: - group_fields.append(field_entry) - if group.min_entries and len(group_fields) < group.min_entries: - raise ValueError( - f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(group_fields)} were provided" - ) - fields[group.identifier] = group_fields[: group.max_entries] - else: - raise ValueError("Unknown field group") - return fields - def convert_fields(self, strings, fields, prefix): converted = [] for i,f in enumerate(fields): @@ -243,13 +228,18 @@ class AppleWalletEventTicket(AppleWalletStyle): strings.add_entry(f"{prefix}-{i}-label", converted_field['label']) converted_field['label'] = f"{prefix}-{i}-label" + if isinstance(converted_field['value'], LazyI18nString): + strings.add_entry(f"{prefix}-{i}-value", converted_field['value']) + converted_field['value'] = f"{prefix}-{i}-value" converted.append(converted_field) return converted - def pass_content(self, layout, context, strings): - fields = self.get_pass_fields(layout, context) + def pass_content(self, fields, strings): return { "eventTicket": { - "primaryFields": self.convert_fields(strings, fields['primary'], 'primary') + "primaryFields": self.convert_fields(strings, fields['primary'], 'primary'), + "secondaryFields": self.convert_fields(strings, fields['secondary'], 'secondary'), + "auxillaryFields": self.convert_fields(strings, fields['auxillary'], 'auxillary'), + "backFields": self.convert_fields(strings, fields['back'], 'back'), } } diff --git a/src/pretix/plugins/wallet/styles/base.py b/src/pretix/plugins/wallet/styles/base.py index ebcc347f96..af42f59693 100644 --- a/src/pretix/plugins/wallet/styles/base.py +++ b/src/pretix/plugins/wallet/styles/base.py @@ -49,8 +49,7 @@ class FieldContentType(enum.Enum): class FieldEntryType(enum.Enum): - IMAGE = "image" - TEXT = "text" + CUSTOM = "custom" PLACEHOLDER = "placeholder" @@ -191,7 +190,7 @@ class PlaceholderFieldGroup(FieldGroup): { "properties": { **baseprops, - "type": {"const": self.content_type.value}, + "type": {"const": "custom"}, "content": {"$ref": "#/$defs/I18nString"}, } }, @@ -276,6 +275,49 @@ class PassStyle: def generate(self, layout, context): raise NotImplementedError() + + def render_placeholder(self, context, content_type, content): + placeholder = ( + context.get("placeholders") + .get(content_type, {}) + .get(content) + ) + if placeholder: + placeholder_value = placeholder["evaluate"]( + *context.get("evaluation_context", []) + ) + if placeholder_value: + return placeholder_value + def get_pass_fields(self, layout, context): + fields = {} + for group in self.fieldgroups: + if isinstance(group, PredefinedFieldGroup): + pass + elif isinstance(group, PlaceholderFieldGroup): + group_fields = fields.get(group.identifier, []) + if group.identifier in layout["fieldgroups"]: + for field in layout["fieldgroups"][group.identifier]["entries"]: + field_entry = {} + if group.labels: + field_entry["label"] = LazyI18nString(field["label"]) + if field["type"] == FieldEntryType.PLACEHOLDER.value: + field_entry["value"] = self.render_placeholder(context, group.content_type.value, field['content']) + elif field["type"] == FieldEntryType.CUSTOM.value: + field_entry["value"] = LazyI18nString(field["content"]) + if "value" in field_entry and field_entry["value"]: + group_fields.append(field_entry) + if group.min_entries and len(group_fields) < group.min_entries: + raise ValueError( + f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(group_fields)} were provided" + ) + fields[group.identifier] = group_fields[: group.max_entries] + if (overflow_group := layout["fieldgroups"][group.identifier]['overflow']): + fields.setdefault(overflow_group, []) + fields[overflow_group] += group_fields[group.max_entries:] + else: + raise ValueError("Unknown field group") + return fields + class PassLayout: style: PassStyle diff --git a/src/pretix/plugins/wallet/ticketoutput.py b/src/pretix/plugins/wallet/ticketoutput.py index 620015a57b..d532fa2ee1 100644 --- a/src/pretix/plugins/wallet/ticketoutput.py +++ b/src/pretix/plugins/wallet/ticketoutput.py @@ -27,6 +27,8 @@ from pretix.base.settings import SettingsSandbox from django.template.loader import render_to_string from .styles import AVAILABLE_STYLES_DICT +from .styles.apple import ApplePlatform +from .styles.google import GooglePlatform from .models import WalletLayout from .views import get_layout_variables @@ -65,12 +67,14 @@ class GoogleWalletTicketOutput(WalletOutput): identifier = "wallet_google" verbose_name = _("Google") download_button_text = "Add to Google Wallet" + platform = GooglePlatform class AppleWalletTicketOutput(WalletOutput): identifier = "wallet_apple" verbose_name = _("Apple") download_button_text = "Add to Apple Wallet" + platform = ApplePlatform def generate(self, op): order = op.order @@ -87,6 +91,7 @@ class AppleWalletTicketOutput(WalletOutput): # ) # ) layout = WalletLayout.objects.get(pk=1) + platform_layout = layout.platform_layouts.get(platform=self.platform.identifier) ticket = str(op.item.name) if op.variation: @@ -121,9 +126,9 @@ class AppleWalletTicketOutput(WalletOutput): "serialNumber": serialNumber, "locales": event.settings.locales } - assert layout.platform == "apple" - data = AVAILABLE_STYLES_DICT[layout.platform][layout.style].generate( - layout.layout, context + + data = AVAILABLE_STYLES_DICT[self.platform.identifier][platform_layout.style].generate( + platform_layout.layout, context ) return filename, "application/vnd.apple.pkpass", data diff --git a/src/pretix/plugins/wallet/views.py b/src/pretix/plugins/wallet/views.py index 163f8e008d..e9ff8090e1 100644 --- a/src/pretix/plugins/wallet/views.py +++ b/src/pretix/plugins/wallet/views.py @@ -12,12 +12,14 @@ from django.conf import settings from .models import WalletLayout from .styles import AVAILABLE_STYLES, AVAILABLE_PLATFORMS +from django.contrib.staticfiles import finders def get_layout_variables(event): return { "text": get_variables(event), "image": get_images(event) - | {"poweredby": {"label": _("pretix-Logo")}}, # TODO: image upload + | {"poweredby": {"label": _("pretix-Logo"), "evaluate": lambda *_: open(finders.find("pretix_passbook/logo.png"), "rb")}, + "poweredby_icon": {"label": _("pretix-Icon"), "evaluate": lambda *_: open(finders.find("pretix_passbook/icon.png"), "rb")}}, # TODO: image upload }