mirror of
https://github.com/pretix/pretix.git
synced 2026-08-08 10:27:49 +00:00
WIP preview
This commit is contained in:
@@ -3,7 +3,7 @@ import { computed, inject, ref, watchEffect } from "vue";
|
||||
import StyleSettings from "./style-settings.vue";
|
||||
import Select from "./input/select.vue";
|
||||
import Input from "./input/input.vue";
|
||||
import PassPreview from "./pass-preview.vue";
|
||||
import PassPreview from "./preview/pass-preview.vue";
|
||||
import { StoreKey } from "../walletStore";
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
+3
-3
@@ -52,12 +52,12 @@ watchEffect(() => {
|
||||
table.table.table-hover
|
||||
thead
|
||||
tr
|
||||
th.col-md-5(v-if="fieldgroup.labels") {{ gettext('Label') }}
|
||||
th(:class="'col-md-' + (fieldgroup.labels ? '6' : '11')") {{ gettext('Content') }}
|
||||
th.col-md-5(v-if="fieldgroup.display == 'with_label'") {{ gettext('Label') }}
|
||||
th(:class="'col-md-' + (fieldgroup.display == 'with_label' ? '6' : '11')") {{ gettext('Content') }}
|
||||
th.col-xs-1
|
||||
tbody
|
||||
tr(v-for="n,i in fieldConfig.entries.length" :key="i")
|
||||
td(v-if="fieldgroup.labels")
|
||||
td(v-if="fieldgroup.display == 'with_label'")
|
||||
.i18n-form-group
|
||||
I18nInput(v-model="fieldConfig.entries[n-1].label")
|
||||
td
|
||||
|
||||
+3
-11
@@ -6,19 +6,10 @@ import PredefinedFieldSettings from "./predefined-field-settings.vue";
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
style?: Style;
|
||||
style?: Style;
|
||||
}>();
|
||||
|
||||
const layout = defineModel<LayoutData>();
|
||||
|
||||
watchEffect(() => {
|
||||
if (layout.value === undefined) {
|
||||
return
|
||||
}
|
||||
if (layout.value.fieldgroups === undefined) {
|
||||
layout.value.fieldgroups = {};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
@@ -29,7 +20,8 @@ watchEffect(() => {
|
||||
v-if="fieldgroup.type == 'placeholder'"
|
||||
v-model="layout.fieldgroups[fieldgroup.identifier]"
|
||||
:fieldgroup="fieldgroup"
|
||||
:overflows="props.style.fieldgroups.slice(fieldgroupId + 1).filter(x => x.type == 'placeholder' && x.content_type === fieldgroup.content_type)"
|
||||
:overflows="props.style.fieldgroups.slice(fieldgroupId + 1) \
|
||||
.filter(x => x.type == 'placeholder' && x.content_type === fieldgroup.content_type)"
|
||||
)
|
||||
PredefinedFieldSettings(v-else-if="fieldgroup.type == 'predefined'"
|
||||
v-model="layout.fieldgroups[fieldgroup.identifier]"
|
||||
|
||||
@@ -7,11 +7,13 @@ type BaseFieldGroupDefinition = {
|
||||
|
||||
type FieldGroupDefinition = PlaceholderFieldGroupDefinition | PredefinedFieldGroupDefinition;
|
||||
|
||||
type FieldGroupDisplay = 'plain' | 'with_label' | 'code';
|
||||
|
||||
type PlaceholderFieldGroupDefinition = BaseFieldGroupDefinition & {
|
||||
type: 'placeholder';
|
||||
content_type: FieldContentType;
|
||||
default_entries: FieldEntry[];
|
||||
labels: boolean;
|
||||
display: FieldGroupDisplay;
|
||||
min_entries: number|null;
|
||||
max_entries: number|null;
|
||||
}
|
||||
@@ -46,6 +48,7 @@ type Style = {
|
||||
|
||||
type Variable = {
|
||||
label: string
|
||||
editor_sample: I18nString;
|
||||
};
|
||||
|
||||
type Platform = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { i18nstringLocalize } from "./helpers.js";
|
||||
import { createStore } from "./lib/store.ts";
|
||||
import { nextTick, type InjectionKey } from "vue";
|
||||
|
||||
@@ -28,7 +29,7 @@ export function createWalletStore(config: {
|
||||
}
|
||||
throw "Unknown platform";
|
||||
},
|
||||
currentPlatformLayout() {
|
||||
currentPlatformLayout(): PlatformLayout {
|
||||
if (!this.walletLayout) {
|
||||
throw "currentPlatformLayout access before store was loaded";
|
||||
}
|
||||
@@ -45,6 +46,67 @@ export function createWalletStore(config: {
|
||||
this.walletLayout.platform_layouts.push(newLayout);
|
||||
return newLayout;
|
||||
},
|
||||
|
||||
currentLayoutFieldContent() {
|
||||
const content = {};
|
||||
const group_defs =
|
||||
this.currentPlatformStyles[this.currentPlatformLayout.style]
|
||||
.fieldgroups;
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
content[fieldgroup.identifier] = [];
|
||||
const layout_group: PlaceholderFieldGroupConfig =
|
||||
this.currentPlatformLayout.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
];
|
||||
for (const entry of layout_group.entries) {
|
||||
const placeholder =
|
||||
entry.type === "placeholder" ? this.variables[fieldgroup.content_type][entry.content] : null;
|
||||
|
||||
let label = i18nstringLocalize(entry.label);
|
||||
if (placeholder && !label) {
|
||||
label = i18nstringLocalize(placeholder.label);
|
||||
}
|
||||
|
||||
let value = null;
|
||||
if (entry.type == "custom") {
|
||||
value = i18nstringLocalize(entry.content);
|
||||
} else if (entry.type == "placeholder") {
|
||||
value = placeholder.editor_sample;
|
||||
}
|
||||
content[fieldgroup.identifier].push({
|
||||
entry,
|
||||
label,
|
||||
content: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
const layout_group: PlaceholderFieldGroupConfig =
|
||||
this.currentPlatformLayout.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
];
|
||||
if (
|
||||
fieldgroup.max_entries &&
|
||||
content[fieldgroup.identifier].length > fieldgroup.max_entries
|
||||
) {
|
||||
const overflow = content[fieldgroup.identifier].slice(
|
||||
fieldgroup.max_entries,
|
||||
);
|
||||
content[fieldgroup.identifier] = content[
|
||||
fieldgroup.identifier
|
||||
].slice(0, fieldgroup.max_entries);
|
||||
if (layout_group.overflow) {
|
||||
content[layout_group.overflow].splice(0, 0, ...overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
load() {
|
||||
@@ -118,7 +180,12 @@ export function createWalletStore(config: {
|
||||
|
||||
for (const key of keysToDefault) {
|
||||
if (newFieldGroups[key].type == "placeholder") {
|
||||
this.currentPlatformLayout.layout.fieldgroups[key] = {overflow: null, entries: JSON.parse(JSON.stringify(newFieldGroups[key].default_entries))};
|
||||
this.currentPlatformLayout.layout.fieldgroups[key] = {
|
||||
overflow: null,
|
||||
entries: JSON.parse(
|
||||
JSON.stringify(newFieldGroups[key].default_entries),
|
||||
),
|
||||
};
|
||||
} else {
|
||||
this.currentPlatformLayout.layout.fieldgroups[key] = {};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .base import (
|
||||
FieldEntryType,
|
||||
FieldGroupDisplay,
|
||||
ImageFieldGroup,
|
||||
PlaceholderFieldGroup,
|
||||
PredefinedFieldGroup,
|
||||
@@ -216,7 +217,6 @@ class AppleWalletStyle(PassStyle):
|
||||
pkpass.add_file("pass.json", json.dumps(pass_json))
|
||||
return pkpass.finish()
|
||||
|
||||
|
||||
class AppleWalletEventTicket(AppleWalletStyle):
|
||||
identifier = "event_1"
|
||||
name = _("Event Ticket Layout 1")
|
||||
@@ -226,7 +226,6 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
name=_("Icon"),
|
||||
min_entries=0,
|
||||
max_entries=1,
|
||||
labels=False,
|
||||
default_entries=[
|
||||
PlaceholderFieldEntry(
|
||||
content="poweredby",
|
||||
@@ -238,7 +237,6 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
name=_("Logo"),
|
||||
min_entries=0,
|
||||
max_entries=1,
|
||||
labels=False,
|
||||
default_entries=[
|
||||
PlaceholderFieldEntry(
|
||||
content="poweredby",
|
||||
@@ -249,7 +247,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
identifier="logo_text",
|
||||
name=_("Logo text"),
|
||||
max_entries=1,
|
||||
labels=False,
|
||||
display=FieldGroupDisplay.PLAIN,
|
||||
default_entries=[],
|
||||
),
|
||||
TextFieldGroup(
|
||||
@@ -275,7 +273,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
identifier="code",
|
||||
name=_("QR-Code"),
|
||||
max_entries=1,
|
||||
labels=False,
|
||||
display=FieldGroupDisplay.CODE,
|
||||
default_entries=[
|
||||
PlaceholderFieldEntry(
|
||||
content="secret",
|
||||
|
||||
@@ -12,6 +12,10 @@ class FieldGroupType(enum.Enum):
|
||||
PLACEHOLDER = "placeholder"
|
||||
PREDEFINED = "predefined"
|
||||
|
||||
class FieldGroupDisplay(enum.Enum):
|
||||
PLAIN = "plain"
|
||||
WITH_LABEL = "with_label"
|
||||
CODE = "code"
|
||||
|
||||
class FieldGroup:
|
||||
type: FieldGroupType
|
||||
@@ -106,7 +110,7 @@ class PlaceholderFieldGroup(FieldGroup):
|
||||
type = FieldGroupType.PLACEHOLDER
|
||||
content_type: FieldContentType
|
||||
default_entries: list[FieldEntry]
|
||||
labels: bool
|
||||
display: FieldGroupDisplay
|
||||
min_entries: int | None
|
||||
max_entries: int | None
|
||||
|
||||
@@ -115,19 +119,19 @@ class PlaceholderFieldGroup(FieldGroup):
|
||||
identifier: str,
|
||||
name: str,
|
||||
content_type: FieldContentType,
|
||||
description: str=None,
|
||||
description: str="",
|
||||
required=False,
|
||||
default_entries=None,
|
||||
min_entries=None,
|
||||
max_entries=None,
|
||||
labels=True,
|
||||
display=FieldGroupDisplay.WITH_LABEL,
|
||||
):
|
||||
super().__init__(identifier, name, description, required)
|
||||
self.content_type = content_type
|
||||
self.default_entries = default_entries or []
|
||||
self.min_entries = min_entries
|
||||
self.max_entries = max_entries
|
||||
self.labels = labels
|
||||
self.display = display
|
||||
|
||||
if self.required and (self.min_entries is None or self.min_entries < 1):
|
||||
self.min_entries = 1
|
||||
@@ -137,7 +141,7 @@ class PlaceholderFieldGroup(FieldGroup):
|
||||
**super().asdict(),
|
||||
"content_type": self.content_type.value,
|
||||
"default_entries": [x.asdict() for x in self.default_entries],
|
||||
"labels": self.labels,
|
||||
"display": self.display.value,
|
||||
"min_entries": self.min_entries,
|
||||
"max_entries": self.max_entries,
|
||||
}
|
||||
@@ -172,7 +176,7 @@ class PlaceholderFieldGroup(FieldGroup):
|
||||
|
||||
def entries_schema(self, placeholders: list[str]):
|
||||
baseprops = {}
|
||||
if self.labels:
|
||||
if self.display == FieldGroupDisplay.WITH_LABEL:
|
||||
baseprops["label"] = {"$ref": "#/$defs/I18nString"}
|
||||
|
||||
schema = {
|
||||
@@ -198,7 +202,7 @@ class PlaceholderFieldGroup(FieldGroup):
|
||||
"required": ["type", "content"],
|
||||
},
|
||||
}
|
||||
if self.labels:
|
||||
if self.display == FieldGroupDisplay.WITH_LABEL:
|
||||
schema["items"]["required"].append("label")
|
||||
if self.min_entries is not None:
|
||||
schema["minItems"] = self.min_entries
|
||||
@@ -216,9 +220,10 @@ class TextFieldGroup(PlaceholderFieldGroup):
|
||||
|
||||
class ImageFieldGroup(PlaceholderFieldGroup):
|
||||
content_type = FieldContentType.IMAGE
|
||||
display = FieldGroupDisplay.PLAIN
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(content_type=self.content_type, **kwargs)
|
||||
super().__init__(content_type=self.content_type, display=self.display, **kwargs)
|
||||
|
||||
|
||||
class PassStyle:
|
||||
@@ -299,11 +304,11 @@ class PassStyle:
|
||||
if group.identifier in layout["fieldgroups"]:
|
||||
for field in layout["fieldgroups"][group.identifier]["entries"]:
|
||||
field_entry = {}
|
||||
if group.labels:
|
||||
if group.display == FieldGroupDisplay.WITH_LABEL:
|
||||
field_entry["label"] = LazyI18nString(field["label"])
|
||||
if field["type"] == FieldEntryType.PLACEHOLDER.value:
|
||||
label, field_entry["value"] = self.render_placeholder(context, group.content_type.value, field['content'])
|
||||
if group.labels and not str(field_entry['label']) and label:
|
||||
if group.display == FieldGroupDisplay.WITH_LABEL and not str(field_entry['label']) and label:
|
||||
field_entry['label'] = LazyI18nString(label)
|
||||
|
||||
elif field["type"] == FieldEntryType.CUSTOM.value:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .base import PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform
|
||||
from .base import FieldGroupDisplay, PassStyle, PredefinedFieldGroup, TextFieldGroup, WalletPlatform
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class GooglePlatform(WalletPlatform):
|
||||
@@ -16,5 +16,5 @@ class GoogleWalletEventTicket(PassStyle):
|
||||
platform = GooglePlatform
|
||||
fieldgroups = [
|
||||
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
|
||||
TextFieldGroup(identifier="qrcode", name=_("QR-Code"), labels=False),
|
||||
TextFieldGroup(identifier="qrcode", name=_("QR-Code"), display=FieldGroupDisplay.PLAIN),
|
||||
]
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
<div id="editor" data-layout-id="{{ object.pk }}"></div>
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/plugins/wallet/static/pretixplugins/wallet/main.ts" %}
|
||||
|
||||
{% csrf_token %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -19,17 +19,37 @@ from django.shortcuts import redirect
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
from pretix.helpers.models import modelclone
|
||||
from .models import WalletLayout
|
||||
from .styles import AVAILABLE_STYLES, AVAILABLE_PLATFORMS, AVAILABLE_STYLES_DICT, PassLayout
|
||||
from .styles import (
|
||||
AVAILABLE_STYLES,
|
||||
AVAILABLE_PLATFORMS,
|
||||
AVAILABLE_STYLES_DICT,
|
||||
PassLayout,
|
||||
)
|
||||
from django.contrib import messages
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.utils.functional import cached_property
|
||||
from django.templatetags.static import static
|
||||
|
||||
def get_layout_variables(event):
|
||||
return {
|
||||
"text": get_variables(event),
|
||||
"image": get_images(event)
|
||||
| {"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
|
||||
| {
|
||||
"poweredby": {
|
||||
"label": _("pretix-Logo"),
|
||||
"evaluate": lambda *_: open(
|
||||
finders.find("pretix_passbook/logo.png"), "rb"
|
||||
),
|
||||
"editor_sample": static("pretix_passbook/logo.png")
|
||||
},
|
||||
"poweredby_icon": {
|
||||
"label": _("pretix-Icon"),
|
||||
"evaluate": lambda *_: open(
|
||||
finders.find("pretix_passbook/icon.png"), "rb"
|
||||
),
|
||||
"editor_sample": static("pretix_passbook/icon.png")
|
||||
},
|
||||
}, # TODO: image upload
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +62,7 @@ def get_editor_variables(event):
|
||||
for t, vs in get_layout_variables(event).items()
|
||||
}
|
||||
|
||||
|
||||
class WalletLayoutMixin:
|
||||
model = WalletLayout
|
||||
permission = "event.settings.general:write"
|
||||
@@ -51,6 +72,7 @@ class WalletLayoutMixin:
|
||||
def get_queryset(self):
|
||||
return self.request.event.wallet_layouts.all()
|
||||
|
||||
|
||||
class LayoutListView(WalletLayoutMixin, EventPermissionRequiredMixin, ListView):
|
||||
template_name = "pretixplugins/wallet/layout_list.html"
|
||||
|
||||
@@ -64,13 +86,16 @@ class LayoutEditorView(LayoutDetailView):
|
||||
|
||||
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['platforms'] = [{
|
||||
context["platforms"] = [
|
||||
{
|
||||
"identifier": platform.identifier,
|
||||
"name": platform.name,
|
||||
"styles": {
|
||||
style.identifier: style.asdict() for style in AVAILABLE_STYLES.get(platform.identifier)
|
||||
}
|
||||
} for platform in AVAILABLE_PLATFORMS
|
||||
style.identifier: style.asdict()
|
||||
for style in AVAILABLE_STYLES.get(platform.identifier)
|
||||
},
|
||||
}
|
||||
for platform in AVAILABLE_PLATFORMS
|
||||
]
|
||||
# context["styles"] = {
|
||||
# style.identifier: style.asdict() for style in self.get_platform_styles()
|
||||
@@ -109,15 +134,15 @@ class LayoutCreateView(WalletLayoutMixin, EventPermissionRequiredMixin, CreateVi
|
||||
for pl in self.copy_from.platform_layouts.all():
|
||||
modelclone(pl, parent=self.object).save()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
def get_form_kwargs(self) -> dict[str, Any]:
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["event"] = self.request.event
|
||||
|
||||
if self.copy_from:
|
||||
kwargs['instance'] = modelclone(self.copy_from, default=False)
|
||||
kwargs.setdefault('initial', {})
|
||||
|
||||
kwargs["instance"] = modelclone(self.copy_from, default=False)
|
||||
kwargs.setdefault("initial", {})
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
@@ -138,6 +163,7 @@ class LayoutCreateView(WalletLayoutMixin, EventPermissionRequiredMixin, CreateVi
|
||||
except WalletLayout.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
class LayoutPreviewView(EventPermissionRequiredMixin, View):
|
||||
permission = "event.settings.general:write"
|
||||
|
||||
@@ -158,7 +184,10 @@ class LayoutPreviewView(EventPermissionRequiredMixin, View):
|
||||
style = AVAILABLE_STYLES_DICT[platform_id][style_id]
|
||||
|
||||
layout = json.loads(layout)
|
||||
with rolledback_transaction(), language(request.event.settings.locale, request.event.settings.region):
|
||||
with (
|
||||
rolledback_transaction(),
|
||||
language(request.event.settings.locale, request.event.settings.region),
|
||||
):
|
||||
p = get_preview_position(request.event)
|
||||
layout = PassLayout(style=style, layout=layout)
|
||||
context = {"placeholders": get_layout_variables(event)}
|
||||
@@ -167,9 +196,10 @@ class LayoutPreviewView(EventPermissionRequiredMixin, View):
|
||||
fname, mimet, data = platform.generate(layout, p)
|
||||
resp = HttpResponse(data, content_type=mimet)
|
||||
ftype = fname.split(".")[-1]
|
||||
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
resp["Content-Disposition"] = (
|
||||
'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
)
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
class LayoutSetDefault(LayoutDetailView):
|
||||
@@ -178,38 +208,45 @@ class LayoutSetDefault(LayoutDetailView):
|
||||
obj = self.get_object()
|
||||
request.event.wallet_layouts.exclude(pk=obj.pk).update(default=False)
|
||||
obj.default = True
|
||||
obj.save(update_fields=['default'])
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
obj.save(update_fields=["default"])
|
||||
messages.success(self.request, _("Your changes have been saved."))
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('plugins:wallet:index', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
return reverse(
|
||||
"plugins:wallet:index",
|
||||
kwargs={
|
||||
"organizer": self.request.event.organizer.slug,
|
||||
"event": self.request.event.slug,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class LayoutDelete(WalletLayoutMixin, DeleteView):
|
||||
template_name = 'pretixplugins/wallet/delete.html'
|
||||
template_name = "pretixplugins/wallet/delete.html"
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('plugins:wallet:index', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
return reverse(
|
||||
"plugins:wallet:index",
|
||||
kwargs={
|
||||
"organizer": self.request.event.organizer.slug,
|
||||
"event": self.request.event.slug,
|
||||
},
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.object = self.get_object()
|
||||
self.object.log_action(action='pretix.plugins.wallet.layout.deleted', user=self.request.user)
|
||||
self.object.log_action(
|
||||
action="pretix.plugins.wallet.layout.deleted", user=self.request.user
|
||||
)
|
||||
self.object.delete()
|
||||
|
||||
if not self.request.event.wallet_layouts.filter(default=True).exists():
|
||||
f = self.request.event.wallet_layouts.first()
|
||||
if f:
|
||||
f.default = True
|
||||
f.save(update_fields=['default'])
|
||||
f.save(update_fields=["default"])
|
||||
|
||||
messages.success(self.request, _('The selected layout been deleted.'))
|
||||
messages.success(self.request, _("The selected layout been deleted."))
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
Reference in New Issue
Block a user