This commit is contained in:
Kara Engelhardt
2026-03-20 12:46:48 +01:00
parent cfcd0f4206
commit a521956aca
7 changed files with 179 additions and 79 deletions

View File

@@ -4,7 +4,23 @@ import enum
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from .models import WalletLayout
class WalletPlatform:
identifier: str
name: str
def get_layout_qs(self):
return WalletLayout.objects.filter(platform=self.identifier)
class ApplePlatform(WalletPlatform):
identifier = "apple"
name = _("Apple")
class GooglePlatform(WalletPlatform):
identifier = "apple"
name = _("Google")
class PlaceholderFieldType(enum.Enum): class PlaceholderFieldType(enum.Enum):
TEXT = "text" TEXT = "text"
CODE = "qr" CODE = "qr"
@@ -108,6 +124,7 @@ class GoogleWalletEventTicket(PassStyle):
] ]
AVAILABLE_PLATFORMS = {"apple": ApplePlatform, "google": GooglePlatform}
AVAILABLE_STYLES = [AppleWalletEventTicket(), GoogleWalletEventTicket()] AVAILABLE_STYLES = [AppleWalletEventTicket(), GoogleWalletEventTicket()]
def get_platforms_with_styles(): def get_platforms_with_styles():
@@ -127,7 +144,7 @@ def get_platform_styles(platform):
return platform_styles return platform_styles
def get_platforms(): def get_platforms():
return sorted(set(style.platform for style in AVAILABLE_STYLES)) return AVAILABLE_PLATFORMS
class PassLayout: class PassLayout:
style: PassStyle style: PassStyle

View File

@@ -1,6 +1,8 @@
{% extends "pretixcontrol/event/base.html" %} {% extends "pretixcontrol/event/base.html" %}
{% load i18n %} {% load i18n %}
{% load money %} {% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Wallet layouts" %}{% endblock %} {% block title %}{% trans "Wallet layouts" %}{% endblock %}
{% block content %} {% block content %}
<pre><code>{{ styles }}</code></pre> <pre><code>{{ styles }}</code></pre>
@@ -9,7 +11,7 @@
{{ variables|json_script:"variables" }} {{ variables|json_script:"variables" }}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{form}} {% bootstrap_form form %}
<button type="submit" class="btn btn-default"> <button type="submit" class="btn btn-default">
{% trans "Submit " %} {% trans "Submit " %}
</button> </button>

View File

@@ -5,62 +5,76 @@
{% block content %} {% block content %}
<h1>{% trans "Wallet layouts" %}</h1> <h1>{% trans "Wallet layouts" %}</h1>
<div class="tabbed-form"> <div class="tabbed-form">
{% for platform in platforms %} {% for platform in platforms.values %}
<fieldset> <fieldset>
<legend>{{platform}}</legend> <legend>{{platform.name}}</legend>
{% if platforms.get_layout_qs|length == 0 %}
<div class="empty-collection">
<p>
{% blocktrans trimmed %}
You haven't created any layouts yet.
{% endblocktrans %}
</p>
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:wallet:add" organizer=request.event.organizer.slug event=request.event.slug platform=platform.identifier %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new layout" %}
</a>
{% endif %}
</div>
{% else %}
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Default" %}</th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody>
{% for l in platforms.get_layout_qs %}
<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 %}">
{{ l.name }}
</a></strong>
{% else %}
<strong>{{ l.name }}</strong>
{% endif %}
</td>
<td>
{% if l.default %}
<span class="text-success">
<span class="fa fa-check"></span>
{% trans "Default" %}
</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 %}">
{% csrf_token %}
<button class="btn btn-default btn-sm">
{% trans "Make default" %}
</button>
</form>
{% endif %}
</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 }}"
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>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</fieldset> </fieldset>
{% endfor %} {% endfor %}
</div> </div>
{% comment %} <div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Default" %}</th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody>
{% for l in layouts %}
<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 %}">
{{ l.name }}
</a></strong>
{% else %}
<strong>{{ l.name }}</strong>
{% endif %}
</td>
<td>
{% if l.default %}
<span class="text-success">
<span class="fa fa-check"></span>
{% trans "Default" %}
</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 %}">
{% csrf_token %}
<button class="btn btn-default btn-sm">
{% trans "Make default" %}
</button>
</form>
{% endif %}
</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 }}"
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>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endcomment %}
{% include "pretixcontrol/pagination.html" %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,8 @@
{% load i18n %}
<p>
<a class="btn btn-primary btn-lg" target="_blank"
href="{% url "plugins:wallet:index" organizer=request.organizer.slug event=request.event.slug %}">
<span class="fa fa-paint-brush"></span>
{% trans "Edit layouts" %}
</a>
</p>

View File

@@ -24,34 +24,48 @@ from django.utils.translation import gettext_lazy as _
from pretix.base.ticketoutput import BaseTicketOutput from pretix.base.ticketoutput import BaseTicketOutput
from pretix.base.models import Event from pretix.base.models import Event
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from django.template.loader import render_to_string
logger = logging.getLogger('pretix.plugins.wallet') logger = logging.getLogger("pretix.plugins.wallet")
class WalletSettingsHolder(BaseTicketOutput): class WalletSettingsHolder(BaseTicketOutput):
identifier = 'wallet' identifier = "wallet"
verbose_name = _('Wallet Output') verbose_name = _("Wallet Output")
is_meta = True is_meta = True
is_enabled = False is_enabled = False
preview_allowed = False # TODO: implement own preview view or hide button for meta-outputs preview_allowed = (
False # TODO: implement own preview view or hide button for meta-outputs
)
def settings_content_render(self, request) -> str:
return render_to_string(
"pretixplugins/wallet/settings_content.html", {"request": request}
)
class WalletOutput(BaseTicketOutput): class WalletOutput(BaseTicketOutput):
settings_form_fields = [] settings_form_fields = []
def __init__(self, event: Event): def __init__(self, event: Event):
super().__init__(event) super().__init__(event)
self.settings = SettingsSandbox('ticketoutput', WalletSettingsHolder.identifier, event) self.settings = SettingsSandbox(
"ticketoutput", WalletSettingsHolder.identifier, event
)
class GoogleWalletTicketOutput(WalletOutput): class GoogleWalletTicketOutput(WalletOutput):
identifier = 'wallet_google' identifier = "wallet_google"
verbose_name = _('Google') verbose_name = _("Google")
download_button_text = "Add to Google Wallet" download_button_text = "Add to Google Wallet"
class AppleWalletTicketOutput(WalletOutput): class AppleWalletTicketOutput(WalletOutput):
identifier = 'wallet_apple' identifier = "wallet_apple"
verbose_name = _('Apple') verbose_name = _("Apple")
download_button_text = "Add to Apple Wallet" download_button_text = "Add to Apple Wallet"
OUTPUTS = [WalletSettingsHolder, GoogleWalletTicketOutput, AppleWalletTicketOutput] OUTPUTS = [WalletSettingsHolder, GoogleWalletTicketOutput, AppleWalletTicketOutput]

