Start with custom google pass layout

This commit is contained in:
Kara Engelhardt committed 2026-11-05 13:22:46 +01:00
1 parent b23a74f3b0
commit ac2d25ba9a
3 files changed
+332 -176

No files matched your search

+1 -1
View File
@@ -121,7 +121,7 @@ def get_preview_position(event):
raise RuntimeError("get_preview_position needs to be called in a rolledback_transaction")
item = event.items.create(name=LazyI18nString.from_gettext("Sample product"), default_price=Decimal('42.23'),
description=_("Sample product description"))
description=LazyI18nString.from_gettext("Sample product description"))
item2 = event.items.create(name=LazyI18nString.from_gettext("Sample workshop"), default_price=Decimal('23.40'))
item.program_times.create(item=item, start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
+2 -2
View File
@@ -1,13 +1,13 @@
from pretix.plugins.wallet.styles.base import PassStyle
from .apple import ApplePlatform, AppleWalletEventTicket
from .google import GooglePlatform, GoogleWalletDefaultEventTicket
from .google import GooglePlatform, GoogleWalletCustomEventTicket, GoogleWalletDefaultEventTicket
AVAILABLE_PLATFORMS = [ApplePlatform, GooglePlatform]
AVAILABLE_STYLES: dict[str, list[type[PassStyle]]] = {
"apple": [AppleWalletEventTicket],
"google": [GoogleWalletDefaultEventTicket],
"google": [GoogleWalletDefaultEventTicket, GoogleWalletCustomEventTicket],
}
AVAILABLE_STYLES_DICT = {
+329 -173
View File
@@ -24,14 +24,18 @@ from walletobjects.constants import (
from pretix.base.models import Event, OrderPosition
from pretix.base.settings import GlobalSettingsObject
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.plugins.wallet.i18n import localizable_to_dict
from pretix.plugins.wallet.models import GoogleWalletInstance, GoogleWalletType
from pretix.plugins.wallet.styles.base import (
ColorSettingsField,
FieldContentType,
FieldGroup,
FieldGroupDisplay,
FloatSettingsField,
ImageSettingsField,
PassStyle,
PlaceholderFieldEntry,
PlaceholderFieldGroup,
PredefinedFieldGroup,
TextFieldGroup,
WalletPlatform,
@@ -102,30 +106,6 @@ class GooglePlatform(WalletPlatform):
class GoogleWalletEventTicketStyle(PassStyle):
platform = GooglePlatform
@property
def settings(self):
return [
ImageSettingsField(
identifier="logo",
label=_("Logo"),
default="pretixplugins/wallet/logo.png",
),
ImageSettingsField(
identifier="hero",
label=_("Hero Image"),
default=self.event.settings.logo_image,
),
ColorSettingsField(
identifier="bg_color",
label=_("Background Color"),
default=self.event.settings.primary_color,
),
FloatSettingsField(identifier="lat", label=_("Latitude"), min=-90, max=90),
FloatSettingsField(
identifier="long", label=_("Longitude"), min=-180, max=180
),
]
@cached_property
def comms(self):
return Comms(self.event.settings.get("wallet_google_credentials").read())
@@ -144,57 +124,6 @@ class GoogleWalletEventTicketStyle(PassStyle):
instance.save(update_fields=["data"])
return result
def _generate_class(self):
output_class = EventTicketClass(
self.event.organizer.name,
get_class_id(self.event, self.op),
MultipleDevicesAndHoldersAllowedStatus.multipleHolders, # TODO: Make configurable
self.event.name,
ReviewStatus.underReview,
self.event.settings.locale,
)
output_class.homepage_uri(
eventreverse_absolute(self.event, "presale:event.index"),
get_translated_string("Website", self.event.settings.locale),
get_translated_dict("Website", self.event.settings.locales),
)
# TODO: callback url
# output_class.callback_url(eventreverse_absolute(event.organizer,"plugins:wallet:google_webhook",))
if (lat := self.cleaned_settings["lat"]) and (
long := self.cleaned_settings["long"]
):
output_class.locations(lat, long)
elif self.event.geo_lat and self.event.geo_lon:
output_class.locations(self.event.geo_lat, self.event.geo_lon)
output_class.country_code(self.event.settings.locale)
if hero := self.cleaned_settings["hero"]:
output_class.hero_image(
urljoin(eventreverse_absolute(self.event, "presale:event.index"), hero),
self.event.name,
)
if logo := self.cleaned_settings["logo"]:
output_class.logo(
urljoin(eventreverse_absolute(self.event, "presale:event.index"), logo),
self.event.name,
)
output_class.hex_background_color(self.cleaned_settings["bg_color"])
output_class.confirmation_code_label(ConfirmationCode.orderNumber)
return output_class
def _generate_object(self, op: OrderPosition, class_id: str):
output_object = EventTicketObject(
get_object_id(op), class_id, ObjectState.active, self.event.settings.locale
)
return output_object
def generate(self, op):
self.op = op
@@ -223,18 +152,35 @@ class GoogleWalletEventTicketStyle(PassStyle):
"https://pay.google.com/gp/v/save/%s" % generated_jwt,
)
class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
identifier = "event"
name = "Event Ticket"
@property
def settings(self):
return [
ImageSettingsField(
identifier="logo",
label=_("Logo"),
default=self.event.settings.logo_image
or "pretixplugins/wallet/logo.png",
),
# TODO: wide logo
ImageSettingsField(
identifier="hero",
label=_("Hero Image"),
),
ColorSettingsField(
identifier="bg_color",
label=_("Background Color"),
default=self.event.settings.primary_color,
),
FloatSettingsField(identifier="lat", label=_("Latitude"), min=-90, max=90),
FloatSettingsField(
identifier="long", label=_("Longitude"), min=-180, max=180
),
]
@property
def fieldgroups(self):
def fieldgroups(self) -> list[FieldGroup]:
return [
PredefinedFieldGroup(identifier="venue", name=_("Venue")),
PredefinedFieldGroup(identifier="date", name=_("Date")),
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
PredefinedFieldGroup(identifier="ticket_holder", name=_("Ticket Holder")),
TextFieldGroup(
identifier="code",
name=_("QR-Code"),
@@ -281,81 +227,22 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
"direction": "column",
"display": ["tight"],
},
{
"fieldgroup": "date",
"sample": [
{
"content": date_format(
self.event.date_from.astimezone(
self.event.timezone
),
"SHORT_DATE_FORMAT",
),
"label": "Date",
},
{
"content": date_format(
self.event.date_from.astimezone(
self.event.timezone
),
"TIME_FORMAT",
),
"label": "Time",
},
],
},
{
"fieldgroup": "seating",
"sample": [
{"content": _("Ground floor"), "label": _("Section")},
{"content": "5 / 2", "label": _("Row") + " / " + _("Seat")},
],
},
*self._preview_layout_front_card,
{"fieldgroup": "code"},
{"setting": "hero"},
],
},
{
"rows": [
{
"fieldgroup": "ticket_holder",
"sample": [
{"content": _("John Doe"), "label": _("Ticket Holder")},
],
},
{
"fieldgroup": "venue",
"sample": [
{"content": self.venue()[1], "label": self.venue()[0]},
],
},
{
"fieldgroup": "date",
"sample": [
{
"content": date_format(
self.event.date_from.astimezone(
self.event.timezone
),
"DATETIME_FORMAT",
),
"label": _("Event Start Time"),
},
],
},
{"value": _("Sample product"), "label": _("Ticket Type")},
{"value": "ABCDE-1", "label": _("Ticket Number")},
{"value": "ABCDE", "label": _("Order Number")},
{
"value": eventreverse_absolute(
self.event, "presale:event.index"
),
"label": _("Website"),
},
]
},
{"rows": [*self._preview_layout_back_details]},
]
@property
def _preview_layout_front_card(self):
return []
@property
def _preview_layout_back_details(self):
return []
def venue(self):
if self.event.location:
name = {}
@@ -374,10 +261,162 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
return "", ""
def _generate_class(self):
output_class = super()._generate_class()
output_class = EventTicketClass(
self.event.organizer.name,
get_class_id(self.event, self.op),
MultipleDevicesAndHoldersAllowedStatus.multipleHolders, # TODO: Make configurable
self.event.name,
ReviewStatus.underReview,
self.event.settings.locale,
)
output_class.homepage_uri(
eventreverse_absolute(self.event, "presale:event.index"),
get_translated_string("Website", self.event.settings.locale),
localizable_to_dict("Website", self.event.settings.locales),
)
if (lat := self.cleaned_settings["lat"]) and (
long := self.cleaned_settings["long"]
):
output_class.locations(lat, long)
elif self.event.geo_lat and self.event.geo_lon:
output_class.locations(self.event.geo_lat, self.event.geo_lon)
output_class.country_code(self.event.settings.locale)
if hero := self.cleaned_settings["hero"]:
output_class.hero_image(
urljoin(eventreverse_absolute(self.event, "presale:event.index"), hero),
self.event.name,
)
if logo := self.cleaned_settings["logo"]:
output_class.logo(
urljoin(eventreverse_absolute(self.event, "presale:event.index"), logo),
self.event.name,
)
output_class.hex_background_color(self.cleaned_settings["bg_color"])
output_class.confirmation_code_label(ConfirmationCode.orderNumber)
if self.group_is_active("venue") and all(self.venue()):
output_class.venue(*self.venue())
if SHIMMER:
output_class.securityAnimation(AnimationType.FOIL_SHIMMER)
# TODO: callback url
# output_class.callback_url(eventreverse_absolute(event.organizer,"plugins:wallet:google_webhook",))
return output_class
def _generate_object(self, op: OrderPosition, class_id: str):
output_object = EventTicketObject(
get_object_id(op), class_id, ObjectState.active, self.event.settings.locale
)
fields = self.get_pass_fields(op)
if fields["code"]:
output_object.barcode(
Barcode.qrCode, fields["code"][0]["value"], fields["code"][0]["value"]
)
output_object.reservation_info(op.order.code)
output_object.ticket_number(op.code)
output_object.valid_time_interval(op.valid_from, op.valid_until)
output_object.ticket_type(
get_translated_dict(
str(op.item)
+ (" – " + str(op.variation.value) if op.variation else ""),
op.order.event.settings.get("locales"),
)
)
return output_object
class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
identifier = "event"
name = "Default Event Ticket"
@property
def fieldgroups(self):
return [
PredefinedFieldGroup(identifier="date", name=_("Date")),
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
PredefinedFieldGroup(identifier="ticket_holder", name=_("Ticket Holder")),
*super().fieldgroups,
]
@property
def _preview_layout_front_card(self):
return [
{
"fieldgroup": "date",
"sample": [
{
"content": date_format(
self.event.date_from.astimezone(self.event.timezone),
"SHORT_DATE_FORMAT",
),
"label": "Date",
},
{
"content": date_format(
self.event.date_from.astimezone(self.event.timezone),
"TIME_FORMAT",
),
"label": "Time",
},
],
},
{
"fieldgroup": "seating",
"sample": [
{"content": _("Ground floor"), "label": _("Section")},
{"content": "5 / 2", "label": _("Row") + " / " + _("Seat")},
],
},
]
@property
def _preview_layout_back_details(self):
return [
{
"fieldgroup": "ticket_holder",
"sample": [
{"content": _("John Doe"), "label": _("Ticket Holder")},
],
},
{
"fieldgroup": "venue",
"sample": [
{"content": self.venue()[1], "label": self.venue()[0]},
],
},
{
"fieldgroup": "date",
"sample": [
{
"content": date_format(
self.event.date_from.astimezone(self.event.timezone),
"DATETIME_FORMAT",
),
"label": _("Event Start Time"),
},
],
},
{"value": _("Sample product"), "label": _("Ticket Type")},
{"value": "ABCDE-1", "label": _("Ticket Number")},
{"value": "ABCDE", "label": _("Order Number")},
{
"value": eventreverse_absolute(self.event, "presale:event.index"),
"label": _("Website"),
},
]
def _generate_class(self):
output_class = super()._generate_class()
if self.group_is_active("date"):
if self.event.date_from:
output_class.start(self.event.date_from.isoformat())
@@ -390,9 +429,6 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
DoorsOpen.doorsOpen, self.event.date_admission.isoformat()
)
if SHIMMER:
output_class.securityAnimation(AnimationType.FOIL_SHIMMER)
print(output_class)
return output_class
def ticket_holder_name(self, op):
@@ -400,14 +436,6 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
def _generate_object(self, op: OrderPosition, class_id: str):
output_object = super()._generate_object(op, class_id)
fields = self.get_pass_fields(op)
if fields["code"]:
output_object.barcode(
Barcode.qrCode, fields["code"][0]["value"], fields["code"][0]["value"]
)
output_object.reservation_info(op.order.code)
output_object.ticket_number(op.code)
if self.group_is_active("ticket_holder") and self.ticket_holder_name(op):
output_object.ticket_holder_name(self.ticket_holder_name(op))
@@ -426,16 +454,144 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
elif seat.seat_number:
output_object.seat(seat.seat_number)
output_object.valid_time_interval(op.valid_from, op.valid_until)
output_object.ticket_type(
get_translated_dict(
str(op.item)
+ (" – " + str(op.variation.value) if op.variation else ""),
op.order.event.settings.get("locales"),
)
)
# places = settings.CURRENCY_PLACES.get(op.order.event.currency, 2)
# output_object.face_value(int(op.price * 1000 ** places), op.order.event.currency)
return output_object
class GoogleWalletCustomEventTicket(GoogleWalletEventTicketStyle):
identifier = "event_custom"
name = "Custom Event Ticket"
@property
def _preview_layout_front_card(self):
return [
{
"fieldgroup": "second_row",
},
{
"fieldgroup": "third_row",
},
]
@property
def _preview_layout_back_details(self):
return [{"fieldgroup": "details", "direction": "column"}]
@property
def fieldgroups(self):
return [
TextFieldGroup(
identifier="second_row",
name=_("Second Row"),
max_entries=3,
context_args={"order_position"},
),
TextFieldGroup(
identifier="third_row",
name=_("Third Row"),
max_entries=3,
context_args={"order_position"},
),
*super().fieldgroups,
TextFieldGroup(
identifier="details",
name=_("Details"),
max_entries=20,
context_args={"order_position"},
),
]
def _generate_class(self):
output_class = super()._generate_class()
output_class.setdefault("classTemplateInfo", {})
output_class["classTemplateInfo"].setdefault("cardTemplateOverride", {})
output_class["classTemplateInfo"]["cardTemplateOverride"] = {
"cardRowTemplateInfos": [
{
"threeItems": {
"startItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['second_row-0']"
}
]
}
},
"middleItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['second_row-1']"
}
]
}
},
"endItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['second_row-2']"
}
]
}
},
}
},
{
"threeItems": {
"startItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['third_row-0']"
}
]
}
},
"middleItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['third_row-1']"
}
]
}
},
"endItem": {
"firstValue": {
"fields": [
{
"fieldPath": "object.textModulesData['third_row-2']"
}
]
}
},
}
},
]
}
output_class["classTemplateInfo"].setdefault("detailsTemplateOverride", {})
output_class["classTemplateInfo"]["detailsTemplateOverride"][
"detailsItemInfo"
] = []
return output_class
def _generate_object(self, op: OrderPosition, class_id: str):
output_object = super()._generate_object(op, class_id)
fields = self.get_pass_fields(op)
for row in ["second_row", "third_row"]:
for i, field in enumerate(fields.get(row, [])):
output_object.text_modules_data(
f"{row}-{i}",
localizable_to_dict(field["label"], self.event.settings.locales),
localizable_to_dict(field["value"], self.event.settings.locales),
)
return output_object