mirror of
https://github.com/pretix/pretix.git
synced 2026-05-18 17:24:03 +00:00
WIP
This commit is contained in:
@@ -193,13 +193,15 @@ class BaseTicketOutput:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def show_settings(self) -> bool:
|
def is_meta(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns whether or not this output should be shown in the ticket settings.
|
Returns whether or whether not this output is a "meta" output that only works as a settings holder
|
||||||
|
and should never be used directly. This is a trick to implement outputs with multiple formats but
|
||||||
|
unified settings.
|
||||||
|
|
||||||
.. note:: If you set this to false, you need to have some other mechanism to decide whether this output is enabled
|
.. note:: You should set is_enabled to False for meta outputs.
|
||||||
"""
|
"""
|
||||||
return True
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def download_button_text(self) -> str:
|
def download_button_text(self) -> str:
|
||||||
|
|||||||
@@ -965,7 +965,7 @@ class TicketSettingsPreview(EventPermissionRequiredMixin, View):
|
|||||||
responses = register_ticket_outputs.send(self.request.event)
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
for receiver, response in responses:
|
for receiver, response in responses:
|
||||||
provider = response(self.request.event)
|
provider = response(self.request.event)
|
||||||
if provider.identifier == self.kwargs.get('output'):
|
if provider.identifier == self.kwargs.get('output') and not provider.is_meta:
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@@ -1068,7 +1068,9 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
|
|||||||
responses = register_ticket_outputs.send(self.request.event)
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
for receiver, response in responses:
|
for receiver, response in responses:
|
||||||
provider = response(self.request.event)
|
provider = response(self.request.event)
|
||||||
if not provider.show_settings:
|
provider_settings_fields = provider.settings_form_fields
|
||||||
|
provider_settings_content = provider.settings_content_render(self.request)
|
||||||
|
if not provider_settings_fields and not provider_settings_content:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
provider.form = ProviderForm(
|
provider.form = ProviderForm(
|
||||||
@@ -1080,17 +1082,17 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
|
|||||||
provider.form.fields = OrderedDict(
|
provider.form.fields = OrderedDict(
|
||||||
[
|
[
|
||||||
('ticketoutput_%s_%s' % (provider.identifier, k), v)
|
('ticketoutput_%s_%s' % (provider.identifier, k), v)
|
||||||
for k, v in provider.settings_form_fields.items()
|
for k, v in provider_settings_fields.items()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
provider.settings_content = provider.settings_content_render(self.request)
|
provider.settings_content = provider_settings_content
|
||||||
provider.form.prepare_fields()
|
provider.form.prepare_fields()
|
||||||
|
|
||||||
provider.evaluated_preview_allowed = True
|
provider.evaluated_preview_allowed = True
|
||||||
if not provider.preview_allowed:
|
if not provider.preview_allowed:
|
||||||
provider.evaluated_preview_allowed = False
|
provider.evaluated_preview_allowed = False
|
||||||
else:
|
else:
|
||||||
for k, v in provider.settings_form_fields.items():
|
for k, v in provider_settings_fields.items():
|
||||||
if v.required and not self.request.event.settings.get('ticketoutput_%s_%s' % (provider.identifier, k)):
|
if v.required and not self.request.event.settings.get('ticketoutput_%s_%s' % (provider.identifier, k)):
|
||||||
provider.evaluated_preview_allowed = False
|
provider.evaluated_preview_allowed = False
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -564,6 +564,8 @@ class OrderDetail(OrderView):
|
|||||||
responses = register_ticket_outputs.send(self.request.event)
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
for receiver, response in responses:
|
for receiver, response in responses:
|
||||||
provider = response(self.request.event)
|
provider = response(self.request.event)
|
||||||
|
if provider.is_meta:
|
||||||
|
continue
|
||||||
buttons.append({
|
buttons.append({
|
||||||
'text': provider.download_button_text or 'Ticket',
|
'text': provider.download_button_text or 'Ticket',
|
||||||
'icon': provider.download_button_icon or 'fa-download',
|
'icon': provider.download_button_icon or 'fa-download',
|
||||||
|
|||||||
44
src/pretix/plugins/wallet/migrations/0001_initial.py
Normal file
44
src/pretix/plugins/wallet/migrations/0001_initial.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 4.2.28 on 2026-03-17 16:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import pretix.base.models.base
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("pretixbase", "0297_outgoingmail"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="WalletLayout",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=190)),
|
||||||
|
("platform", models.CharField(max_length=10)),
|
||||||
|
("style", models.CharField(max_length=255)),
|
||||||
|
("layout", models.TextField()),
|
||||||
|
(
|
||||||
|
"event",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wallet_layouts",
|
||||||
|
to="pretixbase.event",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ("name",),
|
||||||
|
},
|
||||||
|
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
src/pretix/plugins/wallet/migrations/__init__.py
Normal file
0
src/pretix/plugins/wallet/migrations/__init__.py
Normal file
47
src/pretix/plugins/wallet/models.py
Normal file
47
src/pretix/plugins/wallet/models.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from pretix.base.models import LoggedModel
|
||||||
|
|
||||||
|
|
||||||
|
class WalletLayout(LoggedModel):
|
||||||
|
event = models.ForeignKey(
|
||||||
|
'pretixbase.Event',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='wallet_layouts'
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=190,
|
||||||
|
verbose_name=_('Name')
|
||||||
|
)
|
||||||
|
platform = models.CharField(max_length=10)
|
||||||
|
style = models.CharField(max_length=255)
|
||||||
|
layout = models.TextField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("name",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
# TODO:ScopedManager
|
||||||
@@ -20,22 +20,16 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from pretix.base.signals import register_ticket_outputs
|
from pretix.base.signals import register_ticket_outputs
|
||||||
|
from .ticketoutput import OUTPUTS
|
||||||
|
|
||||||
|
def connect_signals():
|
||||||
|
for output in OUTPUTS:
|
||||||
|
# DIY functools.partial to make get_defining_app happy
|
||||||
|
def get_register_func(o):
|
||||||
|
def register(sender, **kwargs):
|
||||||
|
return o
|
||||||
|
return register
|
||||||
|
register_ticket_outputs.connect(get_register_func(output), dispatch_uid=f"output_{output.identifier}")
|
||||||
|
|
||||||
@receiver(register_ticket_outputs, dispatch_uid="output_wallet")
|
connect_signals()
|
||||||
def register_ticket_wallet(sender, **kwargs):
|
|
||||||
from .ticketoutput import WalletTicketOutput
|
|
||||||
return WalletTicketOutput
|
|
||||||
|
|
||||||
@receiver(register_ticket_outputs, dispatch_uid="output_wallet_apple")
|
|
||||||
def register_ticket_wallet_apple(sender, **kwargs):
|
|
||||||
from .ticketoutput import AppleWalletTicketOutput
|
|
||||||
return AppleWalletTicketOutput
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_ticket_outputs, dispatch_uid="output_wallet_google")
|
|
||||||
def register_ticket_wallet_google(sender, **kwargs):
|
|
||||||
from .ticketoutput import GoogleWalletTicketOutput
|
|
||||||
return GoogleWalletTicketOutput
|
|
||||||
|
|||||||
156
src/pretix/plugins/wallet/styles.py
Normal file
156
src/pretix/plugins/wallet/styles.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
from dataclasses import dataclass, field, asdict
|
||||||
|
from typing import Literal
|
||||||
|
import enum
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from i18nfield.strings import LazyI18nString
|
||||||
|
|
||||||
|
class PlaceholderFieldType(enum.Enum):
|
||||||
|
TEXT = "text"
|
||||||
|
CODE = "qr"
|
||||||
|
IMAGE = "image"
|
||||||
|
PREDEFINED = "predefined"
|
||||||
|
# TODO: POWERED_BY ?
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlaceholderField:
|
||||||
|
type: PlaceholderFieldType
|
||||||
|
label: LazyI18nString
|
||||||
|
value: LazyI18nString
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return {'type': self.type.value, 'label': self.label, 'value': self.value}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FieldGroupDefinition:
|
||||||
|
name: str
|
||||||
|
identifier: str
|
||||||
|
entry_type: PlaceholderFieldType
|
||||||
|
min_entries: int | None = None
|
||||||
|
max_entries: int | None = None
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return {"identifier": self.identifier, "name": self.name, "min_entries": self.min_entries, "max_entries": self.max_entries}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlaceholderFieldGroup(FieldGroupDefinition):
|
||||||
|
entry_type: PlaceholderFieldType = PlaceholderFieldType.TEXT
|
||||||
|
default_entries: list[PlaceholderField] = field(default_factory=list)
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
asdict = super().asdict()
|
||||||
|
asdict['default_entries'] = [x.asdict() for x in self.default_entries]
|
||||||
|
return asdict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PredefinedFieldGroup(FieldGroupDefinition):
|
||||||
|
entry_type: PlaceholderFieldType = PlaceholderFieldType.PREDEFINED
|
||||||
|
min_entries = 0
|
||||||
|
max_entries = 1
|
||||||
|
|
||||||
|
|
||||||
|
class PassStyle:
|
||||||
|
identifier: str # unique within platform
|
||||||
|
name: str
|
||||||
|
platform: Literal["apple"] | Literal["google"]
|
||||||
|
fields: list[FieldGroupDefinition]
|
||||||
|
# preview_image: str # TODO: preview
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return {
|
||||||
|
"identifier": self.identifier,
|
||||||
|
"name": self.name,
|
||||||
|
"platform": self.platform,
|
||||||
|
"fields": [x.asdict() for x in self.fields],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AppleWalletEventTicket(PassStyle):
|
||||||
|
identifier = "event_1"
|
||||||
|
name = "Event Ticket Layout 1"
|
||||||
|
platform = "apple"
|
||||||
|
# order here limits in what order users can configure field "overspilling" (if too many fields are defined, where should the rest go) -> can only go down in the list
|
||||||
|
# we evaluate the fields in this order, so they overspill in this order as well (fields from primary are appended to the overspilling field before fields from secondary are etc)
|
||||||
|
fields = [
|
||||||
|
PlaceholderFieldGroup(
|
||||||
|
identifier="logo",
|
||||||
|
name=_("Logo"),
|
||||||
|
min_entries=1,
|
||||||
|
max_entries=1,
|
||||||
|
default_entries=[
|
||||||
|
PlaceholderField(PlaceholderFieldType.IMAGE, "logo", "event:image")
|
||||||
|
],
|
||||||
|
entry_type=PlaceholderFieldType.IMAGE,
|
||||||
|
),
|
||||||
|
PlaceholderFieldGroup(identifier="primary", name=_("Primary"), min_entries=1, max_entries=1),
|
||||||
|
PlaceholderFieldGroup(
|
||||||
|
identifier="secondary", name=_("Secondary"), max_entries=4
|
||||||
|
), # TODO: validation of max field count if combined "Coupons, store cards, and generic passes with a square barcode can have a total of up to four secondary and auxiliary fields, combined."
|
||||||
|
PlaceholderFieldGroup(
|
||||||
|
identifier="headers", name=_("Header"), max_entries=3
|
||||||
|
), # TODO: header image
|
||||||
|
PlaceholderFieldGroup(identifier="auxillary", name=_("Auxillary"), max_entries=4),
|
||||||
|
PlaceholderFieldGroup(identifier="back", name=_("Back")),
|
||||||
|
]
|
||||||
|
# preview_image = "apple/event_ticket.svg"
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleWalletEventTicket(PassStyle):
|
||||||
|
identifier = "event"
|
||||||
|
name = "Event Ticket"
|
||||||
|
platform = "google"
|
||||||
|
fields = [
|
||||||
|
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
|
||||||
|
PlaceholderFieldGroup(identifier="qrcode", name=_("QR-Code"), entry_type=PlaceholderFieldType.CODE),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
AVAILABLE_STYLES = [AppleWalletEventTicket(), GoogleWalletEventTicket()]
|
||||||
|
|
||||||
|
def get_platforms_with_styles():
|
||||||
|
platforms_with_styles = {}
|
||||||
|
for style in AVAILABLE_STYLES:
|
||||||
|
platform = style.platform
|
||||||
|
if platform not in platforms_with_styles:
|
||||||
|
platforms_with_styles[platform] = {}
|
||||||
|
platforms_with_styles[platform][style.identifier] = style
|
||||||
|
return platforms_with_styles
|
||||||
|
|
||||||
|
def get_platform_styles(platform):
|
||||||
|
platform_styles = {}
|
||||||
|
for style in AVAILABLE_STYLES:
|
||||||
|
if style.platform == platform:
|
||||||
|
platform_styles[style.identifier] = style
|
||||||
|
return platform_styles
|
||||||
|
|
||||||
|
def get_platforms():
|
||||||
|
return sorted(set(style.platform for style in AVAILABLE_STYLES))
|
||||||
|
|
||||||
|
class PassLayout:
|
||||||
|
style: PassStyle
|
||||||
|
layout: dict
|
||||||
|
|
||||||
|
def __init__(self, style, layout):
|
||||||
|
self.style = style
|
||||||
|
self.layout = layout
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.validate_fields()
|
||||||
|
|
||||||
|
def validate_fields(self):
|
||||||
|
style_fields = self.style.fields
|
||||||
|
if 'fields' not in self.layout:
|
||||||
|
raise ValidationError(_("Layout did not contain any fields"))
|
||||||
|
layout_fields = self.layout['fields']
|
||||||
|
if not isinstance(layout_fields, dict):
|
||||||
|
raise ValidationError(_("'fields' must be dict"))
|
||||||
|
|
||||||
|
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):
|
||||||
|
raise ValidationError(_("At least {min_entries} must be specified for {name}").format(min_entries=fieldgroup.min_entries, name=fieldgroup.name))
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "pretixcontrol/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load money %}
|
||||||
|
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<pre><code>{{ styles }}</code></pre>
|
||||||
|
<pre><code>{{ variables }}</code></pre>
|
||||||
|
{{ styles|json_script:"styles" }}
|
||||||
|
{{ variables|json_script:"variables" }}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form}}
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
{% trans "Submit " %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{% extends "pretixcontrol/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load money %}
|
||||||
|
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% trans "Wallet layouts" %}</h1>
|
||||||
|
<div class="tabbed-form">
|
||||||
|
{% for platform in platforms %}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{platform}}</legend>
|
||||||
|
</fieldset>
|
||||||
|
{% endfor %}
|
||||||
|
</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 %}
|
||||||
@@ -19,27 +19,39 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from collections import OrderedDict
|
|
||||||
import logging
|
import logging
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from pretix.base.ticketoutput import BaseTicketOutput
|
from pretix.base.ticketoutput import BaseTicketOutput
|
||||||
from django import forms
|
from pretix.base.models import Event
|
||||||
|
from pretix.base.settings import SettingsSandbox
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('pretix.plugins.wallet')
|
logger = logging.getLogger('pretix.plugins.wallet')
|
||||||
|
|
||||||
|
|
||||||
class WalletTicketOutput(BaseTicketOutput):
|
class WalletSettingsHolder(BaseTicketOutput):
|
||||||
identifier = 'wallet'
|
identifier = 'wallet'
|
||||||
verbose_name = _('Wallet output')
|
verbose_name = _('Wallet Output')
|
||||||
|
|
||||||
is_enabeld = False
|
is_meta = True
|
||||||
preview_allowed = False
|
is_enabled = False
|
||||||
|
preview_allowed = False # TODO: implement own preview view or hide button for meta-outputs
|
||||||
|
|
||||||
class GoogleWalletTicketOutput(BaseTicketOutput):
|
class WalletOutput(BaseTicketOutput):
|
||||||
identifier = 'google_wallet'
|
settings_form_fields = []
|
||||||
verbose_name = _('google')
|
|
||||||
show_settings = False
|
def __init__(self, event: Event):
|
||||||
|
super().__init__(event)
|
||||||
class AppleWalletTicketOutput(BaseTicketOutput):
|
self.settings = SettingsSandbox('ticketoutput', WalletSettingsHolder.identifier, event)
|
||||||
identifier = 'apple_wallet'
|
|
||||||
verbose_name = _('apple')
|
class GoogleWalletTicketOutput(WalletOutput):
|
||||||
show_settings = False
|
identifier = 'wallet_google'
|
||||||
|
verbose_name = _('Google')
|
||||||
|
download_button_text = "Add to Google Wallet"
|
||||||
|
|
||||||
|
class AppleWalletTicketOutput(WalletOutput):
|
||||||
|
identifier = 'wallet_apple'
|
||||||
|
verbose_name = _('Apple')
|
||||||
|
download_button_text = "Add to Apple Wallet"
|
||||||
|
|
||||||
|
OUTPUTS = [WalletSettingsHolder, GoogleWalletTicketOutput, AppleWalletTicketOutput]
|
||||||
|
|||||||
36
src/pretix/plugins/wallet/urls.py
Normal file
36
src/pretix/plugins/wallet/urls.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from .views import (
|
||||||
|
EditorView,
|
||||||
|
LayoutListView
|
||||||
|
)
|
||||||
|
|
||||||
|
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>[^/]+)/$',
|
||||||
|
EditorView.as_view(), name='edit'),
|
||||||
|
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/wallet/edit/(?P<platform>[^/]+)/(?P<layout>[^/]+)/$',
|
||||||
|
EditorView.as_view(), name='edit'),
|
||||||
|
]
|
||||||
118
src/pretix/plugins/wallet/views.py
Normal file
118
src/pretix/plugins/wallet/views.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.generic import FormView, ListView, TemplateView
|
||||||
|
from pretix.base.pdf import 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
|
||||||
|
|
||||||
|
|
||||||
|
class LayoutListView(EventPermissionRequiredMixin, ListView):
|
||||||
|
model = WalletLayout
|
||||||
|
permission = "can_change_event_settings"
|
||||||
|
template_name = "pretixplugins/wallet/layout_list.html"
|
||||||
|
context_object_name = "layouts"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.wallet_layouts
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx["platforms"] = get_platforms()
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class EditorForm(forms.Form):
|
||||||
|
name = forms.CharField()
|
||||||
|
style = forms.TypedChoiceField()
|
||||||
|
layout = forms.JSONField(initial={})
|
||||||
|
|
||||||
|
def __init__(self, platform, **kwargs):
|
||||||
|
super().__init__(**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()
|
||||||
|
]
|
||||||
|
self.fields["style"].coerce = self.coerce_style
|
||||||
|
|
||||||
|
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.cleaned_data["style"], layout=self.cleaned_data["layout"]
|
||||||
|
)
|
||||||
|
layout.validate()
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class EditorView(EventPermissionRequiredMixin, FormView):
|
||||||
|
template_name = "pretixplugins/wallet/edit.html"
|
||||||
|
form_class = EditorForm
|
||||||
|
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
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = WalletLayout.objects.create(
|
||||||
|
event=self.request.event,
|
||||||
|
name=form.cleaned_data["name"],
|
||||||
|
platform=self.platform,
|
||||||
|
style=form.cleaned_data["style"],
|
||||||
|
layout=form.cleaned_data["layout"],
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
reverse(
|
||||||
|
"plugins:wallet:edit",
|
||||||
|
kwargs={
|
||||||
|
"organizer": self.request.event.organizer.slug,
|
||||||
|
"event": self.request.event.slug,
|
||||||
|
"platform": self.platform,
|
||||||
|
"layout": self.object.pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user