mirror of
https://github.com/pretix/pretix.git
synced 2026-06-12 01:35:16 +00:00
WIP: first successful pass creation
This commit is contained in:
@@ -21,7 +21,7 @@ const selection = computed({
|
|||||||
get() {
|
get() {
|
||||||
if (entry.value.type === 'placeholder') {
|
if (entry.value.type === 'placeholder') {
|
||||||
return entry.value.content
|
return entry.value.content
|
||||||
} else if (entry.value.type === 'text') {
|
} else if (entry.value.type === 'custom') {
|
||||||
return "other"
|
return "other"
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
||||||
@@ -29,7 +29,7 @@ const selection = computed({
|
|||||||
},
|
},
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
if (newValue == "other") {
|
if (newValue == "other") {
|
||||||
entry.value.type = "text"
|
entry.value.type = "custom"
|
||||||
entry.value.content = {};
|
entry.value.content = {};
|
||||||
} else {
|
} else {
|
||||||
entry.value.type = "placeholder"
|
entry.value.type = "placeholder"
|
||||||
@@ -42,7 +42,7 @@ const textContent = computed({
|
|||||||
get() {
|
get() {
|
||||||
if (entry.value.type === 'placeholder') {
|
if (entry.value.type === 'placeholder') {
|
||||||
return ""
|
return ""
|
||||||
} else if (entry.value.type === 'text') {
|
} else if (entry.value.type === 'custom') {
|
||||||
return entry.value.content
|
return entry.value.content
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
throw new Error(`Unknown entry type "${entry.value.type}"`);
|
||||||
|
|||||||
@@ -111,10 +111,10 @@ class SignedZipFile:
|
|||||||
class AppleWalletStyle(PassStyle):
|
class AppleWalletStyle(PassStyle):
|
||||||
platform = ApplePlatform
|
platform = ApplePlatform
|
||||||
|
|
||||||
def pass_content(self, layout, context, strings):
|
def pass_content(self, fields, strings):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def generate_pass_json(self, layout, context, strings):
|
def generate_pass_json(self, fields, context, strings):
|
||||||
def add_from_context(key):
|
def add_from_context(key):
|
||||||
value = context.get(key)
|
value = context.get(key)
|
||||||
if not value:
|
if not value:
|
||||||
@@ -128,7 +128,7 @@ class AppleWalletStyle(PassStyle):
|
|||||||
"passTypeIdentifier": add_from_context("passTypeIdentifier"),
|
"passTypeIdentifier": add_from_context("passTypeIdentifier"),
|
||||||
"teamIdentifier": add_from_context("teamIdentifier"),
|
"teamIdentifier": add_from_context("teamIdentifier"),
|
||||||
"serialNumber": add_from_context("serialNumber"),
|
"serialNumber": add_from_context("serialNumber"),
|
||||||
**self.pass_content(layout, context, strings),
|
**self.pass_content(fields, strings),
|
||||||
}
|
}
|
||||||
return pass_json
|
return pass_json
|
||||||
|
|
||||||
@@ -136,6 +136,9 @@ class AppleWalletStyle(PassStyle):
|
|||||||
for key in ["ca_certificate", "certificate", "key", "password", "locales"]:
|
for key in ["ca_certificate", "certificate", "key", "password", "locales"]:
|
||||||
if key not in context:
|
if key not in context:
|
||||||
raise ValueError(f"{key} missing from context")
|
raise ValueError(f"{key} missing from context")
|
||||||
|
|
||||||
|
fields = self.get_pass_fields(layout, context)
|
||||||
|
|
||||||
pkpass = SignedZipFile(
|
pkpass = SignedZipFile(
|
||||||
context["ca_certificate"],
|
context["ca_certificate"],
|
||||||
context["certificate"],
|
context["certificate"],
|
||||||
@@ -144,12 +147,23 @@ class AppleWalletStyle(PassStyle):
|
|||||||
)
|
)
|
||||||
strings = StringResource(locales=context['locales'])
|
strings = StringResource(locales=context['locales'])
|
||||||
|
|
||||||
pass_json = self.generate_pass_json(layout, context, strings)
|
pass_json = self.generate_pass_json(fields, context, strings)
|
||||||
print(pass_json)
|
print(pass_json)
|
||||||
pkpass.add_file(
|
if fields['logo']:
|
||||||
"icon.png", open(finders.find("pretix_passbook/icon.png"), "rb").read()
|
logo = fields['logo'][0]['value']
|
||||||
)
|
else:
|
||||||
|
logo = open(finders.find("pretix_passbook/logo.png"), "rb")
|
||||||
|
|
||||||
|
if fields['icon']:
|
||||||
|
icon = fields['icon'][0]['value']
|
||||||
|
else:
|
||||||
|
icon = open(finders.find("pretix_passbook/icon.png"), "rb")
|
||||||
|
|
||||||
|
pkpass.add_file("icon.png", icon.read())
|
||||||
|
pkpass.add_file("logo.png", logo.read())
|
||||||
|
|
||||||
|
for lang, content in strings.generate().items():
|
||||||
|
pkpass.add_file(f"{lang}.lproj/pass.strings", content)
|
||||||
pkpass.add_file("pass.json", json.dumps(pass_json))
|
pkpass.add_file("pass.json", json.dumps(pass_json))
|
||||||
return pkpass.finish()
|
return pkpass.finish()
|
||||||
|
|
||||||
@@ -158,6 +172,18 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
|||||||
identifier = "event_1"
|
identifier = "event_1"
|
||||||
name = _("Event Ticket Layout 1")
|
name = _("Event Ticket Layout 1")
|
||||||
fieldgroups = [
|
fieldgroups = [
|
||||||
|
ImageFieldGroup(
|
||||||
|
identifier="icon",
|
||||||
|
name=_("Icon"),
|
||||||
|
min_entries=0,
|
||||||
|
max_entries=1,
|
||||||
|
labels=False,
|
||||||
|
default_entries=[
|
||||||
|
PlaceholderFieldEntry(
|
||||||
|
content="poweredby",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
ImageFieldGroup(
|
ImageFieldGroup(
|
||||||
identifier="logo",
|
identifier="logo",
|
||||||
name=_("Logo"),
|
name=_("Logo"),
|
||||||
@@ -194,47 +220,6 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
|||||||
]
|
]
|
||||||
# preview_image = "apple/event_ticket.svg"
|
# preview_image = "apple/event_ticket.svg"
|
||||||
|
|
||||||
def get_pass_fields(self, layout, context):
|
|
||||||
fields = {}
|
|
||||||
for group in self.fieldgroups:
|
|
||||||
if isinstance(group, PredefinedFieldGroup):
|
|
||||||
pass
|
|
||||||
elif isinstance(group, PlaceholderFieldGroup):
|
|
||||||
group_fields = []
|
|
||||||
if group.identifier in layout["fieldgroups"]:
|
|
||||||
for field in layout["fieldgroups"][group.identifier]["entries"]:
|
|
||||||
field_entry = {}
|
|
||||||
if group.labels:
|
|
||||||
field_entry["label"] = LazyI18nString(field["label"])
|
|
||||||
if field["type"] == FieldEntryType.PLACEHOLDER.value:
|
|
||||||
placeholder = (
|
|
||||||
context.get("placeholders")
|
|
||||||
.get(group.content_type.value, {})
|
|
||||||
.get(field["content"])
|
|
||||||
)
|
|
||||||
if placeholder:
|
|
||||||
placeholder_value = placeholder["evaluate"](
|
|
||||||
*context.get("evaluation_context", [])
|
|
||||||
)
|
|
||||||
if placeholder_value:
|
|
||||||
field_entry["value"] = placeholder_value
|
|
||||||
elif field["type"] == FieldEntryType.TEXT.value:
|
|
||||||
placeholder_value = LazyI18nString(field["content"])
|
|
||||||
elif field["type"] == FieldEntryType.IMAGE.value:
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Image placeholders not implemented"
|
|
||||||
)
|
|
||||||
if "value" in field_entry and field_entry["value"]:
|
|
||||||
group_fields.append(field_entry)
|
|
||||||
if group.min_entries and len(group_fields) < group.min_entries:
|
|
||||||
raise ValueError(
|
|
||||||
f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(group_fields)} were provided"
|
|
||||||
)
|
|
||||||
fields[group.identifier] = group_fields[: group.max_entries]
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown field group")
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def convert_fields(self, strings, fields, prefix):
|
def convert_fields(self, strings, fields, prefix):
|
||||||
converted = []
|
converted = []
|
||||||
for i,f in enumerate(fields):
|
for i,f in enumerate(fields):
|
||||||
@@ -243,13 +228,18 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
|||||||
strings.add_entry(f"{prefix}-{i}-label", converted_field['label'])
|
strings.add_entry(f"{prefix}-{i}-label", converted_field['label'])
|
||||||
converted_field['label'] = f"{prefix}-{i}-label"
|
converted_field['label'] = f"{prefix}-{i}-label"
|
||||||
|
|
||||||
|
if isinstance(converted_field['value'], LazyI18nString):
|
||||||
|
strings.add_entry(f"{prefix}-{i}-value", converted_field['value'])
|
||||||
|
converted_field['value'] = f"{prefix}-{i}-value"
|
||||||
converted.append(converted_field)
|
converted.append(converted_field)
|
||||||
return converted
|
return converted
|
||||||
|
|
||||||
def pass_content(self, layout, context, strings):
|
def pass_content(self, fields, strings):
|
||||||
fields = self.get_pass_fields(layout, context)
|
|
||||||
return {
|
return {
|
||||||
"eventTicket": {
|
"eventTicket": {
|
||||||
"primaryFields": self.convert_fields(strings, fields['primary'], 'primary')
|
"primaryFields": self.convert_fields(strings, fields['primary'], 'primary'),
|
||||||
|
"secondaryFields": self.convert_fields(strings, fields['secondary'], 'secondary'),
|
||||||
|
"auxillaryFields": self.convert_fields(strings, fields['auxillary'], 'auxillary'),
|
||||||
|
"backFields": self.convert_fields(strings, fields['back'], 'back'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ class FieldContentType(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
class FieldEntryType(enum.Enum):
|
class FieldEntryType(enum.Enum):
|
||||||
IMAGE = "image"
|
CUSTOM = "custom"
|
||||||
TEXT = "text"
|
|
||||||
PLACEHOLDER = "placeholder"
|
PLACEHOLDER = "placeholder"
|
||||||
|
|
||||||
|
|
||||||
@@ -191,7 +190,7 @@ class PlaceholderFieldGroup(FieldGroup):
|
|||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
**baseprops,
|
**baseprops,
|
||||||
"type": {"const": self.content_type.value},
|
"type": {"const": "custom"},
|
||||||
"content": {"$ref": "#/$defs/I18nString"},
|
"content": {"$ref": "#/$defs/I18nString"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -276,6 +275,49 @@ class PassStyle:
|
|||||||
|
|
||||||
def generate(self, layout, context):
|
def generate(self, layout, context):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def render_placeholder(self, context, content_type, content):
|
||||||
|
placeholder = (
|
||||||
|
context.get("placeholders")
|
||||||
|
.get(content_type, {})
|
||||||
|
.get(content)
|
||||||
|
)
|
||||||
|
if placeholder:
|
||||||
|
placeholder_value = placeholder["evaluate"](
|
||||||
|
*context.get("evaluation_context", [])
|
||||||
|
)
|
||||||
|
if placeholder_value:
|
||||||
|
return placeholder_value
|
||||||
|
def get_pass_fields(self, layout, context):
|
||||||
|
fields = {}
|
||||||
|
for group in self.fieldgroups:
|
||||||
|
if isinstance(group, PredefinedFieldGroup):
|
||||||
|
pass
|
||||||
|
elif isinstance(group, PlaceholderFieldGroup):
|
||||||
|
group_fields = fields.get(group.identifier, [])
|
||||||
|
if group.identifier in layout["fieldgroups"]:
|
||||||
|
for field in layout["fieldgroups"][group.identifier]["entries"]:
|
||||||
|
field_entry = {}
|
||||||
|
if group.labels:
|
||||||
|
field_entry["label"] = LazyI18nString(field["label"])
|
||||||
|
if field["type"] == FieldEntryType.PLACEHOLDER.value:
|
||||||
|
field_entry["value"] = self.render_placeholder(context, group.content_type.value, field['content'])
|
||||||
|
elif field["type"] == FieldEntryType.CUSTOM.value:
|
||||||
|
field_entry["value"] = LazyI18nString(field["content"])
|
||||||
|
if "value" in field_entry and field_entry["value"]:
|
||||||
|
group_fields.append(field_entry)
|
||||||
|
if group.min_entries and len(group_fields) < group.min_entries:
|
||||||
|
raise ValueError(
|
||||||
|
f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(group_fields)} were provided"
|
||||||
|
)
|
||||||
|
fields[group.identifier] = group_fields[: group.max_entries]
|
||||||
|
if (overflow_group := layout["fieldgroups"][group.identifier]['overflow']):
|
||||||
|
fields.setdefault(overflow_group, [])
|
||||||
|
fields[overflow_group] += group_fields[group.max_entries:]
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown field group")
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
class PassLayout:
|
class PassLayout:
|
||||||
style: PassStyle
|
style: PassStyle
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ from pretix.base.settings import SettingsSandbox
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from .styles import AVAILABLE_STYLES_DICT
|
from .styles import AVAILABLE_STYLES_DICT
|
||||||
|
from .styles.apple import ApplePlatform
|
||||||
|
from .styles.google import GooglePlatform
|
||||||
|
|
||||||
from .models import WalletLayout
|
from .models import WalletLayout
|
||||||
from .views import get_layout_variables
|
from .views import get_layout_variables
|
||||||
@@ -65,12 +67,14 @@ 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"
|
||||||
|
platform = GooglePlatform
|
||||||
|
|
||||||
|
|
||||||
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"
|
||||||
|
platform = ApplePlatform
|
||||||
|
|
||||||
def generate(self, op):
|
def generate(self, op):
|
||||||
order = op.order
|
order = op.order
|
||||||
@@ -87,6 +91,7 @@ class AppleWalletTicketOutput(WalletOutput):
|
|||||||
# )
|
# )
|
||||||
# )
|
# )
|
||||||
layout = WalletLayout.objects.get(pk=1)
|
layout = WalletLayout.objects.get(pk=1)
|
||||||
|
platform_layout = layout.platform_layouts.get(platform=self.platform.identifier)
|
||||||
|
|
||||||
ticket = str(op.item.name)
|
ticket = str(op.item.name)
|
||||||
if op.variation:
|
if op.variation:
|
||||||
@@ -121,9 +126,9 @@ class AppleWalletTicketOutput(WalletOutput):
|
|||||||
"serialNumber": serialNumber,
|
"serialNumber": serialNumber,
|
||||||
"locales": event.settings.locales
|
"locales": event.settings.locales
|
||||||
}
|
}
|
||||||
assert layout.platform == "apple"
|
|
||||||
data = AVAILABLE_STYLES_DICT[layout.platform][layout.style].generate(
|
data = AVAILABLE_STYLES_DICT[self.platform.identifier][platform_layout.style].generate(
|
||||||
layout.layout, context
|
platform_layout.layout, context
|
||||||
)
|
)
|
||||||
return filename, "application/vnd.apple.pkpass", data
|
return filename, "application/vnd.apple.pkpass", data
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ from django.conf import settings
|
|||||||
from .models import WalletLayout
|
from .models import WalletLayout
|
||||||
from .styles import AVAILABLE_STYLES, AVAILABLE_PLATFORMS
|
from .styles import AVAILABLE_STYLES, AVAILABLE_PLATFORMS
|
||||||
|
|
||||||
|
from django.contrib.staticfiles import finders
|
||||||
|
|
||||||
def get_layout_variables(event):
|
def get_layout_variables(event):
|
||||||
return {
|
return {
|
||||||
"text": get_variables(event),
|
"text": get_variables(event),
|
||||||
"image": get_images(event)
|
"image": get_images(event)
|
||||||
| {"poweredby": {"label": _("pretix-Logo")}}, # TODO: image upload
|
| {"poweredby": {"label": _("pretix-Logo"), "evaluate": lambda *_: open(finders.find("pretix_passbook/logo.png"), "rb")},
|
||||||
|
"poweredby_icon": {"label": _("pretix-Icon"), "evaluate": lambda *_: open(finders.find("pretix_passbook/icon.png"), "rb")}}, # TODO: image upload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user