forked from CGM_Public/pretix_original
WIP: use api
This commit is contained in:
66
src/pretix/plugins/wallet/api.py
Normal file
66
src/pretix/plugins/wallet/api.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from rest_framework import viewsets
|
||||
from django.db import transaction
|
||||
from .styles import PassLayout, get_platform_styles, get_platforms
|
||||
from .models import WalletLayout
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
import django_filters.rest_framework
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class WalletLayoutSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = WalletLayout
|
||||
fields = ("event","platform","name","style","layout")
|
||||
read_only_fields = ("event", "platform")
|
||||
|
||||
def validate_layout(self, value):
|
||||
if not isinstance(value, dict):
|
||||
raise ValidationError(_("Layout must be a dict"))
|
||||
return value
|
||||
|
||||
|
||||
def validate_platform(self, value):
|
||||
if value not in get_platforms():
|
||||
raise ValidationError(_("Invalid Platform"))
|
||||
return value
|
||||
|
||||
def validate(self, data):
|
||||
if "style" in data and "layout" in data and "platform" in data:
|
||||
platform_styles = get_platform_styles(data['platform'])
|
||||
if data['style'] not in platform_styles:
|
||||
raise ValidationError(_("Invalid style"))
|
||||
style = get_platform_styles(data['platform'])[data['style']]
|
||||
|
||||
layout = PassLayout(
|
||||
style=style, layout=data["layout"]
|
||||
)
|
||||
breakpoint()
|
||||
layout.validate(data['event'])
|
||||
return data
|
||||
|
||||
|
||||
|
||||
class WalletLayoutViewSet(viewsets.ModelViewSet):
|
||||
model = WalletLayout
|
||||
queryset = WalletLayout.objects.none()
|
||||
serializer_class = WalletLayoutSerializer
|
||||
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
|
||||
filterset_fields = ['platform']
|
||||
permission = "event.settings.general:write"
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.wallet_layouts.all()
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
return super().get_serializer(*args, **kwargs)
|
||||
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
super().perform_update(serializer)
|
||||
serializer.instance.log_action(
|
||||
action='pretix.plugins.wallet.layout.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
@@ -37,7 +37,7 @@ class WalletLayout(LoggedModel):
|
||||
)
|
||||
platform = models.CharField(max_length=10)
|
||||
style = models.CharField(max_length=255)
|
||||
layout = models.JSONField()
|
||||
layout = models.JSONField(default={})
|
||||
|
||||
class Meta:
|
||||
ordering = ("name",)
|
||||
|
||||
@@ -1,46 +1,86 @@
|
||||
<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'
|
||||
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 gettext = (window as any).gettext;
|
||||
|
||||
// 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 isLoading = ref<boolean>(true);
|
||||
const wallet_layout = ref<Layout | null>(null);
|
||||
|
||||
const name = ref<string>(LAYOUT.name ?? '')
|
||||
const style = ref<string | null>(LAYOUT.style ?? null)
|
||||
const layout = ref<LayoutData>(LAYOUT.layout ?? {fields: {}})
|
||||
const STYLES: Styles = JSON.parse(
|
||||
document.querySelector("#styles")?.textContent ?? "{}",
|
||||
);
|
||||
const VARIABLES: VariableConfig = JSON.parse(
|
||||
document.querySelector("#variables")?.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())
|
||||
.then((x) => {
|
||||
wallet_layout.value = x;
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// TODO: add :key for all `v-for`s
|
||||
// TODO: i18n
|
||||
details
|
||||
pre
|
||||
code {{ FORM_ERRORS }}
|
||||
.row
|
||||
.col-md-8
|
||||
// TODO: show error text
|
||||
.form-group(:class='"name" in FORM_ERRORS ? "has-error" : ""')
|
||||
Input(label="Name" v-model="name" name="name" :errors="FORM_ERRORS['name']")
|
||||
// TODO: i18n textfields
|
||||
// TODO: proper spinner
|
||||
template(v-if="isLoading") {{ gettext("Loading...") }}
|
||||
form(v-else @submit="saveLayout")
|
||||
.row
|
||||
.col-md-8
|
||||
.form-group()
|
||||
Input(label="Name" v-model="wallet_layout.name")
|
||||
|
||||
.form-group(:class='"style" in FORM_ERRORS ? "has-error" : ""')
|
||||
Select(label="Style" v-model="style" :choices="Object.values(STYLES).map(x => [x.identifier, x.name])" name="style" :errors="FORM_ERRORS['style']")
|
||||
.form-group()
|
||||
Select(label="Style" v-model="wallet_layout.style" :choices="Object.values(STYLES).map(x => [x.identifier, x.name])")
|
||||
|
||||
StyleSettings(v-if="style" v-model="layout" :style="style" :styles="STYLES" :variables="VARIABLES")
|
||||
.col-md-4
|
||||
.panel.panel-default
|
||||
.panel-heading Preview
|
||||
.panel-body
|
||||
// TODO: Preview
|
||||
pre
|
||||
code {{ layout }}
|
||||
input(type="hidden" name="layout" :value="JSON.stringify(layout)")
|
||||
.form-group.submit-group
|
||||
button.btn.btn-primary.btn-save(type="submit") Submit
|
||||
StyleSettings(v-if="wallet_layout.style" v-model="wallet_layout.layout" :style="STYLES[wallet_layout.style]" :variables="VARIABLES")
|
||||
.col-md-4
|
||||
.panel.panel-default
|
||||
.panel-heading Preview
|
||||
.panel-body
|
||||
// TODO: Preview
|
||||
pre
|
||||
code {{ wallet_layout }}
|
||||
.form-group.submit-group
|
||||
button.btn.btn-primary.btn-save(type="submit") Submit
|
||||
</template>
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue'
|
||||
import Select from './input/select.vue'
|
||||
import Input from './input/input.vue'
|
||||
import TextContent from './text-content.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 gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
field: FieldGroupDefinition
|
||||
overflows: FieldGroupDefinition[],
|
||||
variables: Variables
|
||||
}>()
|
||||
const fieldConfig = defineModel<FieldConfig>({ required: true })
|
||||
field: FieldGroupDefinition;
|
||||
overflows: FieldGroupDefinition[];
|
||||
variables: Variables;
|
||||
}>();
|
||||
const fieldConfig = defineModel<FieldConfig>({ required: true });
|
||||
|
||||
const overflowOptions = computed((): Array<[string|null, string]> => {
|
||||
const overflowOptions = computed((): Array<[string | null, string]> => {
|
||||
if (props.overflows.length) {
|
||||
return [...props.overflows.map((x): [string, string] => [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 []
|
||||
return [];
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
function addVariable() {
|
||||
fieldConfig.value.entries.push({"type": "placeholder", "label": null, "content": null})
|
||||
fieldConfig.value.entries.push({ type: "placeholder" });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
|
||||
@@ -5,27 +5,22 @@ import FieldSettings from "./field-settings.vue";
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
styles: Styles;
|
||||
variables: VariableConfig
|
||||
style?: string;
|
||||
style?: Style;
|
||||
}>();
|
||||
|
||||
const layout = defineModel<LayoutData>();
|
||||
|
||||
const styleData = computed(() => {
|
||||
if (!props.style || !(props.style in props.styles)) {
|
||||
return null;
|
||||
}
|
||||
return props.styles[props.style];
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
// TODO: this seems wrooong
|
||||
if (!("fields" in layout.value)) {
|
||||
if (layout.value === undefined) {
|
||||
return
|
||||
}
|
||||
if (layout.value.fields === undefined) {
|
||||
layout.value.fields = {};
|
||||
}
|
||||
if (props.style) {
|
||||
for (const field of props.styles[props.style].fields) {
|
||||
for (const field of props.style.fields) {
|
||||
if (!(field.identifier in layout.value.fields)) {
|
||||
layout.value.fields[field.identifier] = {
|
||||
entries: JSON.parse(JSON.stringify(field.default_entries)),
|
||||
@@ -39,11 +34,11 @@ watchEffect(() => {
|
||||
|
||||
<template lang="pug">
|
||||
h2.h3 {{ gettext("Field Groups") }}
|
||||
FieldSettings(v-if="styleData"
|
||||
v-for="(field, fieldId) in styleData.fields"
|
||||
FieldSettings(v-if="props.style"
|
||||
v-for="(field, fieldId) in props.style.fields"
|
||||
v-model="layout.fields[field.identifier]"
|
||||
:field="field"
|
||||
:overflows="styleData.fields.slice(fieldId + 1).filter(x => x.entry_type === field.entry_type)"
|
||||
:overflows="props.style.fields.slice(fieldId + 1).filter(x => x.entry_type === field.entry_type)"
|
||||
:variables="variables[field.entry_type]"
|
||||
)
|
||||
</template>
|
||||
|
||||
@@ -19,10 +19,12 @@ type Styles = Record<string, Style>;
|
||||
type Variables = Record<string, Variable>;
|
||||
type VariableConfig = Record<string, Variables>;
|
||||
|
||||
type I18nString = string | Record<string, string>
|
||||
|
||||
type FieldEntry = {
|
||||
type: 'placeholder' | 'text';
|
||||
label: string; // TODO i18n
|
||||
content: string;
|
||||
label?: I18nString; // TODO i18n
|
||||
content?: string;
|
||||
}
|
||||
|
||||
type FieldConfig = {
|
||||
|
||||
@@ -24,29 +24,36 @@ class GooglePlatform(WalletPlatform):
|
||||
name = _("Google")
|
||||
|
||||
|
||||
class PlaceholderFieldType(enum.Enum):
|
||||
class FieldType(enum.Enum):
|
||||
TEXT = "text"
|
||||
CODE = "qr"
|
||||
IMAGE = "image"
|
||||
PREDEFINED = "predefined"
|
||||
# TODO: POWERED_BY ?
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaceholderField:
|
||||
type: PlaceholderFieldType
|
||||
class BaseField:
|
||||
type: str
|
||||
label: LazyI18nString
|
||||
content: str
|
||||
|
||||
def asdict(self):
|
||||
return {"type": self.type.value, "label": self.label.data, "value": self.content}
|
||||
def __init__(self, label: LazyI18nString, content: str):
|
||||
self.label = label
|
||||
self.content = content
|
||||
|
||||
def asdict(self):
|
||||
return {"type": self.type, "label": self.label.data, "content": self.content}
|
||||
|
||||
class PlaceholderField(BaseField):
|
||||
type = "placeholder"
|
||||
|
||||
class TextField(BaseField):
|
||||
type = "text"
|
||||
|
||||
@dataclass
|
||||
class FieldGroupDefinition:
|
||||
name: str
|
||||
identifier: str
|
||||
entry_type: PlaceholderFieldType
|
||||
entry_type: FieldType
|
||||
min_entries: int | None = None
|
||||
max_entries: int | None = None
|
||||
|
||||
@@ -62,8 +69,8 @@ class FieldGroupDefinition:
|
||||
|
||||
@dataclass
|
||||
class PlaceholderFieldGroup(FieldGroupDefinition):
|
||||
entry_type: PlaceholderFieldType = PlaceholderFieldType.TEXT
|
||||
default_entries: list[PlaceholderField] = field(default_factory=list)
|
||||
entry_type: FieldType = FieldType.TEXT
|
||||
default_entries: list[PlaceholderField | TextField] = field(default_factory=list) # TODO: TextField seems wrong here
|
||||
|
||||
def asdict(self):
|
||||
asdict = super().asdict()
|
||||
@@ -73,7 +80,7 @@ class PlaceholderFieldGroup(FieldGroupDefinition):
|
||||
|
||||
@dataclass
|
||||
class PredefinedFieldGroup(FieldGroupDefinition):
|
||||
entry_type: PlaceholderFieldType = PlaceholderFieldType.PREDEFINED
|
||||
entry_type: FieldType = FieldType.PREDEFINED
|
||||
min_entries = 0
|
||||
max_entries = 1
|
||||
|
||||
@@ -107,9 +114,9 @@ class AppleWalletEventTicket(PassStyle):
|
||||
min_entries=1,
|
||||
max_entries=1,
|
||||
default_entries=[
|
||||
PlaceholderField(PlaceholderFieldType.IMAGE, LazyI18nString("logo"), "event:image")
|
||||
PlaceholderField(LazyI18nString("logo"), "event:image")
|
||||
],
|
||||
entry_type=PlaceholderFieldType.IMAGE,
|
||||
entry_type=FieldType.IMAGE,
|
||||
),
|
||||
PlaceholderFieldGroup(
|
||||
identifier="primary",
|
||||
@@ -117,7 +124,7 @@ class AppleWalletEventTicket(PassStyle):
|
||||
min_entries=1,
|
||||
max_entries=1,
|
||||
default_entries=[
|
||||
PlaceholderField(PlaceholderFieldType.TEXT, LazyI18nString("Ticket type"), "item")
|
||||
PlaceholderField(LazyI18nString("Ticket type"), "item")
|
||||
],
|
||||
),
|
||||
PlaceholderFieldGroup(
|
||||
@@ -141,7 +148,7 @@ class GoogleWalletEventTicket(PassStyle):
|
||||
fields = [
|
||||
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
|
||||
PlaceholderFieldGroup(
|
||||
identifier="qrcode", name=_("QR-Code"), entry_type=PlaceholderFieldType.CODE
|
||||
identifier="qrcode", name=_("QR-Code"), entry_type=FieldType.CODE
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load money %}
|
||||
{% load bootstrap3 %}
|
||||
{% load vite %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
|
||||
|
||||
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "New layout" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">
|
||||
{% trans "Ticket design" %}
|
||||
</label>
|
||||
<div class="col-md-9 form-control-static">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can modify the design after you saved this page.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -11,14 +11,9 @@
|
||||
|
||||
{% block content %}
|
||||
<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">This is a test</div>
|
||||
</form>
|
||||
{{ styles|json_script:"styles" }}
|
||||
{{ variables|json_script:"variables" }}
|
||||
<div id="editor" data-layout-id="{{ object.pk }}"></div>
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts" %}
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<strong><a href="{% url "plugins:ticketoutputpdf:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
|
||||
<strong><a href="{% url "plugins:wallet:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
|
||||
{{ l.name }}
|
||||
</a></strong>
|
||||
{% else %}
|
||||
@@ -54,7 +54,7 @@
|
||||
</span>
|
||||
{% elif "can_change_event_settings" in request.eventpermset %}
|
||||
<form class="form-inline" method="post"
|
||||
action="{% url "plugins:ticketoutputpdf:default" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
|
||||
action="{% url "plugins:wallet:default" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default btn-sm">
|
||||
{% trans "Make default" %}
|
||||
@@ -64,10 +64,10 @@
|
||||
</td>
|
||||
<td class="text-right flip">
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "plugins:ticketoutputpdf:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "plugins:ticketoutputpdf:add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ l.id }}"
|
||||
<a href="{% url "plugins:wallet:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "plugins:wallet:add" organizer=request.event.organizer.slug event=request.event.slug platform=platform.identifier %}?copy_from={{ l.id }}"
|
||||
class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
|
||||
<a href="{% url "plugins:ticketoutputpdf:delete" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
<a href="{% url "plugins:wallet:delete" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -20,18 +20,26 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.urls import re_path
|
||||
from pretix.api.urls import event_router
|
||||
|
||||
from .views import (
|
||||
LayoutEditorView,
|
||||
LayoutCreateView,
|
||||
LayoutListView
|
||||
)
|
||||
from .api import WalletLayoutViewSet
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/$',
|
||||
LayoutListView.as_view(), name='index'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/edit/(?P<platform>[^/]+)/$',
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/add/(?P<platform>[^/]+)/$',
|
||||
LayoutCreateView.as_view(), name='add'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/edit/(?P<platform>[^/]+)/(?P<layout>[^/]+)/$',
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/edit/(?P<layout>[^/]+)/$',
|
||||
LayoutEditorView.as_view(), name='edit'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/default/(?P<layout>[^/]+)/$', # TODO
|
||||
LayoutEditorView.as_view(), name='default'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/delete/(?P<layout>[^/]+)/$', # TODO
|
||||
LayoutEditorView.as_view(), name='delete'),
|
||||
]
|
||||
|
||||
event_router.register('walletlayouts', WalletLayoutViewSet)
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from django import forms
|
||||
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 django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import (
|
||||
CreateView, DetailView, ListView
|
||||
)
|
||||
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
|
||||
import json
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
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
|
||||
from .styles import get_platform_styles, get_platforms
|
||||
|
||||
|
||||
# TODO: should this even be a list view?
|
||||
class LayoutListView(EventPermissionRequiredMixin, ListView):
|
||||
model = WalletLayout
|
||||
@@ -33,102 +31,24 @@ class LayoutListView(EventPermissionRequiredMixin, ListView):
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
class LayoutEditForm(forms.ModelForm):
|
||||
style = forms.ChoiceField()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.platform = kwargs.pop('platform')
|
||||
super().__init__(**kwargs)
|
||||
|
||||
class Meta:
|
||||
model = WalletLayout
|
||||
fields = ("name","style","layout")
|
||||
|
||||
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()
|
||||
]
|
||||
|
||||
def coerce_style(self, value):
|
||||
return self.platform_styles[value]
|
||||
|
||||
def clean_layout(self):
|
||||
layout = self.cleaned_data["layout"]
|
||||
|
||||
if not isinstance(layout, dict):
|
||||
raise ValidationError(_("Layout must be a dict"))
|
||||
return layout
|
||||
|
||||
def clean(self):
|
||||
if "style" in self.cleaned_data and "layout" in self.cleaned_data:
|
||||
layout = PassLayout(
|
||||
style=self.coerce_style(self.cleaned_data["style"]), layout=self.cleaned_data["layout"]
|
||||
)
|
||||
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):
|
||||
class LayoutEditorView(DetailView):
|
||||
template_name = "pretixplugins/wallet/edit.html"
|
||||
form_class = LayoutEditForm
|
||||
model = WalletLayout
|
||||
permission = "can_change_event_settings" # TODO: new permission name
|
||||
permission = "event.settings.general:write"
|
||||
pk_url_kwarg = "layout"
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
return self.kwargs["platform"]
|
||||
|
||||
def get_form_kwargs(self) -> dict[str, Any]:
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['event'] = self.request.event
|
||||
kwargs["platform"] = self.platform
|
||||
return kwargs
|
||||
|
||||
def get_platform_styles(self):
|
||||
if self.platform not in get_platforms():
|
||||
if self.object.platform not in get_platforms():
|
||||
raise Http404(
|
||||
_("Unknown platform '{platform}'").format(platform=self.platform)
|
||||
_("Unknown platform '{platform}'").format(platform=self.object.platform)
|
||||
)
|
||||
return get_platform_styles(self.platform)
|
||||
return get_platform_styles(self.object.platform)
|
||||
|
||||
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
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"]}
|
||||
@@ -140,18 +60,47 @@ class LayoutEditorView(EventPermissionRequiredMixin, UpdateView):
|
||||
}
|
||||
return context
|
||||
|
||||
class WalletLayoutCreateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = WalletLayout
|
||||
fields = ("name",)
|
||||
|
||||
def __init__(self, *args, platform, event, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.platform = platform
|
||||
self.event = event
|
||||
|
||||
def save(self, *args, **kwargs) -> Any:
|
||||
self.instance.platform = self.platform
|
||||
self.instance.event = self.event
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class LayoutCreateView(CreateView):
|
||||
template_name = "pretixplugins/wallet/create.html"
|
||||
form_class = WalletLayoutCreateForm
|
||||
permission = "event.settings.general:write"
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
platform = self.kwargs['platform']
|
||||
if platform not in get_platforms():
|
||||
raise Http404(
|
||||
_("Unknown platform '{platform}'").format(platform=platform)
|
||||
)
|
||||
return platform
|
||||
|
||||
def get_form_kwargs(self) -> dict[str, Any]:
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['platform'] = self.platform
|
||||
kwargs['event'] = self.request.event
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse(
|
||||
"plugins:wallet:edit",
|
||||
kwargs={
|
||||
"organizer": self.request.event.organizer.slug,
|
||||
"event": self.request.event.slug,
|
||||
"platform": self.platform,
|
||||
"layout": self.object.pk,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class LayoutCreateView(LayoutEditorView):
|
||||
def get_object(self, queryset=None):
|
||||
return WalletLayout(event=self.request.event, platform=self.platform)
|
||||
)
|
||||
Reference in New Issue
Block a user