diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2018-05-23 17:13:35 +0200 | 
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2018-06-12 08:49:06 +0200 | 
| commit | d807e2b7b96935174135bc76b6a22f3a0100bd95 (patch) | |
| tree | 427ba1a5c67ec7d578549f64dbd3496d72ec37ca | |
| parent | 88c9aeaeafd9bc77decbf0982be0fd9d20fd3993 (diff) | |
| download | Ishtar-d807e2b7b96935174135bc76b6a22f3a0100bd95.tar.bz2 Ishtar-d807e2b7b96935174135bc76b6a22f3a0100bd95.zip | |
Wizards - JSON fields: Manage field display in forms - management in wizards save and form init (refs #4089)
| -rw-r--r-- | archaeological_operations/templates/ishtar/sheet_operation.html | 4 | ||||
| -rw-r--r-- | ishtar_common/forms.py | 78 | ||||
| -rw-r--r-- | ishtar_common/migrations/0053_auto_20180523_1504.py (renamed from ishtar_common/migrations/0053_auto_20180523_1126.py) | 4 | ||||
| -rw-r--r-- | ishtar_common/models.py | 3 | ||||
| -rw-r--r-- | ishtar_common/wizards.py | 45 | 
5 files changed, 118 insertions, 16 deletions
| diff --git a/archaeological_operations/templates/ishtar/sheet_operation.html b/archaeological_operations/templates/ishtar/sheet_operation.html index 7999dc21d..f3014eaf4 100644 --- a/archaeological_operations/templates/ishtar/sheet_operation.html +++ b/archaeological_operations/templates/ishtar/sheet_operation.html @@ -115,10 +115,10 @@      {% field_flex_full "Comment" item.comment "<pre>" "</pre>" %}      {% field_flex_full "Abstract" item.abstract "<pre>" "</pre>" %}      {% field_flex_full "Comment about scientific documentation" item.scientific_documentation_comment "<pre>" "</pre>" %} - -    {% include "ishtar/blocks/sheet_json.html" %}  </div> +{% include "ishtar/blocks/sheet_json.html" %} +  {% if item.virtual_operation %}  <div class="alert alert-warning" role="alert">      {% trans "This operation is virtual." %} diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index c314e4f13..5de0db91d 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -20,6 +20,7 @@  """  Forms definition  """ +from collections import OrderedDict  import datetime  import re  import types @@ -107,6 +108,16 @@ def get_readonly_clean(key):      return func +JSON_VALUE_TYPES_FIELDS = { +    'T': (forms.CharField, None), +    'LT': (forms.CharField, forms.Textarea), +    'I': (forms.IntegerField, None), +    'F': (forms.FloatField, None), +    'D': (DateField, None), +    'C': (forms.CharField, None), +} + +  class CustomForm(object):      form_admin_name = ""      form_slug = "" @@ -120,8 +131,7 @@ class CustomForm(object):              except AttributeError:                  pass          super(CustomForm, self).__init__(*args, **kwargs) -        available, excluded = self.check_availability_and_excluded_fields( -            current_user) +        available, excluded, json_fields = self.check_custom_form(current_user)          for exc in excluded:              if hasattr(self, 'fields'):                  self.remove_field(exc) @@ -131,6 +141,25 @@ class CustomForm(object):                      if exc in form.fields:                          form.fields.pop(exc) +        new_fields = {} +        for order, key, field in json_fields: +            while order in new_fields:  # json fields with the same number +                order += 1 +            new_fields[order] = (key, field) + +        if not new_fields: +            return + +        # re-order for json fields +        fields = OrderedDict() +        for idx, field in enumerate(self.fields.items()): +            key, c_field = field +            if idx + 1 in new_fields: +                alt_key, alt_field = new_fields[idx + 1] +                fields[alt_key] = alt_field +            fields[key] = c_field +        self.fields = fields +      def are_available(self, keys):          for k in keys:              if k not in self.fields: @@ -142,9 +171,28 @@ class CustomForm(object):              self.fields.pop(key)      @classmethod -    def check_availability_and_excluded_fields(cls, current_user): +    def _get_json_fields(cls, custom_form): +        fields = [] +        for field in custom_form.json_fields.order_by('order').all(): +            key = "data__" + field.json_field.key +            field_cls, widget = forms.CharField, None +            if field.json_field.value_type in JSON_VALUE_TYPES_FIELDS: +                field_cls, widget = JSON_VALUE_TYPES_FIELDS[ +                    field.json_field.value_type] +            attrs = {'label': field.label or field.json_field.name, +                     'required': False} +            if field.help_text: +                attrs['help_text'] = field.help_text +            if widget: +                attrs['widget'] = widget(attrs={"class": "form-control"}) +            f = field_cls(**attrs) +            fields.append((field.order or 1, key, f)) +        return fields + +    @classmethod +    def check_custom_form(cls, current_user):          if not current_user: -            return True, [] +            return True, [], []          base_q = {"form": cls.form_slug, 'available': True}          # order is important : try for user, user type then all          query_dicts = [] @@ -159,23 +207,31 @@ class CustomForm(object):          dct = base_q.copy()          dct.update({'apply_to_all': True})          query_dicts.append(dct) -        excluded_lst = [] +        form = None          for query_dict in query_dicts:              q = models.CustomForm.objects.filter(**query_dict)              if not q.count():                  continue              # todo: prevent multiple result in database              form = q.all()[0] -            if not form.enabled: -                return False, [] -            for excluded in form.excluded_fields.all(): -                # could have be filtered previously -                excluded_lst.append(excluded.field)              break -        return True, excluded_lst +        if not form: +            return True, [], [] +        if not form.enabled: +            return False, [], [] +        excluded_lst = [] +        for excluded in form.excluded_fields.all(): +            # could have be filtered previously +            excluded_lst.append(excluded.field) +        json_fields = cls._get_json_fields(form) +        return True, excluded_lst, json_fields      @classmethod      def get_custom_fields(cls): +        """ +        Get fields than can be customized: excluded, re-ordered (WIP) or +        re-labeled (WIP) +        """          if hasattr(cls, 'base_fields'):              fields = cls.base_fields          else: diff --git a/ishtar_common/migrations/0053_auto_20180523_1126.py b/ishtar_common/migrations/0053_auto_20180523_1504.py index f776dce30..14e34a866 100644 --- a/ishtar_common/migrations/0053_auto_20180523_1126.py +++ b/ishtar_common/migrations/0053_auto_20180523_1504.py @@ -1,5 +1,5 @@  # -*- coding: utf-8 -*- -# Generated by Django 1.11.10 on 2018-05-23 11:26 +# Generated by Django 1.11.10 on 2018-05-23 15:04  from __future__ import unicode_literals  from django.db import migrations, models @@ -17,7 +17,9 @@ class Migration(migrations.Migration):              name='CustomFormJsonField',              fields=[                  ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), +                ('label', models.CharField(blank=True, default=b'', max_length=200, verbose_name='Label')),                  ('order', models.IntegerField(default=1, verbose_name='Order')), +                ('help_text', models.TextField(blank=True, null=True, verbose_name='Help')),                  ('custom_form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='json_fields', to='ishtar_common.CustomForm')),              ],              options={ diff --git a/ishtar_common/models.py b/ishtar_common/models.py index bb285883f..e4c6d0e9a 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1893,7 +1893,10 @@ class CustomFormJsonField(models.Model):      custom_form = models.ForeignKey(CustomForm, related_name='json_fields')      json_field = models.ForeignKey(JsonDataField,                                     related_name='custom_form_details') +    label = models.CharField(_(u"Label"), max_length=200, blank=True, +                             default='')      order = models.IntegerField(verbose_name=_(u"Order"), default=1) +    help_text = models.TextField(_(u"Help"), blank=True, null=True)      class Meta:          verbose_name = _(u"Custom form - Json data field") diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index a6844b674..40044f100 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -120,8 +120,12 @@ def filter_no_fields_form(form, other_check=None):          if not hasattr(self.request.user, 'ishtaruser'):              return False          if issubclass(form, CustomForm): -            enabled, exc = form.check_availability_and_excluded_fields( +            enabled, excluded, json_fields = form.check_custom_form(                  self.request.user.ishtaruser) +            if not hasattr(self, 'json_fields'): +                self.json_fields = {} +            self.json_fields[form.form_slug] = [ +                key for order, key, field in json_fields]              if not enabled:                  return False          if other_check: @@ -595,11 +599,27 @@ class Wizard(NamedUrlWizardView):                     return_object):          dct = self.get_extra_model(dct, form_list)          obj = self.get_current_saved_object() -        # manage dependant items +        data = {} +        if obj: +            data = obj.data + +        # manage dependant items and json fields          other_objs = {}          for k in dct.keys():              if '__' not in k:                  continue +            # manage json field +            if k.startswith('data__'): +                data_keys = k[len('data__'):].split('__') +                # tree +                current_data = data +                for data_key in data_keys[:-1]: +                    if data_key not in current_data: +                        current_data[data_key] = {} +                    current_data = current_data[data_key] +                current_data[data_keys[-1]] = dct.pop(k) +                continue +              vals = k.split('__')              assert len(vals) == 2, \                  "Only one level of dependant item is managed" @@ -627,6 +647,7 @@ class Wizard(NamedUrlWizardView):                      elif type(dct[k]) not in (list, tuple):                          dct[k] = [dct[k]]                  setattr(obj, k, dct[k]) +            obj.data = data              if hasattr(obj, 'pre_save'):                  obj.pre_save()              try: @@ -711,6 +732,7 @@ class Wizard(NamedUrlWizardView):              except ValidationError as e:                  logger.warning(unicode(e))                  return self.render(form_list[-1]) +            obj.data = data              obj.save(**saved_args)              for k in adds:                  getattr(obj, k).add(adds[k]) @@ -1243,6 +1265,25 @@ class Wizard(NamedUrlWizardView):          Get initial data from an object: simple form          """          initial = MultiValueDict() + +        # manage json field +        if hasattr(self, 'json_fields') \ +                and getattr(c_form, 'form_slug', None) \ +                and c_form.form_slug in self.json_fields \ +                and obj.data: +            for key in self.json_fields[c_form.form_slug]: +                if not key.startswith('data__'): +                    continue +                json_keys = key[len('data__'):].split('__') +                value = obj.data +                for json_key in json_keys: +                    if json_key not in value: +                        value = None +                        break +                    value = value[json_key] +                if value: +                    initial[key] = value +          for base_field in c_form.base_fields.keys():              value = obj              base_model = None | 
