Update sponsorship benefits to use json data properly
authorMagnus Hagander <magnus@hagander.net>
Tue, 26 Jun 2018 19:45:02 +0000 (21:45 +0200)
committerMagnus Hagander <magnus@hagander.net>
Wed, 27 Jun 2018 10:55:04 +0000 (12:55 +0200)
This means the sponsorship benefits can now deal with python dicts
instead of converting to and from json.

Also make the sponsorship benefits define their validation rules
declaratively and use the new json structure validator.

postgresqleu/confsponsor/admin.py
postgresqleu/confsponsor/backendforms.py
postgresqleu/confsponsor/benefitclasses/attendeelist.py
postgresqleu/confsponsor/benefitclasses/base.py
postgresqleu/confsponsor/benefitclasses/entryvouchers.py
postgresqleu/confsponsor/benefitclasses/imageupload.py
postgresqleu/confsponsor/benefitclasses/providetext.py
postgresqleu/confsponsor/benefitclasses/requireclaiming.py

index f04754b0d20fe51df2337cf491fefc233b0ba8a8..0818ace2536b55d131ea44a7fcc730215bb45d45 100644 (file)
@@ -16,19 +16,16 @@ from models import SponsorshipBenefit, SponsorClaimedBenefit
 
 from benefits import get_benefit_class
 
-import json
-
 class SponsorshipBenefitInlineFormset(BaseInlineFormSet):
        def clean(self):
                for f in self.forms:
                        if f.cleaned_data.get('benefit_class') >= 0:
                                params = f.cleaned_data.get('class_parameters')
                                benefit = get_benefit_class(f.cleaned_data.get('benefit_class'))(self.instance, params)
-                               if params == "" and benefit.default_params:
-                                       dp = json.dumps(benefit.default_params)
-                                       f.cleaned_data['class_parameters'] = dp
-                                       f.instance.class_parameters = dp
-                                       params = dp
+                               if benefit.default_params and not params:
+                                       f.cleaned_data['class_parameters'] = benefit.default_params
+                                       f.instance.class_parameters = benefit.default_params
+                                       params = benefit.default_params
                                s = benefit.validate_params()
                                if s:
                                        f._errors['class_parameters'] = ErrorList([s])
index 08b5de3901d92b323842a712749357b8ddd93521..a93f10ff066fec491d557e438460fcf0cf3e2afc 100644 (file)
@@ -3,7 +3,7 @@ from django.forms import ValidationError
 from collections import OrderedDict
 
 from postgresqleu.util.magic import magicdb
-from postgresqleu.util.widgets import RequiredFileUploadWidget
+from postgresqleu.util.widgets import RequiredFileUploadWidget, PrettyPrintJsonWidget
 from postgresqleu.confreg.backendforms import BackendForm
 
 from models import SponsorshipLevel, SponsorshipContract, SponsorshipBenefit
@@ -19,23 +19,30 @@ class BackendSponsorshipLevelBenefitForm(BackendForm):
                model = SponsorshipBenefit
                fields = ['benefitname', 'benefitdescription', 'sortkey', 'benefit_class',
                                  'claimprompt', 'class_parameters', ]
+               widgets = {
+                       'class_parameters': PrettyPrintJsonWidget,
+               }
 
        def clean(self):
                cleaned_data = super(BackendSponsorshipLevelBenefitForm, self).clean()
                if cleaned_data.get('benefit_class') >= 0:
                        params = cleaned_data.get('class_parameters')
                        benefit = get_benefit_class(cleaned_data.get('benefit_class'))(self.instance.level, params)
-                       if params in ("","{}") and benefit.default_params:
+                       if not params:
                                # Need a copy of the local data to make it mutable and change our default
                                self.data = self.data.copy()
-                               dp = json.dumps(benefit.default_params)
-                               self.data['class_parameters'] = dp
+                               if benefit.default_params:
+                                       dp = benefit.default_params
+                               else:
+                                       dp = {}
+                               self.data['class_parameters'] = json.dumps(dp)
                                self.instance.class_parameters = dp
+                               cleaned_data['class_parameters'] = dp
                                benefit.params = dp
-                       s = benefit.validate_params()
-                       if s:
-                               self.add_error('class_parameters', s)
-
+                       try:
+                               benefit.do_validate_params()
+                       except ValidationError, e:
+                               self.add_error('class_parameters', e)
                return cleaned_data
 
        @property
