mirror of
https://github.com/pretix/pretix.git
synced 2026-05-18 17:24:03 +00:00
WIP
This commit is contained in:
0
src/pretix/plugins/wallet/serializer.py
Normal file
0
src/pretix/plugins/wallet/serializer.py
Normal file
@@ -1,37 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import StyleSettings from './style-settings.vue'
|
||||
import Select from './input/select.vue'
|
||||
import Input from './input/input.vue'
|
||||
|
||||
const STYLES = JSON.parse(document.querySelector('#styles')?.textContent ?? '{}')
|
||||
const VARIABLES = JSON.parse(document.querySelector('#variables')?.textContent ?? '{}')
|
||||
const FORM_ERRORS = JSON.parse(document.querySelector('#form_errors')?.textContent ?? '{}')
|
||||
const FORM_DATA = JSON.parse(document.querySelector('#form_data')?.textContent ?? '{}')
|
||||
const gettext = (window as any).gettext
|
||||
|
||||
const style = ref<string | null>(FORM_DATA.style ?? null)
|
||||
const name = ref<string>(FORM_DATA.name ?? '')
|
||||
const layout = ref(JSON.parse(FORM_DATA.layout ?? '{}') ?? {})
|
||||
// TODO: Move to store?
|
||||
const STYLES: Styles = JSON.parse(document.querySelector('#styles')?.textContent ?? '{}')
|
||||
const VARIABLES: VariableConfig = JSON.parse(document.querySelector('#variables')?.textContent ?? '{}')
|
||||
const FORM_ERRORS: Record<string, Array<string>> = JSON.parse(document.querySelector('#form_errors')?.textContent ?? '{}')
|
||||
const LAYOUT: Layout = JSON.parse(document.querySelector('#layout')?.textContent ?? '{}')
|
||||
|
||||
const name = ref<string>(LAYOUT.name ?? '')
|
||||
const style = ref<string | null>(LAYOUT.style ?? null)
|
||||
const layout = ref<LayoutData>(LAYOUT.layout ?? {fields: {}})
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// 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
|
||||
|
||||
@@ -1,55 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import OptionalSelect from './optional-select.vue'
|
||||
import { computed, reactive } from 'vue'
|
||||
import Select from './input/select.vue'
|
||||
import Input from './input/input.vue'
|
||||
import TextContent from './text-content.vue'
|
||||
|
||||
const gettext = (window as any).gettext
|
||||
|
||||
const props = defineProps<{
|
||||
field: any // TODO
|
||||
overflows: [string, string][],
|
||||
variables: any // TODO
|
||||
field: FieldGroupDefinition
|
||||
overflows: FieldGroupDefinition[],
|
||||
variables: Variables
|
||||
}>()
|
||||
const fieldConfig = defineModel<any>({ required: true })
|
||||
const fieldConfig = defineModel<FieldConfig>({ required: true })
|
||||
|
||||
const overflowOptions = computed(() => {
|
||||
const overflowOptions = computed((): Array<[string|null, string]> => {
|
||||
if (props.overflows.length) {
|
||||
return [...props.overflows.map(x => [x.identifier, x.name]), [null, "Do not overflow"]]
|
||||
return [...props.overflows.map((x): [string, string] => [x.identifier, x.name]), [null, "Do not overflow"]]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function addVariable() {
|
||||
fieldConfig.value.entries.push({"label": null, "value": null})
|
||||
fieldConfig.value.entries.push({"type": "placeholder", "label": null, "content": null})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
//- 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")
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { useId } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string,
|
||||
errors?: string[],
|
||||
}>()
|
||||
const modelValue = defineModel<string|null>();
|
||||
const id = useId()
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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 }}
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useId, watchEffect } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
choices: Array<[string, string]>
|
||||
errors?: string[]
|
||||
}>()
|
||||
const modelValue = defineModel<string|null>();
|
||||
const id = useId()
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.choices.length === 1) {
|
||||
modelValue.value = props.choices[0][0]
|
||||
} else if (props.choices.length < 1) {
|
||||
modelValue.value = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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 }}
|
||||
</template>
|
||||
@@ -1,18 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useId } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
choices: [string, string][]
|
||||
}>()
|
||||
const modelValue = defineModel<string|null>();
|
||||
const id = useId()
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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] }}
|
||||
</template>
|
||||
@@ -1,45 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, watchEffect } from 'vue'
|
||||
import OptionalSelect from './optional-select.vue'
|
||||
import FieldSettings from './field-settings.vue'
|
||||
import { computed, watchEffect } from "vue";
|
||||
import FieldSettings from "./field-settings.vue";
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
styles: any, // TODO
|
||||
variables: any, // TODO
|
||||
style?: string,
|
||||
}>()
|
||||
styles: Styles;
|
||||
variables: VariableConfig
|
||||
style?: string;
|
||||
}>();
|
||||
|
||||
const layout = defineModel()
|
||||
const layout = defineModel<LayoutData>();
|
||||
|
||||
const styleData = computed(() => {
|
||||
if (!props.style || !(props.style in props.styles)) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
return props.styles[props.style]
|
||||
})
|
||||
return props.styles[props.style];
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
// TODO: this seems wrooong
|
||||
if (!('fields' in layout.value)) {
|
||||
layout.value.fields = {}
|
||||
}
|
||||
if (!("fields" in layout.value)) {
|
||||
layout.value.fields = {};
|
||||
}
|
||||
if (props.style) {
|
||||
for (const field of props.styles[props.style].fields) {
|
||||
if (!(field.identifier in layout.value.fields)) {
|
||||
layout.value.fields[field.identifier] = {entries: JSON.parse(JSON.stringify(field.default_entries)), overflow: null}
|
||||
layout.value.fields[field.identifier] = {
|
||||
entries: JSON.parse(JSON.stringify(field.default_entries)),
|
||||
overflow: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
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]"
|
||||
)
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue'
|
||||
import Select from './input/select.vue'
|
||||
import Input from './input/input.vue'
|
||||
|
||||
const gettext = (window as any).gettext
|
||||
|
||||
const props = defineProps<{
|
||||
variables: Variables
|
||||
}>()
|
||||
const entry = defineModel<FieldEntry>({ required: true })
|
||||
|
||||
const selectChoices = computed(() =>{
|
||||
const choices = Object.entries(props.variables).map(([k,v]): [string, string] => [k, v.label])
|
||||
choices.push(["other", gettext("Other…")])
|
||||
return choices
|
||||
});
|
||||
|
||||
const selection = computed({
|
||||
get() {
|
||||
if (entry.value.type === 'placeholder') {
|
||||
return entry.value.content
|
||||
} else if (entry.value.type === 'text') {
|
||||
return "other"
|
||||
} else {
|
||||
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
||||
}
|
||||
},
|
||||
set(newValue) {
|
||||
if (newValue == "other") {
|
||||
entry.value.type = "text"
|
||||
entry.value.content = "";
|
||||
} else {
|
||||
entry.value.type = "placeholder"
|
||||
entry.value.content = newValue
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const textContent = computed({
|
||||
get() {
|
||||
if (entry.value.type === 'placeholder') {
|
||||
return ""
|
||||
} else if (entry.value.type === 'text') {
|
||||
return entry.value.content
|
||||
} else {
|
||||
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
||||
}
|
||||
},
|
||||
set(newValue) {
|
||||
entry.value.content = newValue
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
Select(:label="gettext('Content')"
|
||||
v-model="selection"
|
||||
:choices="selectChoices"
|
||||
)
|
||||
Input(v-model="textContent" v-if="selection === 'other'")
|
||||
</template>
|
||||
41
src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
vendored
Normal file
41
src/pretix/plugins/wallet/static/pretixplugins/wallet/index.d.ts
vendored
Normal file
@@ -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<string, Style>;
|
||||
type Variables = Record<string, Variable>;
|
||||
type VariableConfig = Record<string, Variables>;
|
||||
|
||||
type FieldEntry = {
|
||||
type: 'placeholder' | 'text';
|
||||
label: string; // TODO i18n
|
||||
content: string;
|
||||
}
|
||||
|
||||
type FieldConfig = {
|
||||
entries: Array<FieldEntry>;
|
||||
overflow: string | null;
|
||||
};
|
||||
|
||||
type LayoutData = {
|
||||
fields?: Record<string, FieldConfig>;
|
||||
};
|
||||
|
||||
type Layout = {
|
||||
name?: string;
|
||||
style?: string;
|
||||
layout?: LayoutData;
|
||||
};
|
||||
@@ -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<HTMLElement>('#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
|
||||
|
||||
@@ -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']))
|
||||
@@ -10,13 +10,14 @@
|
||||
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Edit layout" %}</h1>
|
||||
<h1>{% trans "Edit layout" %} {{ object.name }} </h1>
|
||||
{{ styles|json_script:"styles" }}
|
||||
{{ variables|json_script:"variables" }}
|
||||
{{ form.errors|json_script:"form_errors" }}
|
||||
{{ layout|json_script:"layout" }}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div id="editor"></div>
|
||||
<div id="editor">This is a test</div>
|
||||
</form>
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts" %}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user