EventMetaProperties: Restrict changing "allowed values" if already in use (Z#23235265)

This commit is contained in:
Mira Weller
2026-05-27 13:18:55 +02:00
parent 94aec6f511
commit eb5ff1f34a
2 changed files with 58 additions and 4 deletions

View File

@@ -72,6 +72,9 @@
</div>
<div class="col-sm-6 col-md-4 col-lg-5">
{% bootstrap_field form.key layout='inline' form_group_class="" %}
{% if form.key.help_text %}
<span class="help-block">{{ form.key.help_text|safe }}</span>
{% endif %}
</div>
<div class="col-sm-6 col-md-4 col-lg-5">
{% bootstrap_field form.label layout='inline' form_group_class="" %}

View File

@@ -69,7 +69,7 @@ from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import gettext, gettext_lazy as _, ngettext
from django.views import View
from django.views.decorators.http import require_http_methods
from django.views.generic import (
@@ -91,7 +91,7 @@ from pretix.base.models import (
ReusableMedium, ScheduledOrganizerExport, Team, TeamInvite, User,
)
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue, SubEvent, SubEventMetaValue
from pretix.base.models.giftcards import (
GiftCardAcceptance, GiftCardTransaction, gen_giftcard_secret,
)
@@ -2466,11 +2466,47 @@ class EventMetaPropertyEditorMixin:
@cached_property
def formset(self):
return EventMetaPropertyAllowedValueFormSet(
formset = 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 [],
)
if self.event_value_counts or self.subevent_value_counts:
for form in formset.initial_forms:
uses = []
key = form.initial['key']
if key in self.event_value_counts:
count = self.event_value_counts[key]
uses += [ngettext("%d event", "%d events", count) % count]
if key in self.subevent_value_counts:
count = self.subevent_value_counts[key]
uses += [ngettext("%d subevent", "%d subevents", count) % count]
if uses:
form.fields['key'].help_text = _("Value can not be changed because it is in use (%s).") % (", ".join(uses))
form.fields['key'].widget.attrs['readonly'] = True
return formset
@cached_property
def event_value_counts(self):
if self.object:
return {d['attr_value']: d['count']
for d in self.request.organizer.events.annotate(
attr_value=Subquery(EventMetaValue.objects.filter(
event=OuterRef('pk'),
property__name=self.object.name
).values('value')), count=Count('attr_value')
).values('attr_value', 'count')}
@cached_property
def subevent_value_counts(self):
if self.object:
return {d['attr_value']: d['count']
for d in SubEvent.objects.filter(event__organizer=self.request.organizer).annotate(
attr_value=Subquery(SubEventMetaValue.objects.filter(
subevent=OuterRef('pk'),
property__name=self.object.name,
).values('value')), count=Count('attr_value')
).values('attr_value', 'count')}
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -2493,10 +2529,25 @@ class EventMetaPropertyEditorMixin:
return False
return True
def all_existing_values_valid(self):
if not self.event_value_counts and not self.subevent_value_counts:
return True
choice_keys = set(
f.cleaned_data.get("key") for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
)
if not choice_keys:
return True
existing_values = (self.event_value_counts.keys() | self.subevent_value_counts.keys()) - {None}
missing_choices = existing_values - choice_keys
if missing_choices:
messages.error(self.request, _("When restricting the allowed values, you need to allow all values that already exist on your events. Missing values: %s") % (", ".join(missing_choices)))
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():
if self.form.is_valid() and self.formset.is_valid() and self.is_default_valid() and self.all_existing_values_valid():
return self.form_valid(self.form)
else:
return self.form_invalid(self.form)