mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Add public filters based on meta data (#3673)
* Add public filters based on meta data * Fix licenseheaders * ignore empty values * Fix tests * Full non-widget implementation * Widget support * Add a few tests * Allow to reorder properties * Fix isort * Allow to opt-out for specific events * Fix name clash between new and old field to make migration feasible
This commit is contained in:
@@ -316,12 +316,12 @@ class EventMetaValueForm(forms.ModelForm):
|
||||
self.property = kwargs.pop('property')
|
||||
self.disabled = kwargs.pop('disabled')
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.property.allowed_values:
|
||||
if self.property.choices:
|
||||
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()],
|
||||
] + [(a.strip(), a.strip()) for a in self.property.choice_keys],
|
||||
)
|
||||
else:
|
||||
self.fields['value'].label = self.property.name
|
||||
@@ -558,6 +558,7 @@ class EventSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
'low_availability_percentage',
|
||||
'event_list_type',
|
||||
'event_list_available_only',
|
||||
'event_list_filters',
|
||||
'event_calendar_future_only',
|
||||
'frontpage_text',
|
||||
'event_info_text',
|
||||
@@ -645,6 +646,7 @@ class EventSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
del self.fields['frontpage_subevent_ordering']
|
||||
del self.fields['event_list_type']
|
||||
del self.fields['event_list_available_only']
|
||||
del self.fields['event_list_filters']
|
||||
del self.fields['event_calendar_future_only']
|
||||
|
||||
# create "virtual" fields for better UX when editing <name>_asked and <name>_required fields
|
||||
|
||||
@@ -39,7 +39,7 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
from django.forms import formset_factory, inlineformset_factory
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
@@ -48,7 +48,7 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes.forms import SafeModelChoiceField
|
||||
from i18nfield.forms import (
|
||||
I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
|
||||
I18nForm, I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
|
||||
)
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from pytz import common_timezones
|
||||
@@ -195,14 +195,50 @@ class SafeOrderPositionChoiceField(forms.ModelChoiceField):
|
||||
return f'{op.order.code}-{op.positionid} ({str(op.item) + ((" - " + str(op.variation)) if op.variation else "")})'
|
||||
|
||||
|
||||
class EventMetaPropertyForm(forms.ModelForm):
|
||||
class EventMetaPropertyForm(I18nModelForm):
|
||||
class Meta:
|
||||
model = EventMetaProperty
|
||||
fields = ['name', 'default', 'required', 'protected', 'allowed_values', 'filter_allowed']
|
||||
fields = ['name', 'default', 'required', 'protected', 'filter_public', 'public_label', 'filter_allowed']
|
||||
widgets = {
|
||||
'default': forms.TextInput()
|
||||
'default': forms.TextInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['public_label'].widget.attrs['data-display-dependency'] = '#id_filter_public'
|
||||
|
||||
|
||||
class EventMetaPropertyAllowedValueForm(I18nForm):
|
||||
key = forms.CharField(
|
||||
label=_('Internal name'),
|
||||
max_length=250,
|
||||
required=True
|
||||
)
|
||||
label = I18nFormField(
|
||||
label=_('Public name'),
|
||||
required=False,
|
||||
widget=I18nTextInput,
|
||||
widget_kwargs=dict(attrs={
|
||||
'placeholder': _('Public name'),
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class I18nBaseFormSet(I18nFormSetMixin, forms.BaseFormSet):
|
||||
# compatibility shim for django-i18nfield library
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer', None)
|
||||
if self.organizer:
|
||||
kwargs['locales'] = self.organizer.settings.get('locales')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
EventMetaPropertyAllowedValueFormSet = formset_factory(
|
||||
EventMetaPropertyAllowedValueForm, formset=I18nBaseFormSet,
|
||||
can_order=True, can_delete=True, extra=0
|
||||
)
|
||||
|
||||
|
||||
class MembershipTypeForm(I18nModelForm):
|
||||
class Meta:
|
||||
|
||||
@@ -393,12 +393,12 @@ class SubEventMetaValueForm(forms.ModelForm):
|
||||
self.default = kwargs.pop('default', None)
|
||||
self.disabled = kwargs.pop('disabled', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.property.allowed_values:
|
||||
if self.property.choices:
|
||||
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()],
|
||||
] + [(a.strip(), a.strip()) for a in self.property.choice_keys],
|
||||
)
|
||||
else:
|
||||
self.fields['value'].label = self.property.name
|
||||
|
||||
@@ -316,6 +316,9 @@
|
||||
{% if sform.event_list_available_only %}
|
||||
{% bootstrap_field sform.event_list_available_only layout="control" %}
|
||||
{% endif %}
|
||||
{% if sform.event_list_filters %}
|
||||
{% bootstrap_field sform.event_list_filters layout="control" %}
|
||||
{% endif %}
|
||||
{% if sform.event_calendar_future_only %}
|
||||
{% bootstrap_field sform.event_calendar_future_only layout="control" %}
|
||||
{% endif %}
|
||||
|
||||
@@ -14,29 +14,56 @@
|
||||
<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 %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<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>
|
||||
<th>{% trans "Property" %}</th>
|
||||
<th class="iconcol"></th>
|
||||
<th class="iconcol"></th>
|
||||
<th class="iconcol"></th>
|
||||
<th class="action-col-2"></th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody data-dnd-url="{% url "control:organizer.properties.reorder" organizer=request.organizer.slug %}">
|
||||
{% for p in properties %}
|
||||
<tr data-dnd-id="{{ p.pk }}">
|
||||
<td><strong>
|
||||
<a href="{% url "control:organizer.property.edit" organizer=request.organizer.slug property=p.id %}">
|
||||
{{ p.name }}
|
||||
</a>
|
||||
</strong></td>
|
||||
<td>
|
||||
{% if p.filter_allowed %}
|
||||
<span class="fa fa-filter text-muted" data-toggle="tooltip" title="{% trans "Can be used for filtering" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if p.filter_public %}
|
||||
<span class="fa fa-eye text-muted" data-toggle="tooltip" title="{% trans "Show filter option to customers" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if p.protected %}
|
||||
<span class="fa fa-lock text-muted" data-toggle="tooltip" title="{% trans "Can only be changed by organizer-level administrators" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button formaction="{% url "control:organizer.property.up" organizer=request.organizer.slug property=p.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||
<button formaction="{% url "control:organizer.property.down" organizer=request.organizer.slug property=p.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
||||
<span class="dnd-container"></span>
|
||||
</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>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load formset_tags %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
{% if property %}
|
||||
@@ -10,6 +11,78 @@
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">
|
||||
{% trans "Allowed values" %}<br>
|
||||
<span class="optional">{% trans "Optional" %}</span>
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<p class="help-block">{% trans "If you keep this empty, all input will be allowed." %}</p>
|
||||
<div class="formset tax-rules-formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="row tax-rule-line" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field formset.empty_form.key layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field formset.empty_form.label layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 text-right flip">
|
||||
<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>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<div data-formset-body class="tax-rule-lines">
|
||||
{% for form in formset %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<div class="row tax-rule-line" data-formset-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-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field form.key layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-5">
|
||||
{% bootstrap_field form.label layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 text-right flip">
|
||||
<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>
|
||||
<div class="row tax-rule-line" data-formset-form>
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new value" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -133,6 +133,11 @@
|
||||
{{ s.name }}</a></strong><br>
|
||||
<small class="text-muted">
|
||||
#{{ s.pk }}
|
||||
{% for k, v in s.meta_data.items %}
|
||||
{% if v %}
|
||||
<small class="text-muted">· {{ k }}: {{ v }}</small>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -126,6 +126,12 @@ urlpatterns = [
|
||||
name='organizer.property.edit'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/delete$', organizer.EventMetaPropertyDeleteView.as_view(),
|
||||
name='organizer.property.delete'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/up$', organizer.meta_property_move_up,
|
||||
name='organizer.property.up'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/down$', organizer.meta_property_move_down,
|
||||
name='organizer.property.down'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/property/reorder$', organizer.reorder_meta_properties,
|
||||
name='organizer.properties.reorder'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/membershiptypes$', organizer.MembershipTypeListView.as_view(), name='organizer.membershiptypes'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/membershiptype/add$', organizer.MembershipTypeCreateView.as_view(),
|
||||
name='organizer.membershiptype.add'),
|
||||
|
||||
@@ -37,6 +37,7 @@ import re
|
||||
from datetime import time, timedelta
|
||||
from decimal import Decimal
|
||||
from hashlib import sha1
|
||||
from json import JSONDecodeError
|
||||
|
||||
import bleach
|
||||
import dateutil
|
||||
@@ -52,7 +53,9 @@ from django.db.models import (
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.forms import DecimalField
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
@@ -60,6 +63,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic import (
|
||||
CreateView, DetailView, FormView, ListView, TemplateView, UpdateView,
|
||||
)
|
||||
@@ -97,17 +101,19 @@ from pretix.control.forms.filter import (
|
||||
from pretix.control.forms.orders import ExporterForm
|
||||
from pretix.control.forms.organizer import (
|
||||
CustomerCreateForm, CustomerUpdateForm, DeviceBulkEditForm, DeviceForm,
|
||||
EventMetaPropertyForm, GateForm, GiftCardAcceptanceInviteForm,
|
||||
GiftCardCreateForm, GiftCardUpdateForm, MailSettingsForm,
|
||||
MembershipTypeForm, MembershipUpdateForm, OrganizerDeleteForm,
|
||||
OrganizerFooterLinkFormset, OrganizerForm, OrganizerSettingsForm,
|
||||
OrganizerUpdateForm, ReusableMediumCreateForm, ReusableMediumUpdateForm,
|
||||
SSOClientForm, SSOProviderForm, TeamForm, WebHookForm,
|
||||
EventMetaPropertyAllowedValueFormSet, EventMetaPropertyForm, GateForm,
|
||||
GiftCardAcceptanceInviteForm, GiftCardCreateForm, GiftCardUpdateForm,
|
||||
MailSettingsForm, MembershipTypeForm, MembershipUpdateForm,
|
||||
OrganizerDeleteForm, OrganizerFooterLinkFormset, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, ReusableMediumCreateForm,
|
||||
ReusableMediumUpdateForm, SSOClientForm, SSOProviderForm, TeamForm,
|
||||
WebHookForm,
|
||||
)
|
||||
from pretix.control.forms.rrule import RRuleForm
|
||||
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||
from pretix.control.permissions import (
|
||||
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
||||
organizer_permission_required,
|
||||
)
|
||||
from pretix.control.signals import nav_organizer
|
||||
from pretix.control.views import PaginationMixin
|
||||
@@ -2043,14 +2049,54 @@ class EventMetaPropertyListView(OrganizerDetailViewMixin, OrganizerPermissionReq
|
||||
return self.request.organizer.meta_properties.all()
|
||||
|
||||
|
||||
class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
|
||||
model = EventMetaProperty
|
||||
class EventMetaPropertyEditorMixin:
|
||||
template_name = 'pretixcontrol/organizers/property_edit.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
form_class = EventMetaPropertyForm
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
return EventMetaPropertyAllowedValueFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
organizer=self.request.organizer,
|
||||
initial=(self.object.choices or []) if self.object else [],
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['formset'] = self.formset
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
'event': self.request.organizer,
|
||||
}
|
||||
|
||||
def is_default_valid(self):
|
||||
choice_keys = [
|
||||
f.cleaned_data.get("key") for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
|
||||
]
|
||||
default = self.form.cleaned_data["default"]
|
||||
if default and choice_keys and default not in choice_keys:
|
||||
messages.error(self.request, _("You cannot set a default value that is not a valid value."))
|
||||
return False
|
||||
return True
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(self.get_queryset())
|
||||
self.form = self.get_form()
|
||||
if self.form.is_valid() and self.formset.is_valid() and self.is_default_valid():
|
||||
return self.form_valid(self.form)
|
||||
else:
|
||||
return self.form_invalid(self.form)
|
||||
|
||||
|
||||
class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, EventMetaPropertyEditorMixin, CreateView):
|
||||
model = EventMetaProperty
|
||||
permission = 'can_change_organizer_settings'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(EventMetaProperty, organizer=self.request.organizer, pk=self.kwargs.get('property'))
|
||||
return EventMetaProperty()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.properties', kwargs={
|
||||
@@ -2060,6 +2106,9 @@ class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionR
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('The property has been created.'))
|
||||
form.instance.organizer = self.request.organizer
|
||||
form.instance.choices = [
|
||||
f.cleaned_data for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
|
||||
]
|
||||
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
|
||||
@@ -2071,12 +2120,10 @@ class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionR
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
|
||||
class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, EventMetaPropertyEditorMixin, 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'))
|
||||
@@ -2087,7 +2134,10 @@ class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionR
|
||||
})
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.has_changed():
|
||||
form.instance.choices = [
|
||||
f.cleaned_data for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
|
||||
]
|
||||
if form.has_changed() or self.formset.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
|
||||
@@ -2124,6 +2174,75 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
|
||||
return redirect(success_url)
|
||||
|
||||
|
||||
def meta_property_move(request, property, up=True):
|
||||
property = get_object_or_404(request.organizer.meta_properties, id=property)
|
||||
properties = list(request.organizer.meta_properties.order_by("position"))
|
||||
|
||||
index = properties.index(property)
|
||||
if index != 0 and up:
|
||||
properties[index - 1], properties[index] = properties[index], properties[index - 1]
|
||||
elif index != len(properties) - 1 and not up:
|
||||
properties[index + 1], properties[index] = properties[index], properties[index + 1]
|
||||
|
||||
for i, prop in enumerate(properties):
|
||||
if prop.position != i:
|
||||
prop.position = i
|
||||
prop.save()
|
||||
prop.log_action(
|
||||
'pretix.property.reordered', user=request.user, data={
|
||||
'position': i,
|
||||
}
|
||||
)
|
||||
messages.success(request, _('The order of properties has been updated.'))
|
||||
|
||||
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def meta_property_move_up(request, organizer, property):
|
||||
meta_property_move(request, property, up=True)
|
||||
return redirect('control:organizer.properties',
|
||||
organizer=request.organizer.slug)
|
||||
|
||||
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def meta_property_move_down(request, organizer, property):
|
||||
meta_property_move(request, property, up=False)
|
||||
return redirect('control:organizer.properties',
|
||||
organizer=request.organizer.slug)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@organizer_permission_required("can_change_items")
|
||||
@require_http_methods(["POST"])
|
||||
def reorder_meta_properties(request, organizer):
|
||||
try:
|
||||
ids = json.loads(request.body.decode('utf-8'))['ids']
|
||||
except (JSONDecodeError, KeyError, ValueError):
|
||||
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
||||
|
||||
input_meta_properties = list(request.organizer.meta_properties.filter(id__in=[i for i in ids if i.isdigit()]))
|
||||
|
||||
if len(input_meta_properties) != len(ids):
|
||||
raise Http404(_("Some of the provided object ids are invalid."))
|
||||
|
||||
if len(input_meta_properties) != request.organizer.meta_properties.count():
|
||||
raise Http404(_("Not all objects have been selected."))
|
||||
|
||||
for c in input_meta_properties:
|
||||
pos = ids.index(str(c.pk))
|
||||
if pos != c.position: # Save unneccessary UPDATE queries
|
||||
c.position = pos
|
||||
c.save(update_fields=['position'])
|
||||
c.log_action(
|
||||
'pretix.property.reordered', user=request.user, data={
|
||||
'position': pos,
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
template_name = 'pretixcontrol/organizers/logs.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
|
||||
@@ -120,7 +120,10 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
|
||||
permission = 'can_change_settings'
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset(True)
|
||||
return super().get_queryset(True).prefetch_related(
|
||||
'meta_values',
|
||||
'meta_values__property',
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user