diff --git a/src/pretix/plugins/wallet/api.py b/src/pretix/plugins/wallet/api.py
index 7a67fec2e5..73ee642d84 100644
--- a/src/pretix/plugins/wallet/api.py
+++ b/src/pretix/plugins/wallet/api.py
@@ -5,7 +5,7 @@ from .models import WalletLayout, WalletPlatformLayout
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
-from .views import get_layout_variables
+from .views import get_editor_placeholders
from rest_framework import serializers
@@ -34,7 +34,7 @@ class WalletPlatformLayoutSerializer(I18nAwareModelSerializer):
style = platform_styles[data["style"]]
layout = PassLayout(style=style, layout=data["layout"])
- context = {"placeholders": get_layout_variables(self.context['event'])}
+ context = {"placeholders": get_editor_placeholders(self.context['event'])}
layout.validate(context=context)
return data
diff --git a/src/pretix/plugins/wallet/placeholders.py b/src/pretix/plugins/wallet/placeholders.py
new file mode 100644
index 0000000000..67d31185b3
--- /dev/null
+++ b/src/pretix/plugins/wallet/placeholders.py
@@ -0,0 +1,275 @@
+from collections.abc import Callable
+from .signals import (
+ register_wallet_text_placeholders,
+ register_wallet_image_placeholders,
+)
+from django.core.files import File
+from django.dispatch import receiver
+from pretix.base.templatetags.money import money_filter
+from django.contrib.staticfiles import finders
+from django.templatetags.static import static
+from django.utils.translation import gettext_lazy as _
+from i18nfield.strings import LazyI18nString
+
+
+class BaseWalletPlaceholder:
+ """
+ This is the base class for all wallet placeholders.
+ """
+
+ @property
+ def required_context(self) -> set[str]:
+ """
+ A a set of all attribute names that need to be contained in the base context so that this placeholder is available.
+ """
+ return set()
+
+ @property
+ def identifier(self) -> str:
+ """The unique identifier of this placeholder"""
+ raise NotImplementedError()
+
+ @property
+ def label(self) -> LazyI18nString:
+ """The human readable name of this placeholder"""
+ raise NotImplementedError()
+
+ @property
+ def control_label(self) -> LazyI18nString:
+ """
+ The human readable name of this placeholder shown in the backend.
+
+ Defaults to `label`
+ """
+ return self.label
+
+ def render(self, **context):
+ raise NotImplementedError()
+
+ def render_sample(self, **context):
+ raise NotImplementedError()
+
+
+
+class BaseWalletTextPlaceholder(BaseWalletPlaceholder):
+ def render(self, **context) -> str | None:
+ """
+ This method is called to generate the text that is being shown on the pass.
+ You will be passed the keyword arguments specified in ``required_context``.
+ You are expected to return a plain-text string.
+ """
+ raise NotImplementedError()
+
+ def render_sample(self, **context) -> str:
+ """
+ This method is called to generate a text to be used in previews.
+
+ You will be passed sample instances of the arguments specified in ``required_context``.
+ If those instances contain all data needed, you do not need to implement this.
+ """
+ sample = self.render(**context)
+ if sample is None:
+ raise RuntimeError("`render` returned None when rendering a sample")
+ return sample
+
+
+class BaseWalletImagePlaceholder(BaseWalletPlaceholder):
+ def render(self, **context) -> File | None:
+ """
+ This method is called to generate the image that is being shown on the pass.
+ You will be passed the keyword arguments specified in ``required_context``.
+ You are expected to return a `File` object.
+ """
+ raise NotImplementedError()
+
+ def render_sample(self, **context) -> str | None:
+ """
+ This method is called to generate a text to be used in previews.
+
+ You will be passed sample instances of the arguments specified in ``required_context``.
+ You are expected to return a URL to a sample image or `None` if no sample can be shown.
+ """
+ return None
+
+
+class FunctionalWalletTextPlaceholder(BaseWalletTextPlaceholder):
+ def __init__(
+ self,
+ identifier: str,
+ label: LazyI18nString,
+ args: set[str],
+ func: Callable[..., str | None],
+ sample: None | str | Callable[..., str] = None,
+ ):
+ self._identifier = identifier
+ self._label = label
+ self._args = args
+ self.render = func
+ self._sample = sample
+
+ @property
+ def identifier(self):
+ return self._identifier
+
+ @property
+ def label(self):
+ return self._label
+
+ @property
+ def required_context(self) -> set[str]:
+ return self._args
+
+ def render_sample(self, **context) -> str:
+ if isinstance(self._sample, Callable):
+ return self._sample(**context)
+ elif self._sample:
+ return self._sample
+ else:
+ return super().render_sample(**context)
+
+
+class FunctionalWalletImagePlaceholder(BaseWalletImagePlaceholder):
+ def __init__(
+ self,
+ identifier: str,
+ label: LazyI18nString,
+ args: set[str],
+ func: Callable[..., File | None],
+ sample: None | str | Callable[..., str] = None,
+ ):
+ self._identifier = identifier
+ self._label = label
+ self._args = args
+ self.render = func
+ self._sample = sample
+
+ @property
+ def required_context(self) -> set[str]:
+ return self._args
+
+ @property
+ def identifier(self):
+ return self._identifier
+
+ @property
+ def label(self):
+ return self._label
+
+ def render_sample(self, **context) -> str | None:
+ if isinstance(self._sample, Callable):
+ return self._sample(**context)
+ return self._sample
+
+
+class MissingContextException(Exception):
+ pass
+
+
+class WalletPlaceholderContext:
+ def __init__(self, **kwargs):
+ self.context_args = kwargs
+ self.cache = {}
+
+ def _get_placeholder_context(self, placeholder: BaseWalletPlaceholder):
+ missing_context = placeholder.required_context - self.context_args.keys()
+ if missing_context:
+ raise MissingContextException(
+ f"Missing context args for '{placeholder.identifier}': {', '.join(missing_context)}"
+ )
+
+ return {
+ k: v for k, v in self.context_args.items() if k in placeholder.required_context
+ }
+
+ def render_placeholder(self, placeholder: BaseWalletPlaceholder):
+ if placeholder.identifier in self.cache:
+ return self.cache[placeholder.identifier]
+
+ value = self.cache[placeholder.identifier] = placeholder.render(**self._get_placeholder_context(placeholder))
+ return value
+
+ def render_sample(self, placeholder: BaseWalletPlaceholder):
+ return placeholder.render_sample(**self._get_placeholder_context(placeholder))
+
+
+def get_wallet_placeholders(event):
+ placeholders = {
+ "text": {
+ v.identifier: v
+ for r, vs in register_wallet_text_placeholders.send(sender=event)
+ for v in vs
+ },
+ "image": {
+ v.identifier: v
+ for r, vs in register_wallet_image_placeholders.send(sender=event)
+ for v in vs
+ },
+ }
+ return placeholders
+
+
+
+def get_static_file(name) -> File | None:
+ path: str | None = finders.find(name) # type: ignore
+ if not path:
+ return
+ return File(open(path, "rb"))
+
+
+@receiver(
+ register_wallet_text_placeholders,
+ dispatch_uid="plugin_wallet_register_wallet_text_placeholders",
+)
+def base_text_placeholders(sender, **kwargs):
+ return [
+ FunctionalWalletTextPlaceholder("name", LazyI18nString.from_gettext("Event Name"), {"event"}, lambda event: event.name),
+ FunctionalWalletTextPlaceholder(
+ "event_slug", LazyI18nString.from_gettext("Event Slug"), {"event"}, lambda event: event.slug
+ ),
+ FunctionalWalletTextPlaceholder("order", LazyI18nString.from_gettext("Order Code"), {"order"}, lambda order: order.code),
+ FunctionalWalletTextPlaceholder(
+ "total",
+ LazyI18nString.from_gettext("Order Total"),
+ {"event", "order"},
+ lambda event, order: money_filter(order.total, event.currency),
+ ),
+ FunctionalWalletTextPlaceholder(
+ "order_email",LazyI18nString.from_gettext("Order Email"), {"order"}, lambda order: order.email
+ ),
+ FunctionalWalletTextPlaceholder(
+ "price",LazyI18nString.from_gettext("Item Price"), {"event", "order_position"}, lambda event, order_position: money_filter(order_position.price, event.currency)
+ ),
+ FunctionalWalletTextPlaceholder(
+ "secret",LazyI18nString.from_gettext("Order Secret (QR-Code-Content)"), {"order_position"}, lambda order_position: order_position.secret
+ ),
+ ]
+
+@receiver(
+ register_wallet_image_placeholders,
+ dispatch_uid="plugin_wallet_register_wallet_image_placeholders",
+)
+def base_image_placeholders(sender, **kwargs):
+ return [
+ FunctionalWalletImagePlaceholder(
+ "poweredby",
+ LazyI18nString.from_gettext("Logo"),
+ set(),
+ # TODO: replace with paths not from another plugin
+ lambda: get_static_file("pretix_passbook/logo.png"),
+ static("pretix_passbook/logo.png"),
+ ),
+ FunctionalWalletImagePlaceholder(
+ "poweredby_icon",
+ LazyI18nString.from_gettext("Icon"),
+ set(),
+ lambda: get_static_file("pretix_passbook/icon.png"),
+ static("pretix_passbook/icon.png"),
+ ),
+ FunctionalWalletImagePlaceholder(
+ "example_no_preview",
+ LazyI18nString.from_gettext("Image with no preview"),
+ set(),
+ lambda: get_static_file("pretix_passbook/icon.png"),
+ ),
+ # TODO: Image upload
+ ]
diff --git a/src/pretix/plugins/wallet/signals.py b/src/pretix/plugins/wallet/signals.py
index b5facb8e77..a65907498c 100644
--- a/src/pretix/plugins/wallet/signals.py
+++ b/src/pretix/plugins/wallet/signals.py
@@ -20,7 +20,7 @@
# .
#
-from pretix.base.signals import register_ticket_outputs, register_global_settings
+from pretix.base.signals import register_ticket_outputs, register_global_settings, EventPluginSignal
from .ticketoutput import OUTPUTS
def connect_signals():
@@ -29,9 +29,26 @@ def connect_signals():
def get_register_func(o):
def register(sender, **kwargs):
return o
- return register
+ return register
register_ticket_outputs.connect(get_register_func(output), dispatch_uid=f"wallet_output_{output.identifier}")
if hasattr(output, "get_global_settings"):
register_global_settings.connect(output.get_global_settings, dispatch_uid=f"wallet_global_settings_{output.identifier}")
connect_signals()
+
+
+register_wallet_text_placeholders = EventPluginSignal()
+"""
+This signal is sent out to get all known wallet placeholders. Receivers should return
+an list of subclasses of pretix.plugins.wallet.placeholders.BaseWalletTextPlaceholder.
+
+As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
+"""
+
+register_wallet_image_placeholders = EventPluginSignal()
+"""
+This signal is sent out to get all known wallet placeholders. Receivers should return
+an list of subclasses of pretix.plugins.wallet.placeholders.BaseWalletImagePlaceholder.
+
+As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
+"""
\ No newline at end of file
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue
index fcd8c1788c..80ba7d4733 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue
@@ -49,8 +49,7 @@ const platformChoices = computed(() => {
});
const preview_layout = computed(() => {
- return store.currentPlatformStyles[store.currentPlatformLayout.style]
- .preview_layout;
+ return store.currentPlatformStyles[store.currentPlatformLayout.style]?.preview_layout;
});
@@ -93,3 +92,20 @@ const preview_layout = computed(() => {
button.btn.btn-primary.btn-save(type="submit") Submit
+
+
+
\ No newline at end of file
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue_old b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue_old
deleted file mode 100644
index 21df10b708..0000000000
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue_old
+++ /dev/null
@@ -1,148 +0,0 @@
-
-
-
- // TODO: add :key for all `v-for`s
- // TODO: i18n textfields
- // TODO: proper spinner
- template(v-if="isLoading") {{ gettext("Loading...") }}
- form(v-else @submit="saveLayout")
- .form-group
- Input(label="Name" v-model="wallet_layout.name")
- nav
- ul.nav.nav-tabs
- li(v-for="platform in PLATFORMS" :class="{'active': currentPlatform === platform.identifier}")
- a(role="tab" @click="currentPlatform = platform.identifier") {{ platform.name }}
- .tabbed-form.tab-content
- .tab-pane.active.row
- .col-md-8
- Select.form-group(label="Style" v-model="platformLayout.style" :choices="platformChoices")
-
- StyleSettings(v-if="platformLayout.style" v-model="platformLayout.layout" :style="platformStyles[platformLayout.style]" :variables="VARIABLES" :locales="LOCALES")
- .col-md-4
- .panel.panel-default
- .panel-heading Preview
- .panel-body
- // TODO: Preview
- pre
- code {{ platformLayout }}
- pre(v-if="wallet_layout.style")
- code {{ platformStyles[wallet_layout.style] }}
- pre
- code {{ wallet_layout }}
- .form-group.submit-group
- button.btn.btn-lg.btn-default(type="button" @click="openPreview") Preview
- button.btn.btn-primary.btn-save(type="submit") Submit
-
-
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/checkbox.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/checkbox.vue
new file mode 100644
index 0000000000..a1382af55f
--- /dev/null
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/checkbox.vue
@@ -0,0 +1,20 @@
+
+
+
+ .checkbox
+ label
+ input(:id="id" v-model="modelValue" v-bind="$attrs" type="checkbox")
+ | {{ props.label }}
+
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/placeholder-field-settings.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/placeholder-field-settings.vue
index cde081c79d..b4187c189c 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/placeholder-field-settings.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/placeholder-field-settings.vue
@@ -1,12 +1,12 @@
- .panel.panel-default
+ .panel.panel-default.walletsettings-panel
.panel-heading
h3.panel-title {{ fieldgroup.name }}
.panel-body(v-if="fieldConfig")
@@ -76,4 +86,4 @@ watchEffect(() => {
i.fa.fa-plus
| {{ gettext("Add field") }}
Select(:label="gettext('Overflow to …')" :choices="overflowOptions" v-model="fieldConfig.overflow")
-
\ No newline at end of file
+
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/predefined-field-settings.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/predefined-field-settings.vue
index 9621aaf1d2..4f5539af58 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/predefined-field-settings.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/predefined-field-settings.vue
@@ -1,4 +1,8 @@
- .panel.panel-default
+ .panel.panel-default.walletsettings-panel
.panel-heading
- h3.panel-title {{ fieldgroup.name }}
- .panel-body
+ h3.panel-title.form-inline
+ .form-group
+ Checkbox(:label="fieldgroup.name" v-model="fieldConfig.active")
+ .panel-body(v-if="!!fieldgroup.description")
.form-group
- span.text-muted These fields appear somewhere and are visible too.
+ span.text-muted {{ fieldgroup.description }}
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
index f834cdf336..4818caf217 100644
--- 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
@@ -2,20 +2,26 @@
import { computed, inject, reactive, watchEffect } from "vue";
import { StoreKey } from "../../walletStore";
import PlaceholderFieldgroupPreview from "./placeholder-fieldgroup-preview.vue";
+import PredefinedFieldgroupPreview from "./predefined-fieldgroup-preview.vue";
-const store = inject(StoreKey)!
+const store = inject(StoreKey)!;
const props = defineProps<{
- config: PreviewFieldgroup;
+ config: PreviewFieldgroup;
}>();
-const style_def = Object.fromEntries(store.currentPlatformStyles[store.currentPlatformLayout.style].fieldgroups.map(x => [x.identifier, x]))[props.config.fieldgroup]
-
+const style_def = computed(() => {
+ return Object.fromEntries(
+ store.currentPlatformStyles[
+ store.currentPlatformLayout.style
+ ].fieldgroups.map((x) => [x.identifier, x]),
+ )[props.config.fieldgroup];
+});
PlaceholderFieldgroupPreview(v-if="style_def && style_def.type == 'placeholder'" :config="config" :style_def="style_def")
- PlaceholderFieldgroupPreview(v-else-if="style_def && style_def.type == 'predefined'" :config="config" :style_def="style_def")
+ PredefinedFieldgroupPreview(v-else-if="style_def && style_def.type == 'predefined'" :config="config" :style_def="style_def")
//- // TODO: support predefined group
//- div(:style="{'flex-grow': config.relSize}")
//- div(style="background-color: gray; display: flex; flex-direction: row;")
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
index f8c890805b..33f81b85d5 100644
--- 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
@@ -21,7 +21,6 @@ const props = defineProps<{ layout: Array }>();
width: 100%;
aspect-ratio: 9 / 11;
max-width: 360px;
- max-height: 440px;
vertical-align: top;
display: inline-block;
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
index d2c9834f10..38ff7fd17f 100644
--- 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
@@ -12,7 +12,6 @@ const props = defineProps<{
- // TODO: support predefined group
div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
div.fieldgroup-item(v-for="{ entry, label, content } of store.currentLayoutFieldContent[config.fieldgroup]")
div.fieldgroup-item-qrcode(v-if="style_def.display == 'code'") {{ label }}
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/predefined-fieldgroup-preview.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/predefined-fieldgroup-preview.vue
index 2b80119ee4..c1e6be5eb8 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/predefined-fieldgroup-preview.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/preview/predefined-fieldgroup-preview.vue
@@ -9,19 +9,18 @@ const props = defineProps<{
config: PreviewFieldgroup;
style_def: PredefinedFieldGroupDefinition;
}>();
+
+const isActive = computed(
+ () =>
+ store.currentPlatformLayout.layout.fieldgroups[props.style_def.identifier]?.active,
+);
- div {{ config.fieldgroup }}
- // TODO: support predefined group
- div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
- div.fieldgroup-item(v-for="{ entry, label, content } of store.currentLayoutFieldContent[config.fieldgroup]")
- div.fieldgroup-item-qrcode(v-if="style_def.display == 'code'") {{ label }}
-
- template(v-else)
- div.fieldgroup-label.nowrap(v-if="style_def.display == 'with_label'") {{ label }}
- div.nowrap.content(v-if="style_def.content_type == 'text'" :class="config.display") {{ content }}
- img.fieldgroup-item-image(v-else-if="style_def.content_type == 'image' && content" :src="i18nstringLocalize(content)")
+ div.fieldgroup-container(v-if="isActive" :style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
+ div.fieldgroup-item(v-for="{ label, content } of config.sample")
+ div.fieldgroup-label.nowrap(v-if="!!label") {{ label }}
+ div.nowrap.content(v-if="!!content" :class="config.display") {{ content }}
@@ -31,7 +30,7 @@ const props = defineProps<{
min-width: 0;
display: flex;
gap: 0.5em;
- min-height: 2lh;
+ min-height: 2lh;
}
.fieldgroup-label {
font-weight: bold;
@@ -40,26 +39,26 @@ const props = defineProps<{
.fieldgroup-item {
flex: 0 1 100%;
min-width: 0;
- width: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
}
.fieldgroup-item-image {
- max-height: 3lh;
+ max-height: 3lh;
}
.fieldgroup-item-qrcode {
- font-weight: bold;
- background-color: lightgray;
- width: 50%;
- aspect-ratio: 1;
- margin: auto;
- /* center content */
- display: flex;
+ font-weight: bold;
+ background-color: lightgray;
+ width: 50%;
+ aspect-ratio: 1;
+ margin: auto;
+ /* center content */
+ display: flex;
align-items: center;
text-align: center;
- padding: 1em;
- overflow-wrap: anywhere;
+ padding: 1em;
+ overflow-wrap: anywhere;
}
.nowrap {
@@ -74,6 +73,6 @@ const props = defineProps<{
font-size: 1.5em;
}
.tight {
- line-height: 1;
+ line-height: 1;
}
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts b/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
index f072cf8cda..c1c564cefb 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
@@ -3,6 +3,7 @@ type BaseFieldGroupDefinition = {
identifier: string;
name: string;
required: boolean;
+ description: string;
};
type FieldGroupDefinition =
@@ -50,7 +51,7 @@ type Style = {
type Variable = {
label: string;
- editor_sample: I18nString;
+ sample: string;
};
type Platform = {
@@ -67,9 +68,12 @@ type Platforms = Platform[];
type PlaceholderFieldGroupConfig = {
entries: Array;
overflow: string | null;
+ active: boolean;
};
-type PredefinedFieldGroupConfig = {};
+type PredefinedFieldGroupConfig = {
+ active: boolean;
+};
type FieldGroupConfig =
| PlaceholderFieldGroupConfig
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/walletStore.ts b/src/pretix/plugins/wallet/static/pretixplugins/wallet/walletStore.ts
index e7486c96dd..d4a5693e12 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/walletStore.ts
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/walletStore.ts
@@ -72,7 +72,7 @@ export function createWalletStore(config: {
if (entry.type == "custom") {
value = i18nstringLocalize(entry.content);
} else if (entry.type == "placeholder") {
- value = placeholder.editor_sample;
+ value = placeholder?.sample || `(unknown placeholder: ${entry.content})`;
}
content[fieldgroup.identifier].push({
entry,
@@ -185,9 +185,12 @@ export function createWalletStore(config: {
entries: JSON.parse(
JSON.stringify(newFieldGroups[key].default_entries),
),
+ active: newFieldGroups[key].required || newFieldGroups[key].default_entries.length > 0
};
} else {
- this.currentPlatformLayout.layout.fieldgroups[key] = {};
+ this.currentPlatformLayout.layout.fieldgroups[key] = {
+ active: newFieldGroups[key].required
+ };
}
}
diff --git a/src/pretix/plugins/wallet/styles/__init__.py b/src/pretix/plugins/wallet/styles/__init__.py
index 7c93eecefb..b8afe87c61 100644
--- a/src/pretix/plugins/wallet/styles/__init__.py
+++ b/src/pretix/plugins/wallet/styles/__init__.py
@@ -15,14 +15,6 @@ AVAILABLE_STYLES_DICT = {
plat: {s.identifier: s for s in styls} for plat, styls in AVAILABLE_STYLES.items()
}
-
-# TODO? move to models?
-def get_platform(identifier: str):
- for platform in AVAILABLE_PLATFORMS:
- if platform.identifier == identifier:
- return platform
-
-
def get_style(platform: str, identifier: str):
return AVAILABLE_STYLES_DICT.get(platform, {}).get(identifier)
diff --git a/src/pretix/plugins/wallet/styles/apple.py b/src/pretix/plugins/wallet/styles/apple.py
index d12c7f7a56..fc2cd21e69 100644
--- a/src/pretix/plugins/wallet/styles/apple.py
+++ b/src/pretix/plugins/wallet/styles/apple.py
@@ -32,7 +32,7 @@ class ApplePlatform(WalletPlatform):
@classmethod
def generate(cls, layout: PassLayout, op: OrderPosition):
- from ..views import get_layout_variables
+ context = cls.get_context(op)
order = op.order
event = order.event
@@ -49,9 +49,7 @@ class ApplePlatform(WalletPlatform):
op.pk,
)
- context = {
- "placeholders": get_layout_variables(op.order.event),
- "evaluation_context": [op, order, order.event],
+ context.update({
"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(),
@@ -63,8 +61,7 @@ class ApplePlatform(WalletPlatform):
"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
@@ -234,6 +231,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
content="poweredby",
)
],
+ required=True
),
ImageFieldGroup(
identifier="logo",
@@ -245,6 +243,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
content="poweredby",
)
],
+ required=True
),
TextFieldGroup(
identifier="logo_text",
@@ -265,6 +264,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
)
], # TODO: support Lazyi18nproxy here
description=_("These fields appear prominently featured on the pass."),
+ required=True
),
TextFieldGroup(
identifier="secondary", name=_("Secondary"), max_entries=4
diff --git a/src/pretix/plugins/wallet/styles/base.py b/src/pretix/plugins/wallet/styles/base.py
index fac61f095f..98f4bed4fb 100644
--- a/src/pretix/plugins/wallet/styles/base.py
+++ b/src/pretix/plugins/wallet/styles/base.py
@@ -1,12 +1,31 @@
import enum
+from typing import Any
from i18nfield.strings import LazyI18nString
import jsonschema
from django.core.exceptions import ValidationError
+from pretix.base.models import OrderPosition
class WalletPlatform:
identifier: str
name: str
+ @classmethod
+ def generate(cls, layout: "PassLayout", op: OrderPosition) -> Any: # TODO: Typing
+ pass
+
+ @classmethod
+ def get_context(cls, op):
+ from ..placeholders import get_wallet_placeholders, WalletPlaceholderContext
+
+ order = op.order
+ event = order.event
+
+ return {
+ "placeholders": get_wallet_placeholders(event),
+ "placeholder_context": WalletPlaceholderContext(event=event, order=order, order_position=op),
+ "locale": event.settings.locale, # TODO: should probably be order locale
+ "locales": event.settings.locales,
+ }
class FieldGroupType(enum.Enum):
PLACEHOLDER = "placeholder"
@@ -287,11 +306,9 @@ class PassStyle:
.get(content)
)
if placeholder:
- placeholder_value = placeholder["evaluate"](
- *context.get("evaluation_context", [])
- )
+ placeholder_value = context['placeholder_context'].render_placeholder(placeholder)
if placeholder_value:
- return placeholder["label"], placeholder_value
+ return placeholder.label, placeholder_value
return None, None
diff --git a/src/pretix/plugins/wallet/styles/google.py b/src/pretix/plugins/wallet/styles/google.py
index 4fcd0c1859..a4786b1cb0 100644
--- a/src/pretix/plugins/wallet/styles/google.py
+++ b/src/pretix/plugins/wallet/styles/google.py
@@ -10,7 +10,7 @@ from .base import (
TextFieldGroup,
WalletPlatform,
)
-from django.utils.translation import gettext_lazy as _, gettext
+from django.utils.translation import gettext as _
import json
from walletobjects import ButtonJWT, EventTicketClass, EventTicketObject
@@ -72,7 +72,7 @@ def get_translated_dict(string, locales):
for locale in locales:
translation.activate(locale)
- translated[locale] = gettext(string)
+ translated[locale] = _(string)
translation.deactivate()
return translated
@@ -80,7 +80,7 @@ def get_translated_dict(string, locales):
def get_translated_string(string, locale):
translation.activate(locale)
- translated = gettext(string)
+ translated = _(string)
translation.deactivate()
return translated
@@ -92,26 +92,24 @@ class GooglePlatform(WalletPlatform):
@classmethod
def generate(cls, layout: PassLayout, op: OrderPosition):
- from ..views import get_layout_variables
+ context = cls.get_context(op)
order = op.order
event = order.event
- context = {
- "placeholders": get_layout_variables(event), # TODO: move to higher class
- "evaluation_context": [op, order, event],
- "credentials": event.settings.get("wallet_google_credentials").read(),
- "locale": event.settings.locale,
- "locales": event.settings.locales,
- "issuerName": event.organizer.name,
- "eventName": event.name,
- # TODO: use other classId and objectId in preview mode
- "classId": get_class_id(event),
- "objectId": get_object_id(op),
- "homepageUrl": eventreverse_absolute(event, "presale:event.index"),
- # TODO: add webhook view & register in pass
- "webhookUrl": "", # eventreverse_absolute(event.organizer,"plugins:wallet:google_webhook",))
- }
+ context.update(
+ {
+ "credentials": event.settings.get("wallet_google_credentials").read(),
+ "issuerName": event.organizer.name,
+ "eventName": event.name,
+ # TODO: use other classId and objectId in preview mode
+ "classId": get_class_id(event),
+ "objectId": get_object_id(op),
+ "homepageUrl": eventreverse_absolute(event, "presale:event.index"),
+ # TODO: add webhook view & register in pass
+ # "webhookUrl": eventreverse_absolute(event.organizer,"plugins:wallet:google_webhook",)
+ }
+ )
data = layout.generate(context)
return "url", "text/plain", data
@@ -135,8 +133,9 @@ class GoogleWalletStyle(PassStyle):
get_translated_string("Website", context["locale"]),
get_translated_dict("Website", context["locales"]),
)
- # TODO: add webhook view & register in pass
- # output_class.callback_url(context['webhookUrl'])
+
+ if context.get("webhookUrl"): # TODO: enforce that it exists
+ output_class.callback_url(context["webhookUrl"])
# TODO: move to pass settings or set defaults
# if (event.settings.get('ticketoutput_googlepaypasses_latitude')
@@ -249,55 +248,21 @@ class GoogleWalletStyle(PassStyle):
class_object = self._generate_class(layout, context, fields)
ticket_object = self._generate_object(layout, context, fields)
+ # TODO: privacy screen
+ class_object = comms.put_item(ClassType.eventTicketClass, class_object['id'], class_object)
+ ticket_object = comms.put_item(ObjectType.eventTicketObject, ticket_object['id'], ticket_object)
+
generated_jwt = comms.sign_jwt(
ButtonJWT(
origins=[settings.SITE_URL],
issuer=comms.client_email,
- event_ticket_classes=[class_object],
event_ticket_objects=[ticket_object],
- skinny=False,
+ skinny=True,
)
)
return "https://pay.google.com/gp/v/save/%s" % generated_jwt
- # 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
-
class GoogleWalletEventTicket(GoogleWalletStyle):
identifier = "event"
@@ -352,17 +317,23 @@ class GoogleWalletEventTicket(GoogleWalletStyle):
{"value": "12:34", "label": "Time"},
]
},
- {"fieldgroup": "seating"},
+ {"fieldgroup": "seating",
+ "sample": [
+ {"content": "5", "label": "Row"},
+ {"content": "2", "label": "Seat"},
+ ]
+ },
{"fieldgroup": "code"},
]
]
-
def _generate_object(self, layout: PassLayout, context, fields):
output_object = super()._generate_object(layout, context, fields)
if fields["code"]:
- output_object.barcode(Barcode.qrCode, fields["code"][0]["value"], fields["code"][0]["value"])
+ output_object.barcode(
+ Barcode.qrCode, fields["code"][0]["value"], fields["code"][0]["value"]
+ )
# output_object.reservation_info("%s-%s" % (op.order.event.slug, op.order.code))
# output_object.ticket_holder_name(op.attendee_name or (op.addon_to.attendee_name if op.addon_to else ''))
diff --git a/src/pretix/plugins/wallet/ticketoutput.py b/src/pretix/plugins/wallet/ticketoutput.py
index d82ac22cf0..15a600db52 100644
--- a/src/pretix/plugins/wallet/ticketoutput.py
+++ b/src/pretix/plugins/wallet/ticketoutput.py
@@ -26,13 +26,10 @@ from pretix.base.models import Event
from pretix.base.settings import SettingsSandbox
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
-from .styles import AVAILABLE_STYLES_DICT
-from .styles.base import PassLayout, WalletPlatform
+from .styles.base import WalletPlatform
from .styles.apple import ApplePlatform
from .styles.google import GooglePlatform
from collections import OrderedDict
-from .models import WalletLayout
-from .views import get_layout_variables
from django import forms
from .forms import CertificateFileField, validate_rsa_privkey
from pretix.control.forms import ClearableBasenameFileInput
diff --git a/src/pretix/plugins/wallet/views.py b/src/pretix/plugins/wallet/views.py
index 999af64597..d76254de54 100644
--- a/src/pretix/plugins/wallet/views.py
+++ b/src/pretix/plugins/wallet/views.py
@@ -29,38 +29,24 @@ from django.contrib import messages
from django.contrib.staticfiles import finders
from django.utils.functional import cached_property
from django.templatetags.static import static
-
-def get_layout_variables(event):
- return {
- "text": get_variables(event),
- "image": get_images(event)
- | {
- "poweredby": {
- "label": _("pretix-Logo"),
- "evaluate": lambda *_: open(
- finders.find("pretix_passbook/logo.png"), "rb"
- ),
- "editor_sample": static("pretix_passbook/logo.png")
- },
- "poweredby_icon": {
- "label": _("pretix-Icon"),
- "evaluate": lambda *_: open(
- finders.find("pretix_passbook/icon.png"), "rb"
- ),
- "editor_sample": static("pretix_passbook/icon.png")
- },
- }, # TODO: image upload
- }
+from .placeholders import get_wallet_placeholders, WalletPlaceholderContext
-def get_editor_variables(event):
- return {
- t: {
- vid: {"label": v.get("label"), "editor_sample": v.get("editor_sample")}
- for vid, v in vs.items()
+def get_editor_placeholders(event):
+ with (
+ rolledback_transaction(),
+ language(event.settings.locale, event.settings.region),
+ ):
+ p = get_preview_position(event)
+ context = WalletPlaceholderContext(event=event, order=p.order, order_position=p)
+ placeholders = {
+ t: {
+ pid: {"label": str(p.label), "sample": str(context.render_sample(p))}
+ for pid, p in ps.items()
+ }
+ for t, ps in get_wallet_placeholders(event).items()
}
- for t, vs in get_layout_variables(event).items()
- }
+ return placeholders
class WalletLayoutMixin:
@@ -97,10 +83,7 @@ class LayoutEditorView(LayoutDetailView):
}
for platform in AVAILABLE_PLATFORMS
]
- # context["styles"] = {
- # style.identifier: style.asdict() for style in self.get_platform_styles()
- # }
- context["variables"] = get_editor_variables(self.request.event)
+ context["variables"] = get_editor_placeholders(self.request.event)
context["locales"] = {
l: dict(settings.LANGUAGES).get(l, l)
for l in self.request.event.settings.get("locales")
@@ -190,7 +173,7 @@ class LayoutPreviewView(EventPermissionRequiredMixin, View):
):
p = get_preview_position(request.event)
layout = PassLayout(style=style, layout=layout)
- context = {"placeholders": get_layout_variables(event)}
+ context = {"placeholders": get_wallet_placeholders(event)}
layout.validate(context=context)
fname, mimet, data = platform.generate(layout, p)