mirror of
https://github.com/pretix/pretix.git
synced 2026-05-18 17:24:03 +00:00
WIP
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
"build": "npm run build:control -s && npm run build:widget -s",
|
"build": "npm run build:control -s && npm run build:widget -s",
|
||||||
"build:control": "vite build",
|
"build:control": "vite build",
|
||||||
"build:widget": "vite build src/pretix/static/pretixpresale/widget",
|
"build:widget": "vite build src/pretix/static/pretixpresale/widget",
|
||||||
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin",
|
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin src/pretix/plugins/wallet",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import StyleSettings from './style-settings.vue'
|
||||||
|
|
||||||
|
const STYLES = JSON.parse(document.querySelector('#styles')?.textContent ?? '{}')
|
||||||
|
const VARIABLES = JSON.parse(document.querySelector('#variables')?.textContent ?? '{}')
|
||||||
|
const FORM_ERRORS = JSON.parse(document.querySelector('#form_errors')?.textContent ?? '{}')
|
||||||
|
const FORM_DATA = JSON.parse(document.querySelector('#form_data')?.textContent ?? '{}')
|
||||||
|
|
||||||
|
const style = ref<string | null>(FORM_DATA.style ?? null)
|
||||||
|
const name = ref<string>(FORM_DATA.name ?? '')
|
||||||
|
const layout = ref(JSON.parse(FORM_DATA.layout ?? '{}') ?? {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
// TODO: add :key for all `v-for`s
|
||||||
|
//- pre
|
||||||
|
//- code {{ STYLES }}
|
||||||
|
.row
|
||||||
|
.col-md-8
|
||||||
|
.form-group(:class='"name" in FORM_ERRORS ? "has-error" : ""')
|
||||||
|
label.control-label(for="layout-info-name") Name
|
||||||
|
input#layout-info-name.form-control(v-model="name" name="name")
|
||||||
|
|
||||||
|
.form-group(:class='"style" in FORM_ERRORS ? "has-error" : ""')
|
||||||
|
label.control-label(for="layout-info-style") Style
|
||||||
|
select#layout-info-style.form-control(v-model="style" name="style")
|
||||||
|
option(v-for="styleconfig in STYLES" :key="styleconfig.identifier" :value="styleconfig.identifier") {{ styleconfig.name }}
|
||||||
|
|
||||||
|
StyleSettings(v-model="layout" :style="style" :styles="STYLES" :variables="VARIABLES")
|
||||||
|
.col-md-4
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading Preview
|
||||||
|
// TODO: i18n
|
||||||
|
.panel-body
|
||||||
|
// TODO: Preview
|
||||||
|
pre
|
||||||
|
code {{ layout }}
|
||||||
|
input(type="hidden" name="layout" :value="JSON.stringify(layout)")
|
||||||
|
.form-group.submit-group
|
||||||
|
button.btn.btn-primary.btn-save(type="submit") Submit
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import OptionalSelect from './optional-select.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
field: any // TODO
|
||||||
|
overflows: [string, string][],
|
||||||
|
variables: any // TODO
|
||||||
|
}>()
|
||||||
|
const fieldConfig = defineModel<any>({ required: true })
|
||||||
|
|
||||||
|
const overflowOptions = computed(() => {
|
||||||
|
if (props.overflows.length) {
|
||||||
|
return [...props.overflows.map(x => [x.identifier, x.name]), [null, "Do not overflow"]]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function addVariable() {
|
||||||
|
fieldConfig.value.entries.push({"label": null, "value": null})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
//- pre
|
||||||
|
//- code {{ props }}
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
h3.panel-title {{ field.name }}
|
||||||
|
.panel-body
|
||||||
|
.form-group(v-if="props.variables[field.entry_type]")
|
||||||
|
span.text-muted These fields appear somewhere and are visible too.
|
||||||
|
// TODO: for="..." / labeledby?
|
||||||
|
h4 Fields
|
||||||
|
.row.form-group(v-for="n in fieldConfig.entries.length")
|
||||||
|
.col-md-5
|
||||||
|
// TODO: i18n
|
||||||
|
label.control-label Label
|
||||||
|
input.form-control(v-model="fieldConfig.entries[n-1].label")
|
||||||
|
.col-md-6
|
||||||
|
label.control-label Value
|
||||||
|
select.form-control(v-model="fieldConfig.entries[n-1].value")
|
||||||
|
option(v-for="(config, id) in props.variables[field.entry_type]" :key="id" :value="id") {{ config.label }}
|
||||||
|
.col-md-1
|
||||||
|
label.control-label
|
||||||
|
span.sr-only "Delete"
|
||||||
|
button.btn.btn-danger(type="button" @click="fieldConfig.entries.splice(n-1, 1)")
|
||||||
|
i.fa.fa-trash
|
||||||
|
span.sr-only "Delete"
|
||||||
|
button.btn.btn-default(type="button" @click="addVariable")
|
||||||
|
i.fa.fa-plus
|
||||||
|
span.sr-only Add field
|
||||||
|
OptionalSelect(label="Overflow to ..." :choices="overflowOptions" v-model="fieldConfig.overflow")
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useId } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
label: string
|
||||||
|
choices: [string, string][]
|
||||||
|
}>()
|
||||||
|
const modelValue = defineModel<string|null>();
|
||||||
|
const id = useId()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
template(v-if="choices.length >= 1")
|
||||||
|
label(:for="id") {{ props.label }}
|
||||||
|
select.form-control(:id="id" v-model="modelValue")
|
||||||
|
// TODO: persist/v-model
|
||||||
|
option(v-for="choice in props.choices" :key="choice[0]" :value="choice[0]") {{ choice[1] }}
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, watchEffect } from 'vue'
|
||||||
|
import OptionalSelect from './optional-select.vue'
|
||||||
|
import FieldSettings from './field-settings.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
styles: any, // TODO
|
||||||
|
variables: any, // TODO
|
||||||
|
style?: string,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const layout = defineModel()
|
||||||
|
|
||||||
|
const styleData = computed(() => {
|
||||||
|
if (!props.style || !(props.style in props.styles)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return props.styles[props.style]
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// TODO: this seems wrooong
|
||||||
|
if (!('fields' in layout.value)) {
|
||||||
|
layout.value.fields = {}
|
||||||
|
}
|
||||||
|
if (props.style) {
|
||||||
|
for (const field of props.styles[props.style].fields) {
|
||||||
|
if (!(field.identifier in layout.value.fields)) {
|
||||||
|
layout.value.fields[field.identifier] = {entries: JSON.parse(JSON.stringify(field.default_entries)), overflow: null}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
h2.h3 Form Fields
|
||||||
|
FieldSettings(v-if="styleData"
|
||||||
|
v-for="(field, fieldId) in styleData.fields"
|
||||||
|
v-model="layout.fields[field.identifier]"
|
||||||
|
:field="field"
|
||||||
|
:overflows="styleData.fields.slice(fieldId+1).filter(x => x.entry_type === field.entry_type)"
|
||||||
|
:variables="variables"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './components/app.vue'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mount('#editor')
|
||||||
|
|
||||||
|
app.config.errorHandler = (error, _vm, info) => {
|
||||||
|
// vue fatals on errors by default, which is a weird choice
|
||||||
|
// https://github.com/vuejs/core/issues/3525
|
||||||
|
// https://github.com/vuejs/router/discussions/2435
|
||||||
|
console.error('[VUE]', info, error)
|
||||||
|
}
|
||||||
@@ -6,18 +6,22 @@ from django.core.exceptions import ValidationError
|
|||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from .models import WalletLayout
|
from .models import WalletLayout
|
||||||
|
|
||||||
|
|
||||||
class WalletPlatform:
|
class WalletPlatform:
|
||||||
identifier: str
|
identifier: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class ApplePlatform(WalletPlatform):
|
class ApplePlatform(WalletPlatform):
|
||||||
identifier = "apple"
|
identifier = "apple"
|
||||||
name = _("Apple")
|
name = _("Apple")
|
||||||
|
|
||||||
|
|
||||||
class GooglePlatform(WalletPlatform):
|
class GooglePlatform(WalletPlatform):
|
||||||
identifier = "google"
|
identifier = "google"
|
||||||
name = _("Google")
|
name = _("Google")
|
||||||
|
|
||||||
|
|
||||||
class PlaceholderFieldType(enum.Enum):
|
class PlaceholderFieldType(enum.Enum):
|
||||||
TEXT = "text"
|
TEXT = "text"
|
||||||
CODE = "qr"
|
CODE = "qr"
|
||||||
@@ -30,10 +34,11 @@ class PlaceholderFieldType(enum.Enum):
|
|||||||
class PlaceholderField:
|
class PlaceholderField:
|
||||||
type: PlaceholderFieldType
|
type: PlaceholderFieldType
|
||||||
label: LazyI18nString
|
label: LazyI18nString
|
||||||
value: LazyI18nString
|
value: str
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return {'type': self.type.value, 'label': self.label, 'value': self.value}
|
return {"type": self.type.value, "label": self.label.data, "value": self.value}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FieldGroupDefinition:
|
class FieldGroupDefinition:
|
||||||
@@ -44,7 +49,13 @@ class FieldGroupDefinition:
|
|||||||
max_entries: int | None = None
|
max_entries: int | None = None
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return {"identifier": self.identifier, "name": self.name, "min_entries": self.min_entries, "max_entries": self.max_entries}
|
return {
|
||||||
|
"identifier": self.identifier,
|
||||||
|
"name": self.name,
|
||||||
|
"entry_type": self.entry_type.value,
|
||||||
|
"min_entries": self.min_entries,
|
||||||
|
"max_entries": self.max_entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -54,7 +65,7 @@ class PlaceholderFieldGroup(FieldGroupDefinition):
|
|||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
asdict = super().asdict()
|
asdict = super().asdict()
|
||||||
asdict['default_entries'] = [x.asdict() for x in self.default_entries]
|
asdict["default_entries"] = [x.asdict() for x in self.default_entries]
|
||||||
return asdict
|
return asdict
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +78,7 @@ class PredefinedFieldGroup(FieldGroupDefinition):
|
|||||||
|
|
||||||
class PassStyle:
|
class PassStyle:
|
||||||
identifier: str # unique within platform
|
identifier: str # unique within platform
|
||||||
name: str
|
name: str
|
||||||
platform: Literal["apple"] | Literal["google"]
|
platform: Literal["apple"] | Literal["google"]
|
||||||
fields: list[FieldGroupDefinition]
|
fields: list[FieldGroupDefinition]
|
||||||
# preview_image: str # TODO: preview
|
# preview_image: str # TODO: preview
|
||||||
@@ -94,18 +105,28 @@ class AppleWalletEventTicket(PassStyle):
|
|||||||
min_entries=1,
|
min_entries=1,
|
||||||
max_entries=1,
|
max_entries=1,
|
||||||
default_entries=[
|
default_entries=[
|
||||||
PlaceholderField(PlaceholderFieldType.IMAGE, "logo", "event:image")
|
PlaceholderField(PlaceholderFieldType.IMAGE, LazyI18nString("logo"), "event:image")
|
||||||
],
|
],
|
||||||
entry_type=PlaceholderFieldType.IMAGE,
|
entry_type=PlaceholderFieldType.IMAGE,
|
||||||
),
|
),
|
||||||
PlaceholderFieldGroup(identifier="primary", name=_("Primary"), min_entries=1, max_entries=1),
|
PlaceholderFieldGroup(
|
||||||
|
identifier="primary",
|
||||||
|
name=_("Primary"),
|
||||||
|
min_entries=1,
|
||||||
|
max_entries=1,
|
||||||
|
default_entries=[
|
||||||
|
PlaceholderField(PlaceholderFieldType.TEXT, LazyI18nString("Ticket type"), "item")
|
||||||
|
],
|
||||||
|
),
|
||||||
PlaceholderFieldGroup(
|
PlaceholderFieldGroup(
|
||||||
identifier="secondary", name=_("Secondary"), max_entries=4
|
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."
|
), # 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(
|
PlaceholderFieldGroup(
|
||||||
identifier="headers", name=_("Header"), max_entries=3
|
identifier="headers", name=_("Header"), max_entries=3
|
||||||
), # TODO: header image
|
), # TODO: header image
|
||||||
PlaceholderFieldGroup(identifier="auxillary", name=_("Auxillary"), max_entries=4),
|
PlaceholderFieldGroup(
|
||||||
|
identifier="auxillary", name=_("Auxillary"), max_entries=4
|
||||||
|
),
|
||||||
PlaceholderFieldGroup(identifier="back", name=_("Back")),
|
PlaceholderFieldGroup(identifier="back", name=_("Back")),
|
||||||
]
|
]
|
||||||
# preview_image = "apple/event_ticket.svg"
|
# preview_image = "apple/event_ticket.svg"
|
||||||
@@ -117,13 +138,16 @@ class GoogleWalletEventTicket(PassStyle):
|
|||||||
platform = "google"
|
platform = "google"
|
||||||
fields = [
|
fields = [
|
||||||
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
|
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
|
||||||
PlaceholderFieldGroup(identifier="qrcode", name=_("QR-Code"), entry_type=PlaceholderFieldType.CODE),
|
PlaceholderFieldGroup(
|
||||||
|
identifier="qrcode", name=_("QR-Code"), entry_type=PlaceholderFieldType.CODE
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
AVAILABLE_PLATFORMS = {"apple": ApplePlatform, "google": GooglePlatform}
|
AVAILABLE_PLATFORMS = {"apple": ApplePlatform, "google": GooglePlatform}
|
||||||
AVAILABLE_STYLES = [AppleWalletEventTicket(), GoogleWalletEventTicket()]
|
AVAILABLE_STYLES = [AppleWalletEventTicket(), GoogleWalletEventTicket()]
|
||||||
|
|
||||||
|
|
||||||
def get_platforms_with_styles():
|
def get_platforms_with_styles():
|
||||||
platforms_with_styles = {}
|
platforms_with_styles = {}
|
||||||
for style in AVAILABLE_STYLES:
|
for style in AVAILABLE_STYLES:
|
||||||
@@ -133,6 +157,7 @@ def get_platforms_with_styles():
|
|||||||
platforms_with_styles[platform][style.identifier] = style
|
platforms_with_styles[platform][style.identifier] = style
|
||||||
return platforms_with_styles
|
return platforms_with_styles
|
||||||
|
|
||||||
|
|
||||||
def get_platform_styles(platform):
|
def get_platform_styles(platform):
|
||||||
platform_styles = {}
|
platform_styles = {}
|
||||||
for style in AVAILABLE_STYLES:
|
for style in AVAILABLE_STYLES:
|
||||||
@@ -140,9 +165,11 @@ def get_platform_styles(platform):
|
|||||||
platform_styles[style.identifier] = style
|
platform_styles[style.identifier] = style
|
||||||
return platform_styles
|
return platform_styles
|
||||||
|
|
||||||
|
|
||||||
def get_platforms():
|
def get_platforms():
|
||||||
return AVAILABLE_PLATFORMS
|
return AVAILABLE_PLATFORMS
|
||||||
|
|
||||||
|
|
||||||
class PassLayout:
|
class PassLayout:
|
||||||
style: PassStyle
|
style: PassStyle
|
||||||
layout: dict
|
layout: dict
|
||||||
@@ -156,15 +183,19 @@ class PassLayout:
|
|||||||
|
|
||||||
def validate_fields(self):
|
def validate_fields(self):
|
||||||
style_fields = self.style.fields
|
style_fields = self.style.fields
|
||||||
if 'fields' not in self.layout:
|
if "fields" not in self.layout:
|
||||||
raise ValidationError(_("Layout did not contain any fields"))
|
raise ValidationError(_("Layout did not contain any fields"))
|
||||||
layout_fields = self.layout['fields']
|
layout_fields = self.layout["fields"]
|
||||||
if not isinstance(layout_fields, dict):
|
if not isinstance(layout_fields, dict):
|
||||||
raise ValidationError(_("'fields' must be dict"))
|
raise ValidationError(_("'fields' must be dict"))
|
||||||
|
|
||||||
for fieldgroup in style_fields:
|
for fieldgroup in style_fields:
|
||||||
layout_field_data = layout_fields.get(fieldgroup.identifier, [])
|
layout_field_data = layout_fields.get(fieldgroup.identifier, {})
|
||||||
if fieldgroup.min_entries and fieldgroup.min_entries < len(layout_field_data):
|
if fieldgroup.min_entries and fieldgroup.min_entries < len(
|
||||||
raise ValidationError(_("At least {min_entries} must be specified for {name}").format(min_entries=fieldgroup.min_entries, name=fieldgroup.name))
|
layout_field_data.get('entries')
|
||||||
|
):
|
||||||
|
raise ValidationError(
|
||||||
|
_("At least {min_entries} must be specified for {name}").format(
|
||||||
|
min_entries=fieldgroup.min_entries, name=fieldgroup.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,18 +2,23 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load money %}
|
{% load money %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load vite %}
|
||||||
|
{% load static %}
|
||||||
|
{% load compress %}
|
||||||
|
|
||||||
|
|
||||||
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
{% block title %}{% trans "Wallet layouts" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<pre><code>{{ styles }}</code></pre>
|
<h1>{% trans "Edit layout" %}</h1>
|
||||||
<pre><code>{{ variables }}</code></pre>
|
|
||||||
{{ styles|json_script:"styles" }}
|
{{ styles|json_script:"styles" }}
|
||||||
{{ variables|json_script:"variables" }}
|
{{ variables|json_script:"variables" }}
|
||||||
<form method="post">
|
{{ form.errors|json_script:"form_errors" }}
|
||||||
{% csrf_token %}
|
<form method="post">
|
||||||
{% bootstrap_form form %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-default">
|
<div id="editor"></div>
|
||||||
{% trans "Submit " %}
|
</form>
|
||||||
</button>
|
{% vite_hmr %}
|
||||||
</form>
|
{% vite_asset "src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts" %}
|
||||||
|
{% csrf_token %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load escapejson %}
|
||||||
|
{% load formset_tags %}
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for form in formset %}
|
||||||
|
<div class="row formset-row" data-formset-form>
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.entry layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<i class="fa fa-warning hidden" data-toggle="tooltip" title=""></i>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-default hidden" data-edit-value-map data-toggle="modal"
|
||||||
|
data-target="#editValueMapModal" title="{% trans "Edit value mapping" %}">
|
||||||
|
<i class="fa fa-edit"></i></button>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||||
|
<i class="fa fa-arrow-up"></i></button>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||||
|
<i class="fa fa-arrow-down"></i></button>
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
{% with form=formset.empty_form %}
|
||||||
|
<div class="row formset-row" data-formset-form>
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.entry layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<i class="fa fa-warning hidden" data-toggle="tooltip" title=""></i>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-default hidden" data-edit-value-map data-toggle="modal"
|
||||||
|
data-target="#editValueMapModal" title="{% trans "Edit value mapping" %}">
|
||||||
|
<i class="fa fa-edit"></i></button>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||||
|
<i class="fa fa-arrow-up"></i></button>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||||
|
<i class="fa fa-arrow-down"></i></button>
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endescapescript %}
|
||||||
|
</script>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-add>
|
||||||
|
<i class="fa fa-plus"></i> {% trans "Add field" %}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% if external_fields %}
|
||||||
|
{{ external_fields|json_script:external_fields_id }}
|
||||||
|
{% endif %}
|
||||||
10
src/pretix/plugins/wallet/templatetags/wallet.py
Normal file
10
src/pretix/plugins/wallet/templatetags/wallet.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django import template
|
||||||
|
|
||||||
|
from ..models import WalletLayout
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def platform_layouts(platform, event):
|
||||||
|
return WalletLayout.objects.filter(event=event, platform=platform.identifier)
|
||||||
Reference in New Issue
Block a user