mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Add meta_data for items (#1576)
* PoC for ItemMetaProperties/Values * Missing is_valid * ItemMetaProperties/Values in editable via API, cloneable * Tests * Add Docs * Fix import order * Fix another import sorting... * Typeahead for ItemMetaValues * Test for editing event-objects * Fix typeahead permission checks * Further access restriction Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
This commit is contained in:
@@ -11,6 +11,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.files import File
|
||||
from django.db import transaction
|
||||
from django.db.models import ProtectedError
|
||||
from django.forms import inlineformset_factory
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
|
||||
JsonResponse,
|
||||
@@ -39,9 +40,9 @@ from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.control.forms.event import (
|
||||
CancelSettingsForm, CommentForm, EventDeleteForm, EventMetaValueForm,
|
||||
EventSettingsForm, EventUpdateForm, InvoiceSettingsForm, MailSettingsForm,
|
||||
PaymentSettingsForm, ProviderForm, QuickSetupForm,
|
||||
QuickSetupProductFormSet, TaxRuleForm, TaxRuleLineFormSet,
|
||||
EventSettingsForm, EventUpdateForm, InvoiceSettingsForm,
|
||||
ItemMetaPropertyForm, MailSettingsForm, PaymentSettingsForm, ProviderForm,
|
||||
QuickSetupForm, QuickSetupProductFormSet, TaxRuleForm, TaxRuleLineFormSet,
|
||||
TicketSettingsForm, WidgetCodeForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
@@ -51,6 +52,7 @@ from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.plugins.stripe.payment import StripeSettingsHolder
|
||||
from pretix.presale.style import regenerate_css
|
||||
|
||||
from ...base.models.items import ItemMetaProperty
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -137,6 +139,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['meta_forms'] = self.meta_forms
|
||||
context['formset'] = self.formset
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
@@ -144,6 +147,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
self._save_decoupled(self.sform)
|
||||
self.sform.save()
|
||||
self.save_meta()
|
||||
self.save_formset(self.object)
|
||||
change_css = False
|
||||
|
||||
if self.sform.has_changed():
|
||||
@@ -183,7 +187,8 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid() and all([f.is_valid() for f in self.meta_forms]):
|
||||
if form.is_valid() and self.sform.is_valid() and all([f.is_valid() for f in self.meta_forms]) and \
|
||||
self.formset.is_valid():
|
||||
# reset timezone
|
||||
zone = timezone(self.sform.cleaned_data['timezone'])
|
||||
event = form.instance
|
||||
@@ -200,6 +205,33 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
def reset_timezone(tz, dt):
|
||||
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
formsetclass = inlineformset_factory(
|
||||
Event, ItemMetaProperty,
|
||||
form=ItemMetaPropertyForm, 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.item_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.event = obj
|
||||
form.save()
|
||||
|
||||
|
||||
class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
|
||||
model = Event
|
||||
|
||||
@@ -30,13 +30,14 @@ from pretix.base.models import (
|
||||
QuestionAnswer, QuestionOption, Quota, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
from pretix.base.services.tickets import invalidate_cache
|
||||
from pretix.base.signals import quota_availability
|
||||
from pretix.control.forms.item import (
|
||||
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
|
||||
ItemBundleFormSet, ItemCreateForm, ItemUpdateForm, ItemVariationForm,
|
||||
ItemVariationsFormSet, QuestionForm, QuestionOptionForm, QuotaForm,
|
||||
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemUpdateForm,
|
||||
ItemVariationForm, ItemVariationsFormSet, QuestionForm, QuestionOptionForm,
|
||||
QuotaForm,
|
||||
)
|
||||
from pretix.control.permissions import (
|
||||
EventPermissionRequiredMixin, event_permission_required,
|
||||
@@ -939,6 +940,41 @@ class ItemDetailMixin(SingleObjectMixin):
|
||||
raise Http404(_("The requested item does not exist."))
|
||||
|
||||
|
||||
class MetaDataEditorMixin:
|
||||
meta_form = ItemMetaValueForm
|
||||
meta_model = ItemMetaValue
|
||||
|
||||
@cached_property
|
||||
def meta_forms(self):
|
||||
if hasattr(self, 'object') and self.object:
|
||||
val_instances = {
|
||||
v.property_id: v for v in self.object.meta_values.all()
|
||||
}
|
||||
else:
|
||||
val_instances = {}
|
||||
|
||||
formlist = []
|
||||
|
||||
for p in self.request.event.item_meta_properties.all():
|
||||
formlist.append(self._make_meta_form(p, val_instances))
|
||||
return formlist
|
||||
|
||||
def _make_meta_form(self, p, val_instances):
|
||||
return self.meta_form(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
instance=val_instances.get(p.pk, self.meta_model(property=p, item=self.object)),
|
||||
data=(self.request.POST if self.request.method == "POST" else None)
|
||||
)
|
||||
|
||||
def save_meta(self):
|
||||
for f in self.meta_forms:
|
||||
if f.cleaned_data.get('value'):
|
||||
f.save()
|
||||
elif f.instance and f.instance.pk:
|
||||
f.instance.delete()
|
||||
|
||||
|
||||
class ItemCreate(EventPermissionRequiredMixin, CreateView):
|
||||
form_class = ItemCreateForm
|
||||
template_name = 'pretixcontrol/item/create.html'
|
||||
@@ -985,7 +1021,7 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateView):
|
||||
class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
|
||||
form_class = ItemUpdateForm
|
||||
template_name = 'pretixcontrol/item/index.html'
|
||||
permission = 'can_change_items'
|
||||
@@ -1038,7 +1074,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.get_object()
|
||||
form = self.get_form()
|
||||
if self.is_valid(form):
|
||||
if self.is_valid(form) and all([f.is_valid() for f in self.meta_forms]):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
@@ -1088,6 +1124,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.save_meta()
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
if form.has_changed() or any(f.has_changed() for f in self.plugin_forms):
|
||||
data = {
|
||||
@@ -1137,6 +1174,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['plugin_forms'] = self.plugin_forms
|
||||
ctx['meta_forms'] = self.meta_forms
|
||||
ctx['formsets'] = self.formsets
|
||||
|
||||
if not ctx['item'].active and ctx['item'].bundled_with.count() > 0:
|
||||
|
||||
@@ -13,7 +13,8 @@ from django.utils.timezone import make_aware
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
|
||||
from pretix.base.models import (
|
||||
EventMetaProperty, EventMetaValue, Order, Organizer, User, Voucher,
|
||||
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue, Order,
|
||||
Organizer, User, Voucher,
|
||||
)
|
||||
from pretix.control.forms.event import EventWizardCopyForm
|
||||
from pretix.control.permissions import event_permission_required
|
||||
@@ -589,3 +590,46 @@ def meta_values(request):
|
||||
for v in sorted(set(defaults.values_list('default', flat=True)[:10]) | set(matches.values_list('value', flat=True)[:10]))
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
def item_meta_values(request, organizer, event):
|
||||
q = request.GET.get('q')
|
||||
propname = request.GET.get('property')
|
||||
|
||||
matches = ItemMetaValue.objects.filter(
|
||||
value__icontains=q,
|
||||
property__name=propname
|
||||
)
|
||||
defaults = ItemMetaProperty.objects.filter(
|
||||
name=propname,
|
||||
default__icontains=q
|
||||
)
|
||||
|
||||
organizer = get_object_or_404(Organizer, slug=organizer)
|
||||
if not request.user.has_organizer_permission(organizer, request=request):
|
||||
raise PermissionDenied()
|
||||
|
||||
defaults = defaults.filter(event__organizer_id=organizer.pk)
|
||||
matches = matches.filter(item__event__organizer_id=organizer.pk)
|
||||
all_access = (
|
||||
request.user.has_active_staff_session(request.session.session_key)
|
||||
or request.user.teams.filter(all_events=True, organizer=organizer, can_change_items=True).exists()
|
||||
)
|
||||
if not all_access:
|
||||
defaults = matches.filter(
|
||||
event__id__in=request.user.teams.filter(can_change_items=True).values_list(
|
||||
'limit_events__id', flat=True
|
||||
)
|
||||
)
|
||||
matches = matches.filter(
|
||||
item__event__id__in=request.user.teams.filter(can_change_items=True).values_list(
|
||||
'limit_events__id', flat=True
|
||||
)
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'results': [
|
||||
{'name': v, 'id': v}
|
||||
for v in sorted(set(defaults.values_list('default', flat=True)[:10]) | set(matches.values_list('value', flat=True)[:10]))
|
||||
]
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user