From 912b0b4211e99798c07039983fdda55f813c5b85 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 14 Oct 2014 00:45:42 +0200 Subject: [PATCH] Bootstrap the form magic for inline restriction formset variation controls --- doc/development/api/restriction.rst | 79 +++++++++++++++++++ .../tixlcontrol/item/restrictions.html | 2 +- src/tixlcontrol/views/forms.py | 45 +++++++++++ src/tixlcontrol/views/item.py | 2 +- src/tixlplugins/timerestriction/signals.py | 18 ++--- 5 files changed, 135 insertions(+), 11 deletions(-) diff --git a/doc/development/api/restriction.rst b/doc/development/api/restriction.rst index b34d115f5..56fa92da9 100644 --- a/doc/development/api/restriction.rst +++ b/doc/development/api/restriction.rst @@ -213,4 +213,83 @@ In our example, the implementation could look like this:: If you do not copy down to the ``dict`` objects, you will run into interference problems with other plugins. +Control interface formsets +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To make it possible for the event organizer to configure your restriction, there is a +'Restrictions' page in the item configuration. This page is able to show a formset for +each restriction plugin, but *you* are required to create this formset. This is why you +should listen to the the ``tixlcontrol.signals.restriction_formset`` signal. + +Currently, the signal comes with only one keyword argument: + + ``item`` + The instance of ``tixlbase.models.Item`` we want a formset for. + +You are expected to return a dict containing the following items: + + ``formsetclass`` + An inline formset class (not a formset object). + + ``prefix`` + A unique prefix for your queryset. + + ``title`` + A title for your formset (normally your plugin name) + + +Our time restriction example looks like this:: + + from django.utils.translation import ugettext_lazy as _ + from django.dispatch import receiver + from django.forms.models import inlineformset_factory + + from tixlcontrol.signals import restriction_formset + from tixlbase.models import Item + from tixlcontrol.views.forms import ( + VariationsField, RestrictionInlineFormset, RestrictionForm + ) + + from .models import TimeRestriction + + class TimeRestrictionForm(RestrictionForm): + + class Meta: + model = TimeRestriction + localized_fields = '__all__' + fields = [ + 'variations', + 'timeframe_from', + 'timeframe_to', + 'price', + ] + + + @receiver(restriction_formset) + def formset_handler(sender, **kwargs): + formset = inlineformset_factory( + Item, + TimeRestriction, + formset=RestrictionInlineFormset, + form=TimeRestrictionForm, + can_order=False, + can_delete=True, + extra=0, + ) + + return { + 'title': _('Restriction by time'), + 'formsetclass': formset, + 'prefix': 'timerestriction', + } + + +.. NOTE:: + If you do use the ``RestrictionInlineFormset``, ``RestrictionForm`` and + ``VariationsField`` classes in your implementation, we will do a lot of magic for you + to display the ``variations`` field in the form in a nice and consistent way. So please, + use these base classes and test carefully, if you make any changes to the behaviour + of this field. + + .. _caching feature: https://docs.djangoproject.com/en/1.7/topics/cache/ diff --git a/src/tixlcontrol/templates/tixlcontrol/item/restrictions.html b/src/tixlcontrol/templates/tixlcontrol/item/restrictions.html index eb0e3d079..8412a4c12 100644 --- a/src/tixlcontrol/templates/tixlcontrol/item/restrictions.html +++ b/src/tixlcontrol/templates/tixlcontrol/item/restrictions.html @@ -21,7 +21,7 @@ diff --git a/src/tixlcontrol/views/forms.py b/src/tixlcontrol/views/forms.py index 176a67c8e..0f9e6d6ce 100644 --- a/src/tixlcontrol/views/forms.py +++ b/src/tixlcontrol/views/forms.py @@ -30,3 +30,48 @@ class TolerantFormsetModelForm(forms.ModelForm): if field._has_changed(initial_value, data_value): return True return False + + +class RestrictionForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + if 'item' in kwargs: + self.item = kwargs['item'] + del kwargs['item'] + super().__init__(*args, **kwargs) + if 'variations' in self.fields: + self.fields['variations'] = VariationsField(item=self.item) + + +class RestrictionInlineFormset(forms.BaseInlineFormSet): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def initialized_empty_form(self): + form = self.form( + auto_id=self.auto_id, + prefix=self.add_prefix('__prefix__'), + empty_permitted=True, + item=self.instance + ) + return form + + def _construct_form(self, i, **kwargs): + kwargs['item'] = self.instance + return super()._construct_form(i, **kwargs) + + +class VariationsField(forms.ModelMultipleChoiceField): + + def __init__(self, item=None, **kwargs): + self.item = item + super().__init__(self, **kwargs) + + def _get_choices(self): + if not hasattr(self, 'item'): + return () + print(self.item.pk) + return () + + choices = property(_get_choices, forms.ChoiceField._set_choices) diff --git a/src/tixlcontrol/views/item.py b/src/tixlcontrol/views/item.py index 0f520e985..d0bea70de 100644 --- a/src/tixlcontrol/views/item.py +++ b/src/tixlcontrol/views/item.py @@ -649,7 +649,7 @@ class ItemRestrictions(ItemDetailMixin, EventPermissionRequiredMixin, TemplateVi for receiver, response in responses: response['formset'] = response['formsetclass']( self.request.POST if self.request.method == 'POST' else None, - queryset=response['queryset'], + instance=self.object, prefix=response['prefix'], ) formsets.append(response) diff --git a/src/tixlplugins/timerestriction/signals.py b/src/tixlplugins/timerestriction/signals.py index 0ab411d00..4b805c1a7 100644 --- a/src/tixlplugins/timerestriction/signals.py +++ b/src/tixlplugins/timerestriction/signals.py @@ -1,10 +1,11 @@ from django.dispatch import receiver from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ -from django import forms -from django.forms.models import modelformset_factory +from django.forms.models import inlineformset_factory from tixlbase.signals import determine_availability +from tixlbase.models import Item +from tixlcontrol.views.forms import VariationsField, RestrictionInlineFormset, RestrictionForm from tixlcontrol.signals import restriction_formset from .models import TimeRestriction @@ -111,11 +112,13 @@ def availability_handler(sender, **kwargs): return variations -class TimeRestrictionForm(forms.ModelForm): +class TimeRestrictionForm(RestrictionForm): + class Meta: model = TimeRestriction localized_fields = '__all__' fields = [ + 'variations', 'timeframe_from', 'timeframe_to', 'price', @@ -124,21 +127,18 @@ class TimeRestrictionForm(forms.ModelForm): @receiver(restriction_formset) def formset_handler(sender, **kwargs): - # Handle the signal's input arguments - item = kwargs['item'] - - formset = modelformset_factory( + formset = inlineformset_factory( + Item, TimeRestriction, + formset=RestrictionInlineFormset, form=TimeRestrictionForm, can_order=False, can_delete=True, extra=0, ) - queryset = TimeRestriction.objects.filter(item=item) return { 'title': _('Restriction by time'), 'formsetclass': formset, - 'queryset': queryset, 'prefix': 'timerestriction', }