mirror of
https://github.com/pretix/pretix.git
synced 2026-07-31 09:15:08 +00:00
WIP: predefined preview, google api, custom placeholder class
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
]
|
||||
@@ -20,7 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
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.
|
||||
"""
|
||||
@@ -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;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -93,3 +92,20 @@ const preview_layout = computed(() => {
|
||||
button.btn.btn-primary.btn-save(type="submit") Submit
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="css">
|
||||
.walletsettings-panel .panel-heading {
|
||||
.checkbox {
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
input[type=checkbox] {
|
||||
margin-top: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 0px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,148 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
import StyleSettings from "./style-settings.vue";
|
||||
import Select from "./input/select.vue";
|
||||
import Input from "./input/input.vue";
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const isLoading = ref<boolean>(true);
|
||||
const wallet_layout = ref<PlatformLayout | null>(null);
|
||||
|
||||
const PLATFORMS: Platforms = JSON.parse(
|
||||
document.querySelector("#platforms")?.textContent ?? "{}",
|
||||
);
|
||||
const VARIABLES: VariableConfig = JSON.parse(
|
||||
document.querySelector("#variables")?.textContent ?? "{}",
|
||||
);
|
||||
const LOCALES: Record<string, string> = JSON.parse(
|
||||
document.querySelector("#locales")?.textContent ?? "{}",
|
||||
);
|
||||
const CSRF_TOKEN =
|
||||
document.querySelector<HTMLInputElement>("input[name=csrfmiddlewaretoken]")
|
||||
?.value ?? "";
|
||||
|
||||
const props = defineProps<{
|
||||
layoutId: string;
|
||||
}>();
|
||||
|
||||
watchEffect(() => {
|
||||
// TODO: error handling / proper api client
|
||||
isLoading.value = true;
|
||||
fetch(
|
||||
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => {
|
||||
wallet_layout.value = x;
|
||||
isLoading.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
function saveLayout(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
isLoading.value = true;
|
||||
// TODO: error handling / proper api client
|
||||
fetch(
|
||||
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"X-CSRFToken": CSRF_TOKEN,
|
||||
},
|
||||
body: JSON.stringify(wallet_layout.value),
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch((x) => alert(x))
|
||||
.then((x) => {
|
||||
wallet_layout.value = x;
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function openForm(url: string, data: Record<string, string>) {
|
||||
|
||||
let form = document.createElement("form");
|
||||
form.target = "_blank";
|
||||
form.method = "POST";
|
||||
form.action = url;
|
||||
form.style.display = "none";
|
||||
|
||||
for (var key in data) {
|
||||
var input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = key;
|
||||
input.value = data[key];
|
||||
form.appendChild(input);
|
||||
}
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
}
|
||||
|
||||
|
||||
const currentPlatform = ref(PLATFORMS[0].identifier);
|
||||
const currentLayout = computed(() => ({}));
|
||||
const platformStyles = computed(() => {
|
||||
for (const platform of PLATFORMS) {
|
||||
if (platform.identifier === currentPlatform.value) {
|
||||
return platform.styles
|
||||
}
|
||||
}
|
||||
});
|
||||
const platformLayout = computed(() => {
|
||||
for (const layout of wallet_layout.value.platform_layouts) {
|
||||
if (layout.platform === currentPlatform.value) {
|
||||
return layout
|
||||
}
|
||||
}
|
||||
const newLayout = {platform: currentPlatform, style: null, layout: {}};
|
||||
wallet_layout.value.platform_layouts.push(newLayout);
|
||||
return newLayout
|
||||
});
|
||||
const platformChoices = computed(() => {
|
||||
return [[null, "Do not generate pass"], ...Object.values(platformStyles.value).map(x => [x.identifier, x.name])]
|
||||
});
|
||||
|
||||
function openPreview(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
openForm("../../preview/", {"csrfmiddlewaretoken": CSRF_TOKEN, "platform": currentPlatform.value, "style": platformLayout.value.style, "layout": JSON.stringify(platformLayout.value.layout)})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// 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
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { useId } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
}>()
|
||||
const modelValue = defineModel<boolean|null>();
|
||||
const id = useId()
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
.checkbox
|
||||
label
|
||||
input(:id="id" v-model="modelValue" v-bind="$attrs" type="checkbox")
|
||||
| {{ props.label }}
|
||||
</template>
|
||||
+21
-11
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import Select from "./input/select.vue";
|
||||
import Input from "./input/input.vue";
|
||||
import Checkbox from "./input/checkbox.vue";
|
||||
import I18nInput from "./input/i18ninput.vue";
|
||||
import TextContent from "./text-content.vue";
|
||||
import { StoreKey } from "../walletStore";
|
||||
|
||||
const store = inject(StoreKey)!
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
@@ -14,7 +14,9 @@ const props = defineProps<{
|
||||
fieldgroup: PlaceholderFieldGroupDefinition;
|
||||
overflows: FieldGroupDefinition[];
|
||||
}>();
|
||||
const fieldConfig = defineModel<PlaceholderFieldGroupConfig>({ required: true });
|
||||
const fieldConfig = defineModel<PlaceholderFieldGroupConfig>({
|
||||
required: true,
|
||||
});
|
||||
|
||||
const overflowOptions = computed((): Array<[string | null, string]> => {
|
||||
if (props.overflows.length) {
|
||||
@@ -32,17 +34,25 @@ function addVariable() {
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (!fieldConfig.value) {
|
||||
fieldConfig.value = {overflow: null, entries: JSON.parse(JSON.stringify(props.fieldgroup.default_entries))};
|
||||
}
|
||||
if (fieldConfig.value && !fieldConfig.value.entries) {
|
||||
fieldConfig.value.entries = JSON.parse(JSON.stringify(props.fieldgroup.default_entries))
|
||||
}
|
||||
if (!fieldConfig.value) {
|
||||
fieldConfig.value = {
|
||||
overflow: null,
|
||||
entries: JSON.parse(JSON.stringify(props.fieldgroup.default_entries)),
|
||||
active:
|
||||
props.fieldgroup.required ||
|
||||
props.fieldgroup.default_entries.length > 0,
|
||||
};
|
||||
}
|
||||
if (fieldConfig.value && !fieldConfig.value.entries) {
|
||||
fieldConfig.value.entries = JSON.parse(
|
||||
JSON.stringify(props.fieldgroup.default_entries),
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
.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")
|
||||
</template>
|
||||
</template>
|
||||
|
||||
+15
-4
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { watchEffect } from 'vue';
|
||||
import Input from './input/input.vue';
|
||||
import Checkbox from './input/checkbox.vue';
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -6,13 +10,20 @@ const props = defineProps<{
|
||||
}>();
|
||||
const fieldConfig = defineModel<PredefinedFieldGroupConfig>({ required: true });
|
||||
|
||||
watchEffect(() => {
|
||||
if (!fieldConfig.value) {
|
||||
fieldConfig.value = {active: props.fieldgroup.required};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
.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 }}
|
||||
</template>
|
||||
|
||||
+11
-5
@@ -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];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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;")
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ const props = defineProps<{ layout: Array<PreviewLayout> }>();
|
||||
width: 100%;
|
||||
aspect-ratio: 9 / 11;
|
||||
max-width: 360px;
|
||||
max-height: 440px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// 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 }}
|
||||
|
||||
+25
-26
@@ -9,19 +9,18 @@ const props = defineProps<{
|
||||
config: PreviewFieldgroup;
|
||||
style_def: PredefinedFieldGroupDefinition;
|
||||
}>();
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
store.currentPlatformLayout.layout.fieldgroups[props.style_def.identifier]?.active,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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 }}
|
||||
|
||||
|
||||
</template>
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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<FieldEntry>;
|
||||
overflow: string | null;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type PredefinedFieldGroupConfig = {};
|
||||
type PredefinedFieldGroupConfig = {
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type FieldGroupConfig =
|
||||
| PlaceholderFieldGroupConfig
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ''))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user