index ec6aeb9d95505e99f8f2a86e5d07d44c0d8e950d..6fa3d0af639f286f850f720afd17a188ad3b2b4c 100644 (file)
@@ -7,21 +7,13 @@ import cStringIO as StringIO
 import csv
 import json
 
-from base import BaseBenefit
+from base import BaseBenefit, BaseBenefitForm
 
 from postgresqleu.confreg.models import ConferenceRegistration
 
-class AttendeeListForm(forms.Form):
+class AttendeeListForm(BaseBenefitForm):
        confirm = forms.ChoiceField(label="Claim benefit", choices=((0, '* Choose'), (1, 'Claim this benefit'), (2, 'Decline this benefit')))
 
-       def __init__(self, benefit, *args, **kwargs):
-               super(AttendeeListForm, self).__init__(*args, **kwargs)
-
-               if benefit.class_parameters:
-                       params = json.loads(benefit.class_parameters)
-                       if params.has_key('claimcheckbox'):
-                               self.fields['confirm'].help_text = params['claimcheckbox']
-
        def clean_confirm(self):
                if not int(self.cleaned_data['confirm']) in (1,2):
                        raise ValidationError('You must decide if you want to claim this benefit')
@@ -29,32 +21,18 @@ class AttendeeListForm(forms.Form):
 
 class AttendeeList(BaseBenefit):
        description = "List of attendee email addresses"
-
-       def validate_params(self):
-               # Just see that it's valid json, and then pass it upwards
-               try:
-                       json.loads(self.params)
-               except Exception, e:
-                       return e
+       param_struct = {}
 
        def generate_form(self):
                return AttendeeListForm
 
        def save_form(self, form, claim, request):
-               try:
-                       p = json.loads(self.params)
-               except Exception:
-                       p = {}
-
                if int(form.cleaned_data['confirm']) == 2:
                        # This is actually a deny
                        claim.declined = True
                        claim.confirmed = True
                        return True
 
-               if p.has_key('autoconfirm') and p['autoconfirm']:
-                       claim.confirmed = True
-                       return False
                return True
 
        def render_claimdata(self, claimedbenefit):
index 7b2167714351807d511d5bc8d9a97fd422611555..48342e16d35f2f5b1127fe4169076bc9902775ff 100644 (file)
@@ -1,9 +1,17 @@
+from django import forms
+
+from postgresqleu.util.validators import validate_json_structure
+
 class BaseBenefit(object):
        default_params = {}
        def __init__(self, level, params):
                self.level = level
                self.params = params
 
+       def do_validate_params(self):
+               validate_json_structure(self.params, self.param_struct)
+               self.validate_params()
+
        def validate_params(self):
                pass
 
@@ -12,3 +20,8 @@ class BaseBenefit(object):
 
        def save_form(self, form, claim, request):
                raise Exception("Form saving not implemented!")
+
+class BaseBenefitForm(forms.Form):
+       def __init__(self, benefit, *args, **kwargs):
+               self.params = benefit.class_parameters
+               super(BaseBenefitForm, self).__init__(*args, **kwargs)
index 41ed30b2fce712c3625854d792a5c52245278ccc..63bdfe19eea098410bb11de308c0c03e069862b9 100644 (file)
@@ -3,35 +3,18 @@ from django.core.validators import MaxValueValidator
 
 import base64
 import os
-import json
 import cStringIO as StringIO
 
-from base import BaseBenefit
+from base import BaseBenefit, BaseBenefitForm
 
 from postgresqleu.mailqueue.util import send_template_mail
 
 from postgresqleu.confreg.models import RegistrationType, PrepaidBatch, PrepaidVoucher
 
-def _validate_params(level, params):
-       try:
-               j = json.loads(params)
-               if sorted(j.keys()) != [u"num", u"type"]:
-                       raise Exception("Parameters 'num' and 'type' are mandatory")
-               if int(j['num']) < 1:
-                       raise Exception("Parameter 'num' must be positive integer!")
-               if not RegistrationType.objects.filter(conference=level.conference, regtype=j['type']).exists():
-                       raise Exception("Registation type '%s' does not exist" % j['type'])
-               return j
-       except ValueError:
-               raise Exception("Can't parse JSON")
-
-
-class EntryVouchersForm(forms.Form):
+class EntryVouchersForm(BaseBenefitForm):
        vouchercount = forms.IntegerField(label='Number of vouchers', min_value=0)
 
-       def __init__(self, benefit, *args, **kwargs):
-               self.params = _validate_params(benefit.level, benefit.class_parameters)
-
+       def __init__(self, *args, **kwargs):
                super(EntryVouchersForm, self).__init__(*args, **kwargs)
 
                self.fields['vouchercount'].validators.append(MaxValueValidator(int(self.params['num'])))
