diff --git a/src/pretix/plugins/wallet/serializer.py b/src/pretix/plugins/wallet/serializer.py
new file mode 100644
index 0000000000..e69de29bb2
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 cb59ad70d3..e1d9d554a0 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/app.vue
@@ -1,37 +1,41 @@
// TODO: add :key for all `v-for`s
- //- pre
- //- code {{ STYLES }}
+ // TODO: i18n
+ details
+ pre
+ code {{ FORM_ERRORS }}
.row
.col-md-8
+ // TODO: show error text
.form-group(:class='"name" in FORM_ERRORS ? "has-error" : ""')
- label.control-label(for="layout-info-name") Name
- input#layout-info-name.form-control(v-model="name" name="name")
+ Input(label="Name" v-model="name" name="name" :errors="FORM_ERRORS['name']")
.form-group(:class='"style" in FORM_ERRORS ? "has-error" : ""')
- label.control-label(for="layout-info-style") Style
- select#layout-info-style.form-control(v-model="style" name="style")
- option(v-for="styleconfig in STYLES" :key="styleconfig.identifier" :value="styleconfig.identifier") {{ styleconfig.name }}
+ Select(label="Style" v-model="style" :choices="Object.values(STYLES).map(x => [x.identifier, x.name])" name="style" :errors="FORM_ERRORS['style']")
- StyleSettings(v-model="layout" :style="style" :styles="STYLES" :variables="VARIABLES")
+ StyleSettings(v-if="style" v-model="layout" :style="style" :styles="STYLES" :variables="VARIABLES")
.col-md-4
.panel.panel-default
.panel-heading Preview
- // TODO: i18n
.panel-body
// TODO: Preview
pre
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/field-settings.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/field-settings.vue
index 0ba28ad736..76f8b05146 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/field-settings.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/field-settings.vue
@@ -1,55 +1,60 @@
- //- pre
- //- code {{ props }}
.panel.panel-default
.panel-heading
h3.panel-title {{ field.name }}
.panel-body
- .form-group(v-if="props.variables[field.entry_type]")
+ .form-group
span.text-muted These fields appear somewhere and are visible too.
- // TODO: for="..." / labeledby?
- h4 Fields
+ h4 {{ gettext("Content") }}
.row.form-group(v-for="n in fieldConfig.entries.length")
.col-md-5
- // TODO: i18n
- label.control-label Label
- input.form-control(v-model="fieldConfig.entries[n-1].label")
- .col-md-6
- label.control-label Value
- select.form-control(v-model="fieldConfig.entries[n-1].value")
- option(v-for="(config, id) in props.variables[field.entry_type]" :key="id" :value="id") {{ config.label }}
+ Input(:label="gettext('Label')" v-model="fieldConfig.entries[n-1].label")
+ .col-md-6(v-if='field.entry_type == "text"')
+ TextContent(v-model="fieldConfig.entries[n-1]"
+ :variables="props.variables")
+ .col-md-6(v-else-if='field.entry_type == "image"')
+ Select(:label="gettext('Content')"
+ v-model="fieldConfig.entries[n-1].content"
+ :choices="Object.entries(props.variables).map(([k,v]) => [k, v.label])"
+ )
.col-md-1
label.control-label
- span.sr-only "Delete"
+ span.sr-only {{ gettext('Delete')}}
button.btn.btn-danger(type="button" @click="fieldConfig.entries.splice(n-1, 1)")
i.fa.fa-trash
- span.sr-only "Delete"
+ span.sr-only {{ gettext('Delete')}}
button.btn.btn-default(type="button" @click="addVariable")
i.fa.fa-plus
- span.sr-only Add field
- OptionalSelect(label="Overflow to ..." :choices="overflowOptions" v-model="fieldConfig.overflow")
+ span.sr-only {{ gettext("Add field") }}
+ Select(:label="gettext('Overflow to …')" :choices="overflowOptions" v-model="fieldConfig.overflow")
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/input.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/input.vue
new file mode 100644
index 0000000000..9a286d20dd
--- /dev/null
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/input.vue
@@ -0,0 +1,20 @@
+
+
+
+ label.control-label(:for="id", v-if="props.label") {{ props.label }}
+ input.form-control(:id="id" v-model="modelValue" v-bind="$attrs")
+ .help-block(v-if="props.errors" v-for="error in props.errors") {{ error }}
+
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/select.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/select.vue
new file mode 100644
index 0000000000..52d3656188
--- /dev/null
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/input/select.vue
@@ -0,0 +1,31 @@
+
+
+
+ template(v-if="choices.length >= 1")
+ label.control-label(:for="id") {{ props.label }}
+ select.form-control(:id="id" v-model="modelValue" v-bind="$attrs")
+ option(v-for="choice in props.choices" :key="choice[0]" :value="choice[0]") {{ choice[1] }}
+ .help-block(v-if="props.errors" v-for="error in props.errors") {{ error }}
+
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/optional-select.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/optional-select.vue
deleted file mode 100644
index ac738b0564..0000000000
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/optional-select.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- template(v-if="choices.length >= 1")
- label(:for="id") {{ props.label }}
- select.form-control(:id="id" v-model="modelValue")
- // TODO: persist/v-model
- option(v-for="choice in props.choices" :key="choice[0]" :value="choice[0]") {{ choice[1] }}
-
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/style-settings.vue b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/style-settings.vue
index b8910f8f04..eb6cf73902 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/style-settings.vue
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/style-settings.vue
@@ -1,45 +1,49 @@
- h2.h3 Form Fields
+ h2.h3 {{ gettext("Field Groups") }}
FieldSettings(v-if="styleData"
v-for="(field, fieldId) in styleData.fields"
v-model="layout.fields[field.identifier]"
:field="field"
- :overflows="styleData.fields.slice(fieldId+1).filter(x => x.entry_type === field.entry_type)"
- :variables="variables"
+ :overflows="styleData.fields.slice(fieldId + 1).filter(x => x.entry_type === field.entry_type)"
+ :variables="variables[field.entry_type]"
)
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
new file mode 100644
index 0000000000..defc8b6a7f
--- /dev/null
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/components/text-content.vue
@@ -0,0 +1,63 @@
+
+
+
+ Select(:label="gettext('Content')"
+ v-model="selection"
+ :choices="selectChoices"
+ )
+ Input(v-model="textContent" v-if="selection === 'other'")
+
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts b/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
new file mode 100644
index 0000000000..1f1c451dc0
--- /dev/null
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
@@ -0,0 +1,41 @@
+type FieldGroupDefinition = {
+ identifier: string;
+ entry_type: string;
+ name: string;
+ default_entries: FieldConfig[];
+};
+
+type Style = {
+ identifier: string;
+ name: string;
+ fields: FieldGroupDefinition[];
+};
+
+type Variable = {
+ label: string
+};
+
+type Styles = Record;
+type Variables = Record;
+type VariableConfig = Record;
+
+type FieldEntry = {
+ type: 'placeholder' | 'text';
+ label: string; // TODO i18n
+ content: string;
+}
+
+type FieldConfig = {
+ entries: Array;
+ overflow: string | null;
+};
+
+type LayoutData = {
+ fields?: Record;
+};
+
+type Layout = {
+ name?: string;
+ style?: string;
+ layout?: LayoutData;
+};
diff --git a/src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts b/src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts
index ec20a3924f..b727795b4f 100644
--- a/src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts
+++ b/src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts
@@ -1,8 +1,9 @@
import { createApp } from 'vue'
import App from './components/app.vue'
-const app = createApp(App)
-app.mount('#editor')
+const mountEl = document.querySelector('#editor')!
+const app = createApp(App, mountEl.dataset)
+app.mount(mountEl)
app.config.errorHandler = (error, _vm, info) => {
// vue fatals on errors by default, which is a weird choice
diff --git a/src/pretix/plugins/wallet/styles.py b/src/pretix/plugins/wallet/styles.py
index c851281cba..a4329b2cc3 100644
--- a/src/pretix/plugins/wallet/styles.py
+++ b/src/pretix/plugins/wallet/styles.py
@@ -4,6 +4,8 @@ import enum
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from i18nfield.strings import LazyI18nString
+
+from pretix.base.pdf import get_images, get_variables
from .models import WalletLayout
@@ -34,10 +36,10 @@ class PlaceholderFieldType(enum.Enum):
class PlaceholderField:
type: PlaceholderFieldType
label: LazyI18nString
- value: str
+ content: str
def asdict(self):
- return {"type": self.type.value, "label": self.label.data, "value": self.value}
+ return {"type": self.type.value, "label": self.label.data, "value": self.content}
@dataclass
@@ -178,10 +180,12 @@ class PassLayout:
self.style = style
self.layout = layout
- def validate(self):
- self.validate_fields()
+ def validate(self, event):
+ self.validate_fields(event)
+
+ def validate_fields(self, event):
+ placeholders = {"text": get_variables(event), "image": get_images(event)}
- def validate_fields(self):
style_fields = self.style.fields
if "fields" not in self.layout:
raise ValidationError(_("Layout did not contain any fields"))
@@ -192,10 +196,16 @@ class PassLayout:
for fieldgroup in style_fields:
layout_field_data = layout_fields.get(fieldgroup.identifier, {})
if fieldgroup.min_entries and fieldgroup.min_entries < len(
- layout_field_data.get('entries')
+ layout_field_data.get('entries', [])
):
raise ValidationError(
_("At least {min_entries} must be specified for {name}").format(
min_entries=fieldgroup.min_entries, name=fieldgroup.name
)
)
+ # TODO: move field validation to json schema
+ for entry in layout_field_data.get('entries', []):
+ if entry['type'] not in ('placeholder', fieldgroup.entry_type.value):
+ raise ValidationError(_("Placeholder of wrong type \"{type}\" in {name}").format(type=entry['type'], name="fieldgroup.name"))
+ if entry['type'] == 'placeholder' and entry['content'] not in placeholders[fieldgroup.entry_type.value]:
+ raise ValidationError(_("Unknown placeholder {name}").format(name=entry['content']))
\ No newline at end of file
diff --git a/src/pretix/plugins/wallet/templates/pretixplugins/wallet/edit.html b/src/pretix/plugins/wallet/templates/pretixplugins/wallet/edit.html
index e0d869cbb0..a2b79b7a81 100644
--- a/src/pretix/plugins/wallet/templates/pretixplugins/wallet/edit.html
+++ b/src/pretix/plugins/wallet/templates/pretixplugins/wallet/edit.html
@@ -10,13 +10,14 @@
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
{% block content %}
- {% trans "Edit layout" %}
+ {% trans "Edit layout" %} {{ object.name }}
{{ styles|json_script:"styles" }}
{{ variables|json_script:"variables" }}
{{ form.errors|json_script:"form_errors" }}
+ {{ layout|json_script:"layout" }}
{% vite_hmr %}
{% vite_asset "src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts" %}
diff --git a/src/pretix/plugins/wallet/views.py b/src/pretix/plugins/wallet/views.py
index 596c4f4248..cb346289b5 100644
--- a/src/pretix/plugins/wallet/views.py
+++ b/src/pretix/plugins/wallet/views.py
@@ -4,7 +4,7 @@ from django.http import Http404
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import FormView, ListView, CreateView, UpdateView
-from pretix.base.pdf import get_variables
+from pretix.base.pdf import get_images, get_variables
from pretix.control.permissions import EventPermissionRequiredMixin
from .styles import PassLayout, get_platform_styles, get_platforms
from .models import WalletLayout
@@ -14,7 +14,9 @@ from django import forms
from django.core.exceptions import ValidationError
from i18nfield.fields import I18nCharField
from i18nfield.forms import I18nModelForm
-
+from pretix.api.serializers.i18n import I18nAwareModelSerializer
+from rest_framework import serializers
+from rest_framework.renderers import JSONRenderer
# TODO: should this even be a list view?
class LayoutListView(EventPermissionRequiredMixin, ListView):
model = WalletLayout
@@ -33,7 +35,7 @@ class LayoutListView(EventPermissionRequiredMixin, ListView):
class LayoutEditForm(forms.ModelForm):
- style = forms.TypedChoiceField()
+ style = forms.ChoiceField()
def __init__(self, **kwargs):
self.platform = kwargs.pop('platform')
@@ -43,14 +45,14 @@ class LayoutEditForm(forms.ModelForm):
model = WalletLayout
fields = ("name","style","layout")
- def __init__(self, platform, **kwargs):
+ def __init__(self, event, platform, **kwargs):
super().__init__(**kwargs)
+ self.event = event
self.platform = platform
self.platform_styles = get_platform_styles(platform)
self.fields["style"].choices = [
(id, style.name) for id, style in self.platform_styles.items()
]
- self.fields["style"].coerce = self.coerce_style
def coerce_style(self, value):
return self.platform_styles[value]
@@ -65,11 +67,28 @@ class LayoutEditForm(forms.ModelForm):
def clean(self):
if "style" in self.cleaned_data and "layout" in self.cleaned_data:
layout = PassLayout(
- style=self.cleaned_data["style"], layout=self.cleaned_data["layout"]
+ style=self.coerce_style(self.cleaned_data["style"]), layout=self.cleaned_data["layout"]
)
- layout.validate()
+ layout.validate(self.event)
return self.cleaned_data
+class LayoutSerializer(I18nAwareModelSerializer):
+ # # TODO: only necessary if we save through this serializer
+ # style = serializers.ChoiceField(choices={})
+
+ # def __init__(self, *args, platform, **kwargs):
+ # super().__init__(*args, **kwargs)
+ # self.platform = platform
+ # self.platform_styles = get_platform_styles(platform)
+ # self.fields["style"].choices = [
+ # (id, style.name) for id, style in self.platform_styles.items()
+ # ]
+
+
+ class Meta:
+ model = WalletLayout
+ fields = ("name","style","layout")
+
class LayoutEditorView(EventPermissionRequiredMixin, UpdateView):
template_name = "pretixplugins/wallet/edit.html"
form_class = LayoutEditForm
@@ -83,6 +102,7 @@ class LayoutEditorView(EventPermissionRequiredMixin, UpdateView):
def get_form_kwargs(self) -> dict[str, Any]:
kwargs = super().get_form_kwargs()
+ kwargs['event'] = self.request.event
kwargs["platform"] = self.platform
return kwargs
@@ -98,11 +118,25 @@ class LayoutEditorView(EventPermissionRequiredMixin, UpdateView):
context["styles"] = {
id: style.asdict() for id, style in self.get_platform_styles().items()
}
+ if self.request.method == "POST":
+ form = self.get_form()
+ if not form.is_valid():
+ layout_data = LayoutSerializer(self.object).data
+ layout_data.update(form.cleaned_data)
+ context['layout'] = layout_data
+ else:
+ context["layout"] = LayoutSerializer(self.object).data
+ else:
+ context["layout"] = LayoutSerializer(self.object).data
+
context["variables"] = {
"text": {
varname: {"label": var["label"], "editor_sample": var["editor_sample"]}
for varname, var in get_variables(self.request.event).items()
- }
+ },
+ "image": {
+ varname: {"label": var['label']} for varname, var in get_images(self.request.event).items()
+ } | {"poweredby": {"label": _("pretix-Logo")}} # TODO: image upload
}
return context