forked from CGM_Public/pretix_original
Add more features to custom meta properties (#1922)
This commit is contained in:
@@ -265,15 +265,32 @@ class EventMetaValueForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.property = kwargs.pop('property')
|
||||
self.disabled = kwargs.pop('disabled')
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.property.allowed_values:
|
||||
self.fields['value'] = forms.ChoiceField(
|
||||
label=self.property.name,
|
||||
choices=[
|
||||
('', _('Default ({value})').format(value=self.property.default) if self.property.default else ''),
|
||||
] + [(a.strip(), a.strip()) for a in self.property.allowed_values.splitlines()],
|
||||
)
|
||||
else:
|
||||
self.fields['value'].label = self.property.name
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.property.default
|
||||
self.fields['value'].widget.attrs['data-typeahead-url'] = (
|
||||
reverse('control:events.meta.typeahead') + '?' + urlencode({
|
||||
'property': self.property.name,
|
||||
'organizer': self.property.organizer.slug,
|
||||
})
|
||||
)
|
||||
self.fields['value'].required = False
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.property.default
|
||||
self.fields['value'].widget.attrs['data-typeahead-url'] = (
|
||||
reverse('control:events.meta.typeahead') + '?' + urlencode({
|
||||
'property': self.property.name,
|
||||
'organizer': self.property.organizer.slug,
|
||||
})
|
||||
)
|
||||
if self.disabled:
|
||||
self.fields['value'].widget.attrs['readonly'] = 'readonly'
|
||||
|
||||
def clean_slug(self):
|
||||
if self.disabled:
|
||||
return self.instance.value if self.instance else None
|
||||
return self.cleaned_data['slug']
|
||||
|
||||
class Meta:
|
||||
model = EventMetaValue
|
||||
|
||||
@@ -13,7 +13,9 @@ from pretix.api.models import WebHook
|
||||
from pretix.api.webhooks import get_all_webhook_events
|
||||
from pretix.base.forms import I18nModelForm, SettingsForm
|
||||
from pretix.base.forms.widgets import SplitDateTimePickerWidget
|
||||
from pretix.base.models import Device, Gate, GiftCard, Organizer, Team
|
||||
from pretix.base.models import (
|
||||
Device, EventMetaProperty, Gate, GiftCard, Organizer, Team,
|
||||
)
|
||||
from pretix.control.forms import ExtFileField, SplitDateTimeField
|
||||
from pretix.control.forms.event import SafeEventMultipleChoiceField
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
@@ -125,7 +127,8 @@ class OrganizerUpdateForm(OrganizerForm):
|
||||
|
||||
class EventMetaPropertyForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ['name', 'default']
|
||||
model = EventMetaProperty
|
||||
fields = ['name', 'default', 'required', 'protected', 'allowed_values']
|
||||
widgets = {
|
||||
'default': forms.TextInput()
|
||||
}
|
||||
|
||||
@@ -162,15 +162,32 @@ class SubEventMetaValueForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.property = kwargs.pop('property')
|
||||
self.default = kwargs.pop('default', None)
|
||||
self.disabled = kwargs.pop('disabled')
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.property.allowed_values:
|
||||
self.fields['value'] = forms.ChoiceField(
|
||||
label=self.property.name,
|
||||
choices=[
|
||||
('', _('Default ({value})').format(value=self.default or self.property.default) if self.default or self.property.default else ''),
|
||||
] + [(a.strip(), a.strip()) for a in self.property.allowed_values.splitlines()],
|
||||
)
|
||||
else:
|
||||
self.fields['value'].label = self.property.name
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.default or self.property.default
|
||||
self.fields['value'].widget.attrs['data-typeahead-url'] = (
|
||||
reverse('control:events.meta.typeahead') + '?' + urlencode({
|
||||
'property': self.property.name,
|
||||
'organizer': self.property.organizer.slug,
|
||||
})
|
||||
)
|
||||
self.fields['value'].required = False
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.default or self.property.default
|
||||
self.fields['value'].widget.attrs['data-typeahead-url'] = (
|
||||
reverse('control:events.meta.typeahead') + '?' + urlencode({
|
||||
'property': self.property.name,
|
||||
'organizer': self.property.organizer.slug,
|
||||
})
|
||||
)
|
||||
if self.disabled:
|
||||
self.fields['value'].widget.attrs['readonly'] = 'readonly'
|
||||
|
||||
def clean_slug(self):
|
||||
if self.disabled:
|
||||
return self.instance.value if self.instance else None
|
||||
return self.cleaned_data['slug']
|
||||
|
||||
class Meta:
|
||||
model = SubEventMetaValue
|
||||
|
||||
@@ -427,6 +427,13 @@ def get_organizer_navigation(request):
|
||||
}),
|
||||
'active': url.url_name == 'organizer.edit',
|
||||
},
|
||||
{
|
||||
'label': _('Event metadata'),
|
||||
'url': reverse('control:organizer.properties', kwargs={
|
||||
'organizer': request.organizer.slug
|
||||
}),
|
||||
'active': url.url_name.startswith('organizer.propert'),
|
||||
},
|
||||
]
|
||||
})
|
||||
if 'can_change_teams' in request.orgapermset:
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="inline" %}
|
||||
{% bootstrap_form form layout="inline" error_types="all" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -70,65 +70,6 @@
|
||||
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Event metadata" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can here define a set of metadata properties (i.e. variables) that you can later set for your
|
||||
events and re-use in places like ticket layouts. This is an useful timesaver if you create lots and
|
||||
lots of events.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<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" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-6">
|
||||
{% bootstrap_field form.default layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 col-lg-1 text-right flip">
|
||||
<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 %}
|
||||
<div class="row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-6">
|
||||
{% bootstrap_field formset.empty_form.default layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 col-lg-1 text-right flip">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add property" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p>{% blocktrans %}Are you sure you want to delete the gate?{% endblocktrans %}
|
||||
</p>
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:organizer.teams" organizer=request.organizer.slug %}" class="btn btn-default btn-cancel">
|
||||
<a href="{% url "control:organizer.gates" organizer=request.organizer.slug %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save">
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
<h1>{% trans "Event metadata" %}</h1>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can here define a set of metadata properties (i.e. variables) that you can later set for your
|
||||
events and re-use in places like ticket layouts. This is an useful timesaver if you create lots and
|
||||
lots of events.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<a href="{% url "control:organizer.property.add" organizer=request.organizer.slug %}" class="btn btn-default">
|
||||
<span class="fa fa-plus"></span>
|
||||
{% trans "Create a new property" %}
|
||||
</a>
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Property" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in properties %}
|
||||
<tr>
|
||||
<td><strong>
|
||||
<a href="{% url "control:organizer.property.edit" organizer=request.organizer.slug property=p.id %}">
|
||||
{{ p.name }}
|
||||
</a>
|
||||
</strong></td>
|
||||
<td class="text-right flip">
|
||||
<a href="{% url "control:organizer.property.edit" organizer=request.organizer.slug property=p.id %}"
|
||||
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:organizer.property.delete" organizer=request.organizer.slug property=p.id %}"
|
||||
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
<h1>{% trans "Delete property:" %} {{ gate.name }}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete the property?{% endblocktrans %}
|
||||
</p>
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:organizer.properties" organizer=request.organizer.slug %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
{% if gate %}
|
||||
<h1>{% trans "Property:" %} {{ property.name }}</h1>
|
||||
{% else %}
|
||||
<h1>{% trans "Create a new property" %}</h1>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -422,7 +422,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="inline" %}
|
||||
{% bootstrap_form form layout="inline" error_types="all" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="inline" %}
|
||||
{% bootstrap_form form layout="inline" error_types="all" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -77,6 +77,13 @@ urlpatterns = [
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(),
|
||||
name='organizer.display'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/properties$', organizer.EventMetaPropertyListView.as_view(), name='organizer.properties'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/property/add$', organizer.EventMetaPropertyCreateView.as_view(),
|
||||
name='organizer.property.add'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/edit$', organizer.EventMetaPropertyUpdateView.as_view(),
|
||||
name='organizer.property.edit'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/delete$', organizer.EventMetaPropertyDeleteView.as_view(),
|
||||
name='organizer.property.delete'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/giftcards$', organizer.GiftCardListView.as_view(), name='organizer.giftcards'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/giftcard/add$', organizer.GiftCardCreateView.as_view(), name='organizer.giftcard.add'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/giftcard/(?P<giftcard>[^/]+)/$', organizer.GiftCardDetailView.as_view(), name='organizer.giftcard'),
|
||||
|
||||
@@ -91,6 +91,10 @@ class MetaDataEditorMixin:
|
||||
return self.meta_form(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
disabled=(
|
||||
p.protected and
|
||||
not self.request.user.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings', request=self.request)
|
||||
),
|
||||
instance=val_instances.get(p.pk, self.meta_model(property=p, event=self.object)),
|
||||
data=(self.request.POST if self.request.method == "POST" else None)
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.db.models import (
|
||||
Count, Max, Min, OuterRef, Prefetch, ProtectedError, Subquery, Sum,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.forms import DecimalField, inlineformset_factory
|
||||
from django.forms import DecimalField
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
@@ -269,12 +269,10 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def get_context_data(self, *args, **kwargs) -> dict:
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['formset'] = self.formset
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.save_formset(self.object)
|
||||
self.sform.save()
|
||||
change_css = False
|
||||
if self.sform.has_changed():
|
||||
@@ -321,38 +319,11 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid() and self.formset.is_valid():
|
||||
if form.is_valid() and self.sform.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
formsetclass = inlineformset_factory(
|
||||
Organizer, EventMetaProperty,
|
||||
form=EventMetaPropertyForm, can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
instance=self.object, queryset=self.object.meta_properties.all())
|
||||
|
||||
def save_formset(self, obj):
|
||||
for form in self.formset.initial_forms:
|
||||
if form in self.formset.deleted_forms:
|
||||
if not form.instance.pk:
|
||||
continue
|
||||
form.instance.delete()
|
||||
form.instance.pk = None
|
||||
elif form.has_changed():
|
||||
form.save()
|
||||
|
||||
for form in self.formset.extra_forms:
|
||||
if not form.has_changed():
|
||||
continue
|
||||
if self.formset._should_delete_form(form):
|
||||
continue
|
||||
form.instance.organizer = obj
|
||||
form.save()
|
||||
|
||||
|
||||
class OrganizerCreate(CreateView):
|
||||
model = Organizer
|
||||
@@ -1365,3 +1336,94 @@ class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
||||
self.object.delete()
|
||||
messages.success(request, _('The selected gate has been deleted.'))
|
||||
return redirect(success_url)
|
||||
|
||||
|
||||
class EventMetaPropertyListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
||||
model = EventMetaProperty
|
||||
template_name = 'pretixcontrol/organizers/properties.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'properties'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.organizer.meta_properties.all()
|
||||
|
||||
|
||||
class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
|
||||
model = EventMetaProperty
|
||||
template_name = 'pretixcontrol/organizers/property_edit.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
form_class = EventMetaPropertyForm
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(EventMetaProperty, organizer=self.request.organizer, pk=self.kwargs.get('property'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.properties', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('The property has been created.'))
|
||||
form.instance.organizer = self.request.organizer
|
||||
ret = super().form_valid(form)
|
||||
form.instance.log_action('pretix.property.created', user=self.request.user, data={
|
||||
k: getattr(self.object, k) for k in form.changed_data
|
||||
})
|
||||
return ret
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('Your changes could not be saved.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
|
||||
model = EventMetaProperty
|
||||
template_name = 'pretixcontrol/organizers/property_edit.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'property'
|
||||
form_class = EventMetaPropertyForm
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(EventMetaProperty, organizer=self.request.organizer, pk=self.kwargs.get('property'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.properties', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.has_changed():
|
||||
self.object.log_action('pretix.property.changed', user=self.request.user, data={
|
||||
k: getattr(self.object, k)
|
||||
for k in form.changed_data
|
||||
})
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('Your changes could not be saved.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView):
|
||||
model = EventMetaProperty
|
||||
template_name = 'pretixcontrol/organizers/property_delete.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'property'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(EventMetaProperty, organizer=self.request.organizer, pk=self.kwargs.get('property'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.properties', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, request, *args, **kwargs):
|
||||
success_url = self.get_success_url()
|
||||
self.object = self.get_object()
|
||||
self.object.log_action('pretix.property.deleted', user=self.request.user)
|
||||
self.object.delete()
|
||||
messages.success(request, _('The selected property has been deleted.'))
|
||||
return redirect(success_url)
|
||||
|
||||
@@ -167,6 +167,10 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
return self.meta_form(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
disabled=(
|
||||
p.protected and
|
||||
not self.request.user.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings', request=self.request)
|
||||
),
|
||||
default=self._default_meta.get(p.name, ''),
|
||||
instance=val_instances.get(p.pk, self.meta_model(property=p, subevent=self.object)),
|
||||
data=(self.request.POST if self.request.method == "POST" else None)
|
||||
|
||||
Reference in New Issue
Block a user