@@ -40,17 +23,19 @@ class EntryVouchersForm(forms.Form):
 class EntryVouchers(BaseBenefit):
        description = "Claim entry vouchers"
        default_params = {"num": 1, "type": ""}
+       param_struct = {
+               'num': int,
+               'type': unicode,
+       }
+
        def validate_params(self):
-               try:
-                       _validate_params(self.level, self.params)
-               except Exception, e:
-                       return e
+               if not RegistrationType.objects.filter(conference=self.level.conference, regtype=self.params['type']).exists():
+                       raise forms.ValidationError("Registration type '%s' does not exist" % self.params['type'])
 
        def generate_form(self):
                return EntryVouchersForm
 
        def save_form(self, form, claim, request):
-               j = _validate_params(self.level, self.params)
                if int(form.cleaned_data['vouchercount']) == 0:
                        # No vouchers --> unclaim this benefit
                        claim.claimdata = "0"
@@ -59,7 +44,7 @@ class EntryVouchers(BaseBenefit):
                else:
                        # Actual number, form has been validated, so create the vouchers.
                        batch = PrepaidBatch(conference=self.level.conference,
-                                                                regtype=RegistrationType.objects.get(conference=self.level.conference, regtype=j['type']),
+                                                                regtype=RegistrationType.objects.get(conference=self.level.conference, regtype=self.params['type']),
                                                                 buyer=request.user,
                                                                 buyername="%s %s" % (request.user.first_name, request.user.last_name),
                                                                 sponsor=claim.sponsor)
index 13bd32ed5d297ecefb0fd710a3fdb31e21bfdd67..0777c723d9dab6d679308ab5b6477557ab2cd15c 100644 (file)
@@ -1,37 +1,17 @@
 from django import forms
 from django.core.exceptions import ValidationError
 
-import json
 from PIL import ImageFile
 
 from postgresqleu.util.storage import InlineEncodedStorage
 
-from base import BaseBenefit
-
-def _validate_params(params):
-       try:
-               j = json.loads(params)
-               keys = set(j.keys())
-               if not keys.issuperset([u"format", u"xres", u"yres"]):
-                       raise Exception("Parameters 'format', 'xres' and 'yres' are mandatory")
-               if not keys.issubset([u"format", u"xres", u"yres", u"transparent"]):
-                       raise Exception("Only parameters 'format', 'xres', 'yres' and 'transparent' can be specified")
-               if int(j['xres']) < 1:
-                       raise Exception("Parameter 'xres' must be positive integer!")
-               if int(j['yres']) < 1:
-                       raise Exception("Parameter 'yres' must be positive integer!")
-
-               return j
-       except ValueError:
-               raise Exception("Can't parse JSON")
-
-class ImageUploadForm(forms.Form):
+from base import BaseBenefit, BaseBenefitForm
+
+class ImageUploadForm(BaseBenefitForm):
        decline = forms.BooleanField(label='Decline this benefit', required=False)
        image = forms.FileField(label='Image file', required=False)
 
-       def __init__(self, benefit, *args, **kwargs):
-               self.params = _validate_params(benefit.class_parameters)
-
+       def __init__(self, *args, **kwargs):
                super(ImageUploadForm, self).__init__(*args, **kwargs)
 
                self.fields['image'].help_text = "Upload a file in %s format, fitting in a box of %sx%s pixels." % (self.params['format'].upper(), self.params['xres'], self.params['yres'])
@@ -85,11 +65,12 @@ class ImageUploadForm(forms.Form):
 class ImageUpload(BaseBenefit):
        description = 'Require uploaded image'
        default_params = {"format": "png", "xres": 0, "yres": 0, "transparent": 0}
-       def validate_params(self):
-               try:
-                       _validate_params(self.params)
-               except Exception, e:
-                       return e
+       param_struct = {
+               'format': unicode,
+               'xres': int,
+               'yres': int,
+               'transparent': int,
+       }
 
        def generate_form(self):
                return ImageUploadForm
index 6ffd2bf1d5e0d805209714c9d1af84e433d12a67..b49910c18bcf8028d99881c44677b5c15c8fed01 100644 (file)
@@ -1,30 +1,12 @@
 from django.core.exceptions import ValidationError
 from django import forms
 
-import json
+from base import BaseBenefit, BaseBenefitForm
 
