mirror of
https://github.com/pretix/pretix.git
synced 2026-08-03 09:37:51 +00:00
WIP
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import PlaceholderFieldgroupPreview from "./placeholder-fieldgroup-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!
|
||||
|
||||
const props = defineProps<{
|
||||
config: Record<string, string>;
|
||||
}>();
|
||||
|
||||
const style_def = Object.fromEntries(store.currentPlatformStyles[store.currentPlatformLayout.style].fieldgroups.map(x => [x.identifier, x]))[props.config.fieldgroup]
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
PlaceholderFieldgroupPreview(v-if="style_def.type == 'placeholder'" :config="config" :style_def="style_def")
|
||||
//- // TODO: support predefined group
|
||||
//- div(:style="{'flex-grow': config.relSize}")
|
||||
//- div(style="background-color: gray; display: flex; flex-direction: row;")
|
||||
//- div(v-for="entry in layout.entries.slice(0,style_def.max_entries)")
|
||||
//- div(v-if="style_def.labels") {{ i18nstringLocalize(entry.label) }}
|
||||
//- div(v-if="entry.type == 'custom'") {{ i18nstringLocalize(entry.content) }}
|
||||
//- div(v-if="entry.type == 'placeholder'") {{ store.variables[style_def.content_type][entry.content] }}
|
||||
//- pre
|
||||
//- //- code {{ config }}
|
||||
//- code {{ style_def }}
|
||||
//- code {{ layout }}
|
||||
//- div(style="position: absolute; background-color: red; top: 0.5cm; left: 0cm; height: 1cm; width: 2cm;") Logo
|
||||
//- div(style="position: absolute; background-color: red; top: 0.5cm; left: 2.25cm; height: 1cm; width: 5.5cm;") Logo Text
|
||||
//- div(style="position: absolute; background-color: red; top: 0.5cm; left: 8cm; height: 1cm; width: 2cm;") Header
|
||||
//- div(style="position: absolute; background-color: red; top: 2.5cm; left: 0cm; height: 2cm; width: 10cm;") Primary
|
||||
//- div(style="position: absolute; background-color: red; top: 4.75cm; left: 0cm; height: 1cm; width: 10cm;") Secondary
|
||||
//- div(style="position: absolute; background-color: red; top: 7cm; left: 0cm; height: 1cm; width: 10cm;") Auxiliary
|
||||
//- div(style="position: absolute; background-color: red; top: 10cm; left: 3cm; height: 4cm; width: 4cm;")
|
||||
</template>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore.js";
|
||||
import RowPreview from "./row-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{ layout: Array<PreviewLayout> }>();
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.pass-container
|
||||
div.pass-content
|
||||
RowPreview(v-for="row in layout" :config="row")
|
||||
</template>
|
||||
<style lang="css" scoped>
|
||||
.pass-container {
|
||||
margin: 1em;
|
||||
|
||||
width: 100%;
|
||||
aspect-ratio: 9 / 11;
|
||||
max-width: 360px;
|
||||
max-height: 440px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
|
||||
outline: solid 1px #949494;
|
||||
border-radius: 1em;
|
||||
|
||||
padding: 1em;
|
||||
}
|
||||
.pass-content {
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import { i18nstringLocalize } from "../../helpers";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{
|
||||
config: PreviewFieldgroup;
|
||||
style_def: PlaceholderFieldGroupDefinition;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// TODO: support predefined group
|
||||
div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
|
||||
div.fieldgroup-item(v-for="{ entry, label, content } in store.currentLayoutFieldContent[config.fieldgroup]")
|
||||
div.fieldgroup-item-qrcode(v-if="style_def.display == 'code'") {{ label }}
|
||||
|
||||
template(v-else)
|
||||
div.fieldgroup-label.nowrap(v-if="style_def.display == 'with_label'") {{ label }}
|
||||
div.nowrap.content(v-if="style_def.content_type == 'text'" :class="config.display") {{ content }}
|
||||
img.fieldgroup-item-image(v-else-if="style_def.content_type == 'image' && content" :src="i18nstringLocalize(content)")
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.fieldgroup-container {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
min-height: 2lh;
|
||||
}
|
||||
.fieldgroup-label {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.fieldgroup-item {
|
||||
flex: 0 1 100%;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.fieldgroup-item-image {
|
||||
max-height: 3lh;
|
||||
}
|
||||
.fieldgroup-item-qrcode {
|
||||
font-weight: bold;
|
||||
background-color: lightgray;
|
||||
width: 50%;
|
||||
aspect-ratio: 1;
|
||||
margin: auto;
|
||||
/* center content */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.large {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.tight {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import FieldgroupPreview from './fieldgroup-preview.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
config: Record<string, string>;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div(v-if="'children' in config" style="width: 100%; display: flex; gap: 1em")
|
||||
RowPreview(v-for="child in config.children" :config="child")
|
||||
FieldgroupPreview(v-else :config="config")
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
export function i18nstringLocalize(s: I18nString): string {
|
||||
if (typeof s === 'string') {
|
||||
return s
|
||||
}
|
||||
if (s === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
var locale = document.body.attributes['data-pretixlocale'].value
|
||||
var short_locale = locale.split('-')[0]
|
||||
if (locale in s)
|
||||
return s[locale]
|
||||
|
||||
if (short_locale in s)
|
||||
return s[short_locale]
|
||||
|
||||
for (const k of Object.keys(s)) {
|
||||
if (k.split('-')[0] === short_locale && s[k]) {
|
||||
return s[k]
|
||||
}
|
||||
}
|
||||
|
||||
if (s['en'])
|
||||
return s['en']
|
||||
|
||||
for (const k of Object.keys(s)) {
|
||||
if (s[k]) {
|
||||
return s[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,210 @@
|
||||
from .base import FieldGroupDisplay, PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pretix.base.models import Event, OrderPosition
|
||||
|
||||
from .base import FieldGroupDisplay, PassLayout, PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform
|
||||
from django.utils.translation import gettext_lazy as _, gettext
|
||||
import json
|
||||
|
||||
from walletobjects import ButtonJWT, EventTicketClass, EventTicketObject
|
||||
from walletobjects.comms import Comms
|
||||
from walletobjects.constants import (
|
||||
Barcode, ClassType, ConfirmationCode, DoorsOpen,
|
||||
MultipleDevicesAndHoldersAllowedStatus, ObjectState, ObjectType,
|
||||
ReviewStatus, Seat,
|
||||
)
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
import uuid
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from django.utils import translation
|
||||
|
||||
def _get_instance_uuid():
|
||||
gs = GlobalSettingsObject()
|
||||
if not gs.settings.wallet_google_instance_uuid:
|
||||
gs.settings.wallet_google_instance_uuid = str(uuid.uuid4())
|
||||
return gs.settings.get("wallet_google_instance_uuid")
|
||||
|
||||
def get_class_id(event: Event):
|
||||
instance_uuid = _get_instance_uuid()
|
||||
issuer_id = event.settings.get('wallet_google_issuer_id')
|
||||
return "%s.pretix-%s-%s-%s" % (issuer_id, instance_uuid, event.organizer.slug, event.slug)
|
||||
|
||||
|
||||
def get_object_id(op: OrderPosition):
|
||||
instance_uuid = _get_instance_uuid()
|
||||
issuer_id = op.order.event.settings.get('wallet_google_issuer_id')
|
||||
|
||||
return "%s.pretix-%s-%s-%s-%s-%s" % (issuer_id, instance_uuid,
|
||||
op.order.event.organizer.slug, op.order.event.slug,
|
||||
op.order.code, op.positionid)
|
||||
|
||||
def get_translated_dict(string, locales):
|
||||
translated = {}
|
||||
|
||||
for locale in locales:
|
||||
translation.activate(locale)
|
||||
translated[locale] = gettext(string)
|
||||
translation.deactivate()
|
||||
|
||||
return translated
|
||||
|
||||
|
||||
def get_translated_string(string, locale):
|
||||
translation.activate(locale)
|
||||
translated = gettext(string)
|
||||
translation.deactivate()
|
||||
|
||||
return translated
|
||||
|
||||
class GooglePlatform(WalletPlatform):
|
||||
identifier = "google"
|
||||
name = _("Google")
|
||||
|
||||
@classmethod
|
||||
def generate(cls, layout: PassLayout, op: OrderPosition):
|
||||
class_object = cls._generate_class(op.order.event)
|
||||
|
||||
# ticket_object = cls._get_object(op)
|
||||
|
||||
# if not ticket_object:
|
||||
# return False
|
||||
|
||||
# generated_jwt = self._comms().sign_jwt(
|
||||
# ButtonJWT(
|
||||
# origins=[django_settings.SITE_URL],
|
||||
# issuer=self._comms().client_email,
|
||||
# event_ticket_objects=[ticket_object],
|
||||
# skinny=True
|
||||
# )
|
||||
# )
|
||||
|
||||
# if generated_jwt:
|
||||
# return 'googlepaypass', 'text/uri-list', 'https://pay.google.com/gp/v/save/%s' % generated_jwt
|
||||
# else:
|
||||
# return False
|
||||
|
||||
data = {"class": class_object}
|
||||
return 'pass.json', 'application/json', json.dumps(data)
|
||||
# from ..views import get_layout_variables
|
||||
|
||||
# order = op.order
|
||||
# event = order.event
|
||||
# filename = "{}-{}.pkpass".format(order.event.slug, order.code)
|
||||
|
||||
# ticket = str(op.item.name)
|
||||
# if op.variation:
|
||||
# ticket += " - " + str(op.variation)
|
||||
|
||||
# serialNumber = "%s-%s-%s-%d" % (
|
||||
# order.event.organizer.slug,
|
||||
# order.event.slug,
|
||||
# order.code,
|
||||
# op.pk,
|
||||
# )
|
||||
|
||||
# context = {
|
||||
# "placeholders": get_layout_variables(op.order.event),
|
||||
# "evaluation_context": [op, order, order.event],
|
||||
# "ca_certificate": order.event.settings.wallet_apple_ca_certificate.read(),
|
||||
# "certificate": order.event.settings.wallet_apple_certificate.read(),
|
||||
# "key": order.event.settings.wallet_apple_key.read(),
|
||||
# "password": order.event.settings.wallet_apple_key_password,
|
||||
# "description": _("Ticket for {event} ({product})").format( # TODO: i18n
|
||||
# event=event.name, product=ticket
|
||||
# ),
|
||||
# "organizationName": event.organizer.name,
|
||||
# "passTypeIdentifier": order.event.settings.wallet_apple_pass_type_id,
|
||||
# "teamIdentifier": order.event.settings.wallet_apple_team_id,
|
||||
# "serialNumber": serialNumber,
|
||||
# "locales": event.settings.locales,
|
||||
# }
|
||||
|
||||
# data = layout.generate(context)
|
||||
# return filename, "application/vnd.apple.pkpass", data
|
||||
|
||||
@classmethod
|
||||
def _generate_class(cls, event: Event):
|
||||
class_name = get_class_id(event)
|
||||
|
||||
output_class = EventTicketClass(
|
||||
event.organizer.name,
|
||||
class_name,
|
||||
MultipleDevicesAndHoldersAllowedStatus.multipleHolders, # TODO: Make configurable
|
||||
event.name,
|
||||
ReviewStatus.draft, # TODO: ReviewStatus.underReview,
|
||||
event.settings.locale
|
||||
)
|
||||
|
||||
output_class.homepage_uri(
|
||||
eventreverse_absolute(event, 'presale:event.index'),
|
||||
get_translated_string('Website', event.settings.locale),
|
||||
get_translated_dict('Website', event.settings.locales)
|
||||
)
|
||||
|
||||
# output_class.callback_url(eventreverse_absolute(event.organizer, 'plugins:pretix_googlepaypasses:webhook'))
|
||||
|
||||
# if (event.settings.get('ticketoutput_googlepaypasses_latitude')
|
||||
# and event.settings.get('ticketoutput_googlepaypasses_longitude')):
|
||||
# output_class.locations(
|
||||
# event.settings.get('ticketoutput_googlepaypasses_latitude'),
|
||||
# event.settings.get('ticketoutput_googlepaypasses_longitude')
|
||||
# )
|
||||
# elif event.geo_lat and event.geo_lon:
|
||||
# output_class.locations(
|
||||
# event.geo_lat,
|
||||
# event.geo_lon
|
||||
# )
|
||||
|
||||
output_class.country_code(event.settings.locale)
|
||||
|
||||
output_class.hide_barcode(False)
|
||||
|
||||
# if event.settings.get('ticketoutput_googlepaypasses_hero'):
|
||||
# output_class.hero_image(
|
||||
# urljoin(django_settings.SITE_URL, event.settings.get('ticketoutput_googlepaypasses_hero').url),
|
||||
# str(event.name),
|
||||
# event.name,
|
||||
# )
|
||||
|
||||
# output_class.hex_background_color(event.settings.get('primary_color'))
|
||||
# output_class.event_id('pretix-%s-%s-%s' % (gs.settings.get('update_check_id'), event.organizer.id, event.id))
|
||||
|
||||
# if event.settings.get('ticketoutput_googlepaypasses_logo'):
|
||||
# output_class.logo(
|
||||
# urljoin(django_settings.SITE_URL, event.settings.get('ticketoutput_googlepaypasses_logo').url),
|
||||
# str(event.name),
|
||||
# event.name,
|
||||
# )
|
||||
|
||||
# if event.location:
|
||||
# name = {}
|
||||
# address = {}
|
||||
|
||||
# for key, value in event.location.data.items():
|
||||
# lines = value.splitlines()
|
||||
# name[key] = lines[0]
|
||||
# # We must provide at least one address line each for the name and address - no way around it.
|
||||
# if len(lines) > 1:
|
||||
# address[key] = '\n'.join(value.splitlines()[1:])
|
||||
# else:
|
||||
# address[key] = lines[0]
|
||||
|
||||
# output_class.venue(name, address)
|
||||
|
||||
# if event.date_from and event.date_to and event.date_admission:
|
||||
# output_class.date_time(
|
||||
# DoorsOpen.doorsOpen,
|
||||
# event.date_admission.isoformat(),
|
||||
# event.date_from.isoformat(),
|
||||
# event.date_to.isoformat(),
|
||||
# )
|
||||
|
||||
# output_class.confirmation_code_label(ConfirmationCode.orderNumber)
|
||||
|
||||
# if event.seating_plan_id is not None:
|
||||
# output_class.seat_label(Seat.seat)
|
||||
|
||||
# return self._comms().put_item(ClassType.eventTicketClass, class_name, output_class)
|
||||
return output_class._eventTicketClass
|
||||
|
||||
|
||||
class GoogleWalletStyle(PassStyle):
|
||||
platform = GooglePlatform
|
||||
|
||||
@@ -71,7 +71,9 @@ class WalletOutput(BaseTicketOutput):
|
||||
wallet_layout = op.item.walletlayout
|
||||
else:
|
||||
wallet_layout = op.event.wallet_layouts.get(default=True)
|
||||
platform_layout = get_object_or_404(wallet_layout.platform_layouts, platform=self.platform.identifier)
|
||||
platform_layout = get_object_or_404(
|
||||
wallet_layout.platform_layouts, platform=self.platform.identifier
|
||||
)
|
||||
return self.platform.generate(platform_layout.pass_layout, op)
|
||||
|
||||
|
||||
@@ -81,6 +83,48 @@ class GoogleWalletTicketOutput(WalletOutput):
|
||||
download_button_text = "Add to Google Wallet"
|
||||
platform = GooglePlatform
|
||||
|
||||
def get_global_settings(sender, **kwargs):
|
||||
return OrderedDict(
|
||||
[
|
||||
(
|
||||
"wallet_google_issuer_id",
|
||||
forms.CharField(
|
||||
label=_("Google Wallet Issuer/Merchant ID"),
|
||||
help_text=_(
|
||||
"After getting accepted by Google into the Google Pay API for Passes program, "
|
||||
"your Issuer ID can be found in the Merchant center at "
|
||||
"https://wallet.google.com/merchant/walletobjects/"
|
||||
),
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"wallet_google_credentials",
|
||||
forms.FileField(
|
||||
label=_("Google Wallet Service Account Credentials"),
|
||||
help_text=_(
|
||||
"Please paste the contents of the JSON credentials file "
|
||||
"of the service account you tied to your Google Pay API "
|
||||
"for Passes Issuer ID"
|
||||
),
|
||||
required=False,
|
||||
# validators=[validate_json_credentials]
|
||||
),
|
||||
),
|
||||
(
|
||||
"wallet_google_instance_uuid",
|
||||
forms.CharField(
|
||||
label=_("Google Wallet Pass Instance UUID"),
|
||||
help_text=_(
|
||||
"Instance-specific part to be added to the wallet ids"
|
||||
),
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class AppleWalletTicketOutput(WalletOutput):
|
||||
identifier = "wallet_apple"
|
||||
@@ -129,7 +173,7 @@ class AppleWalletTicketOutput(WalletOutput):
|
||||
label=_("Apple Wallet Pass secret key"),
|
||||
required=False,
|
||||
validators=[validate_rsa_privkey],
|
||||
widget=ClearableBasenameFileInput
|
||||
widget=ClearableBasenameFileInput,
|
||||
),
|
||||
),
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user