View File

@@ -22,7 +22,8 @@
from django.urls import re_path from django.urls import re_path
from .views import ( from .views import (
EditorView, LayoutEditorView,
LayoutCreateView,
LayoutListView LayoutListView
) )
@@ -30,7 +31,7 @@ urlpatterns = [
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/$', re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/$',
LayoutListView.as_view(), name='index'), 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/edit/(?P<platform>[^/]+)/$',
EditorView.as_view(), name='edit'), 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<platform>[^/]+)/(?P<layout>[^/]+)/$',
EditorView.as_view(), name='edit'), LayoutEditorView.as_view(), name='edit'),
] ]

View File

@@ -3,7 +3,7 @@ from typing import Any
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic import FormView, ListView, TemplateView from django.views.generic import FormView, ListView, CreateView, UpdateView
from pretix.base.pdf import get_variables from pretix.base.pdf import get_variables
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
from .styles import PassLayout, get_platform_styles, get_platforms from .styles import PassLayout, get_platform_styles, get_platforms
@@ -12,8 +12,10 @@ import json
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from i18nfield.fields import I18nCharField
from i18nfield.forms import I18nModelForm
# TODO: should this even be a list view?
class LayoutListView(EventPermissionRequiredMixin, ListView): class LayoutListView(EventPermissionRequiredMixin, ListView):
model = WalletLayout model = WalletLayout
permission = "can_change_event_settings" permission = "can_change_event_settings"
@@ -29,11 +31,19 @@ class LayoutListView(EventPermissionRequiredMixin, ListView):
return ctx return ctx
class EditorForm(forms.Form):
name = forms.CharField() class LayoutEditForm(forms.ModelForm):
style = forms.TypedChoiceField() style = forms.TypedChoiceField()
layout = forms.JSONField(initial={}) layout = forms.JSONField(initial={})
def __init__(self, **kwargs):
self.platform = kwargs.pop('platform')
super().__init__(**kwargs)
class Meta:
model = WalletLayout
fields = ("name","style","layout")
def __init__(self, platform, **kwargs): def __init__(self, platform, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.platform = platform self.platform = platform
@@ -60,13 +70,12 @@ class EditorForm(forms.Form):
) )
layout.validate() layout.validate()
return self.cleaned_data return self.cleaned_data
class LayoutCreateView(EventPermissionRequiredMixin, FormView):
class EditorView(EventPermissionRequiredMixin, FormView):
template_name = "pretixplugins/wallet/edit.html" template_name = "pretixplugins/wallet/edit.html"
form_class = EditorForm form_class = LayoutEditForm
success_url = "" model = WalletLayout
permission = "can_change_event_settings" permission = "can_change_event_settings" # TODO: new permission name
@property @property
def platform(self): def platform(self):
@@ -116,3 +125,38 @@ class EditorView(EventPermissionRequiredMixin, FormView):
}, },
) )
) )
class LayoutEditorView(EventPermissionRequiredMixin, UpdateView):
template_name = "pretixplugins/wallet/edit.html"
form_class = LayoutEditForm
success_url = ""
permission = "can_change_event_settings"
@property
def platform(self):
return self.kwargs["platform"]
def get_form_kwargs(self) -> dict[str, Any]:
kwargs = super().get_form_kwargs()
kwargs["platform"] = self.platform
return kwargs
def get_platform_styles(self):
if self.platform not in get_platforms():
raise Http404(
_("Unknown platform '{platform}'").format(platform=self.platform)
)
return get_platform_styles(self.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()
}
context["variables"] = {
"text": {
varname: {"label": var["label"], "editor_sample": var["editor_sample"]}
for varname, var in get_variables(self.request.event).items()
}
}
return context