-from base import BaseBenefit
-
-def _validate_params(params):
-       try:
-               j = json.loads(params)
-               keys = set(j.keys())
-               if not keys.issubset([u"minwords", u"maxwords", u"minchars", u"maxchars"]):
-                       raise Exception("Only parameters 'minwords', 'maxwords', 'minchars', 'maxchars' can be specified")
-               return j
-       except ValueError:
-               raise Exception("Can't parse JSON")
-
-
-class ProvideTextForm(forms.Form):
+class ProvideTextForm(BaseBenefitForm):
        decline = forms.BooleanField(label='Decline this benefit', required=False)
        text = forms.CharField(label='Text', required=False, widget=forms.Textarea)
 
-       def __init__(self, benefit, *args, **kwargs):
-               self.params = _validate_params(benefit.class_parameters)
-
-               super(ProvideTextForm, self).__init__(*args, **kwargs)
-
        def clean(self):
                declined = self.cleaned_data.get('decline', False)
                if not declined:
@@ -42,24 +24,25 @@ class ProvideTextForm(forms.Form):
 
                d = self.cleaned_data['text']
                words = len(d.split())
-               if self.params.has_key('minchars') and len(d) < self.params['minchars']:
+               if self.params.get('minchars', 0) and len(d) < self.params['minchars']:
                        raise ValidationError('Must be at least %s characters.' % self.params['minchars'])
-               if self.params.has_key('maxchars') and len(d) > self.params['maxchars']:
+               if self.params.get('maxchars', 0) and len(d) > self.params['maxchars']:
                        raise ValidationError('Must be less than %s characters.' % self.params['maxchars'])
-               if self.params.has_key('minwords') and words < self.params['minwords']:
+               if self.params.get('minwords', 0) and words < self.params['minwords']:
                        raise ValidationError('Must be at least %s words.' % self.params['minwords'])
-               if self.params.has_key('maxwords') and words > self.params['maxwords']:
+               if self.params.get('maxwords', 0) and words > self.params['maxwords']:
                        raise ValidationError('Must be less than %s words.' % self.params['maxwords'])
                return d
 
 class ProvideText(BaseBenefit):
        description = "Provide text string"
        default_params = {"minwords": 0, "maxwords": 0, "minchars": 0, "maxchars": 0}
-       def validate_params(self):
-               try:
-                       _validate_params(self.params)
-               except Exception, e:
-                       return e
+       param_struct = {
+               'minwords': int,
+               'maxwords': int,
+               'minchars': int,
+               'maxchars': int,
+       }
 
        def generate_form(self):
                return ProvideTextForm
index d79def4bc093c91de9aae6cb9e8a89318b4ade4e..c0659208b89b35c4076fdbd0da5dcf6dbc47c87f 100644 (file)
@@ -1,21 +1,11 @@
 from django import forms
 from django.core.exceptions import ValidationError
 
-import json
+from base import BaseBenefit, BaseBenefitForm
 
-from base import BaseBenefit
-
-class RequireClaimingForm(forms.Form):
+class RequireClaimingForm(BaseBenefitForm):
        confirm = forms.ChoiceField(label="Claim benefit", choices=((0, '* Choose'), (1, 'Claim this benefit'), (2, 'Decline this benefit')))
 
-       def __init__(self, benefit, *args, **kwargs):
-               super(RequireClaimingForm, self).__init__(*args, **kwargs)
-
-               if benefit.class_parameters:
-                       params = json.loads(benefit.class_parameters)
-                       if params.has_key('claimcheckbox'):
-                               self.fields['confirm'].help_text = params['claimcheckbox']
-
        def clean_confirm(self):
                if not int(self.cleaned_data['confirm']) in (1,2):
                        raise ValidationError('You must decide if you want to claim this benefit')
@@ -23,30 +13,16 @@ class RequireClaimingForm(forms.Form):
 
 class RequireClaiming(BaseBenefit):
        description = "Requires explicit claiming"
-
-       def validate_params(self):
-               # Just see that it's valid json, and then pass it upwards
-               try:
-                       json.loads(self.params)
-               except Exception, e:
-                       return e
+       param_struct = {}
 
        def generate_form(self):
                return RequireClaimingForm
 
        def save_form(self, form, claim, request):
-               try:
-                       p = json.loads(self.params)
-               except Exception:
-                       p = {}
-
                if int(form.cleaned_data['confirm']) == 2:
                        # This is actually a deny
                        claim.declined = True
                        claim.confirmed = True
                        return True
 
-               if p.has_key('autoconfirm') and p['autoconfirm']:
-                       claim.confirmed = True
-                       return False
                return True