Projet

Général

Profil

0004-add-ParametersCell-9596.patch

Benjamin Dauvergne, 17 janvier 2016 23:52

Télécharger (14,3 ko)

Voir les différences:

Subject: [PATCH 4/6] add ParametersCell (#9596)

The parameter cell create a combox box which update a context variable named,
parameters. Any change to the combobox modify the query string of the current
URL, which modifies the parameters dictionnary in the context. This dictionnary
can be looked up by other celle for parameters. For example the cubes cells
could look for any key with prefix cubes-cut-, for example
cubes-cut-comune=CLAPIERS and produces a cut filter from it.
 combo/data/forms.py                               | 35 +++++++++
 combo/data/migrations/0013_parameterscell.py      | 37 +++++++++
 combo/data/models.py                              | 95 ++++++++++++++++++++++-
 combo/public/static/js/combo.public.js            | 64 +++++++++++++++
 combo/public/templates/combo/parameters-cell.html | 12 +++
 combo/public/templates/combo/placeholder.html     |  3 +-
 6 files changed, 244 insertions(+), 2 deletions(-)
 create mode 100644 combo/data/forms.py
 create mode 100644 combo/data/migrations/0013_parameterscell.py
 create mode 100644 combo/public/templates/combo/parameters-cell.html
combo/data/forms.py
1
from django import forms
2

  
3
from .models import ParametersCell
4
from jsonfield.widgets import JSONWidget
5

  
6
class ParametersForm(forms.Form):
7
    choice = forms.ChoiceField(choices=[])
8

  
9
    def __init__(self, *args, **kwargs):
10
        self.parameters = kwargs.pop('parameters')
11
        empty_label = kwargs.pop('empty_label')
12
        super(ParametersForm, self).__init__(*args, **kwargs)
13
        self.fields['choice'].choices = [('', empty_label)] + [(x['name'], x['name']) for x in self.parameters]
14

  
15
    def clean(self):
16
        choice = self.cleaned_data.get('choice')
17
        for parameter in self.parameters:
18
            if parameter['name'] == choice:
19
                self.cleaned_data['choice'] = parameter['value']
20
                break
21
        return self.cleaned_data
22

  
23
class ParametersCellForm(forms.ModelForm):
24
    class Meta:
25
        model = ParametersCell
26
        fields = ('title', 'url', 'empty_label', 'parameters',)
27
        widgets = {
28
            # XXX: replace with a more ergonomic widget
29
            'parameters': JSONWidget(
30
                attrs={
31
                    'rows': 30,
32
                    'cols': 100,
33
                    'style': 'resize: none'
34
                })
35
        }
combo/data/migrations/0013_parameterscell.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5
import jsonfield.fields
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('auth', '0001_initial'),
12
        ('data', '0012_auto_20151029_1535'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='ParametersCell',
18
            fields=[
19
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20
                ('placeholder', models.CharField(max_length=20)),
21
                ('order', models.PositiveIntegerField()),
22
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
23
                ('public', models.BooleanField(default=True, verbose_name='Public')),
24
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
25
                ('title', models.CharField(max_length=150, verbose_name='Title', blank=True)),
26
                ('url', models.URLField(verbose_name='URL', blank=True)),
27
                ('empty_label', models.CharField(default=b'---', max_length=64, verbose_name='Empty label')),
28
                ('parameters', jsonfield.fields.JSONField(default=dict, help_text='Must be a JSON list, containing dictionnaries with 3 keys: name, value and optionnally roles; name must be a string, value must be a dictionnary and roles must a list of role names. Role names limit the visibility of the choice.', verbose_name='Parameters', blank=True)),
29
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
30
                ('page', models.ForeignKey(to='data.Page')),
31
            ],
32
            options={
33
                'verbose_name': 'Parameters',
34
            },
35
            bases=(models.Model,),
36
        ),
37
    ]
combo/data/models.py
24 24
from django.contrib.auth.models import Group
25 25
from django.contrib.contenttypes.models import ContentType
26 26
from django.core.cache import cache
27
from django.core.exceptions import ObjectDoesNotExist
27
from django.core.exceptions import ObjectDoesNotExist, ValidationError
28 28
from django.core import serializers
29 29
from django.db import models
30 30
from django.db.models.base import ModelBase
......
35 35
from django.utils.safestring import mark_safe
36 36
from django.utils.translation import ugettext_lazy as _
37 37
from django.forms.widgets import MediaDefiningClass
38
from django.contrib.auth.models import Group
38 39

  
39 40
from ckeditor.fields import RichTextField
40 41
import cmsplugin_blurp.utils
41 42

  
43
from jsonfield import JSONField
44

  
42 45
from .library import register_cell_class, get_cell_classes, get_cell_class
43 46

  
44 47
from combo import utils
......
556 559
        if not context.get('synchronous') and feed_content is None:
557 560
            raise NothingInCacheException()
558 561
        return super(FeedCell, self).render(context)
562

  
563

  
564
@register_cell_class
565
class ParametersCell(CellBase):
566
    title = models.CharField(_('Title'), max_length=150, blank=True)
567
    url = models.URLField(_('URL'), blank=True)
568
    empty_label = models.CharField(_('Empty label'), max_length=64, default='---')
569
    parameters = JSONField(_('Parameters'), blank=True,
570
                           help_text=_('Must be a JSON list, containing dictionnaries with 3 keys: '
571
                                       'name, value and optionnally roles; name must be a string, '
572
                                       'value must be a dictionnary and roles must a list of role '
573
                                       'names. Role names limit the visibility of the choice.'))
574

  
575
    template_name = 'combo/parameters-cell.html'
576

  
577
    class Meta:
578
        verbose_name = _('Parameters')
579

  
580
    def get_additional_label(self):
581
        return self.title
582

  
583
    def is_visible(self, user=None):
584
        return bool(self.parameters) and super(ParametersCell, self).is_visible(user=user)
585

  
586
    def validate_schema(self, value):
587
        if not isinstance(value, list):
588
            return False, _('it must be a list')
589
        if not all(isinstance(x, dict) for x in value):
590
            return False, _('it must be a list of dictionnaries')
591
        if not all(set(x.keys()) <= set(['roles', 'name', 'value']) for x in value):
592
            return False, _('permitted keys in the dictionnaries are name, roles and value')
593
        for x in value:
594
            if 'roles' not in x:
595
                continue
596
            if not isinstance(x['roles'], list):
597
                return False, _('roles must be a list')
598
            if not all(isinstance(y, unicode) for y in x['roles']):
599
                return False, _('roles must be a list of strings')
600
            if len(set(x['roles'])) != len(x['roles']):
601
                return False, _('role\'s names must be unique in a list of roles')
602
            existing = Group.objects.filter(name__in=x['roles']).values_list('name', flat=True)
603
            if len(existing) != len(x['roles']):
604
                l = u', '.join(set(x['roles']) - set(existing))
605
                return False, _('role(s) %s do(es) not exist') % l
606
        if not all(isinstance(x['name'], unicode) for x in value):
607
            return False, _('name must be a string')
608
        if not all(isinstance(x['value'], dict) for x in value):
609
            return Falsea, ('value must be a dictionnary')
610
        if not len(set(x['name'] for x in value)) == len(value):
611
            return False, _('names must be unique')
612
        return True, ''
613

  
614
    def clean(self):
615
        validated, msg = self.validate_schema(self.parameters)
616
        if not validated:
617
            raise ValidationError(_('Parameters does not validate the expected schema: %s') % msg)
618

  
619
    def get_form(self, request):
620
        from .forms import ParametersForm
621
        if not request.user.is_anonymous():
622
            groups = set(request.user.groups.values_list('name', flat=True))
623
        else:
624
            groups = set()
625
        parameters = [param for param in self.parameters
626
                      if not param.get('roles') or set(param['roles']) & groups]
627
        return ParametersForm(request.GET, parameters=parameters,
628
                              empty_label=self.empty_label,
629
                              prefix='parameters-cells-' + str(self.pk))
630

  
631
    def modify_global_context(self, context, request):
632
        if not bool(self.parameters):
633
            return
634
        # Store form for later use by get_cell_extra_context
635
        self._form = self.get_form(request)
636
        if self._form.is_valid():
637
            parameters = context['parameters'] if 'parameters' in context else {}
638
            context['parameters'] = parameters
639
            parameters.update(self._form.cleaned_data['choice'])
640

  
641
    def get_cell_extra_context(self, context):
642
        ctx = super(ParametersCell, self).get_cell_extra_context(context)
643
        if hasattr(self, '_form'):
644
            ctx['form'] = self._form
645
        ctx['title'] = self.title
646
        ctx['url'] = self.url
647
        return ctx
648

  
649
    def get_default_form_class(self):
650
        from .forms import ParametersCellForm
651
        return ParametersCellForm
combo/public/static/js/combo.public.js
6 6
          crossDomain: true,
7 7
          success: function(data) {
8 8
              var parent = $(elem).parent();
9
              $(elem).parent().parent().addClass('ajax-loaded');
9 10
              if (data == '') {
10 11
                  $(elem).remove();
11 12
              } else {
......
20 21
  });
21 22
}
22 23

  
24
function combo_modify_query_string(name, value) {
25
    var search = window.location.search.substring(1);
26
    var parts = search.split('&');
27
    var newparts = [];
28
    var modified = 0;
29
    value = encodeURIComponent(value);
30
    for (var i = 0; i < parts.length; i++) {
31
        if (! parts[i]) {
32
            continue;
33
        }
34
        var pair = parts[i].split('=');
35
        if (pair[0] == name) {
36
            if (value) {
37
                newparts.push(name + '=' + value);
38
            }
39
            modified = 1;
40
        } else {
41
            newparts.push(parts[i]);
42
        }
43
    }
44
    if (! modified && value) {
45
        newparts.push(name + '=' + value);
46
    }
47
    if (newparts) {
48
        search = '?' + newparts.join('&');
49
    } else {
50
        search = '';
51
    }
52
    return search;
53
}
54

  
23 55
$(function() {
24 56
  $('[data-ajax-cell-refresh]').each(function(idx, elem) {
25 57
    var $elem = $(elem);
......
37 69
  $('.togglable').on('click', function() {
38 70
    $(this).toggleClass('toggled');
39 71
  });
72
  /* reload cells when parameters changes */
73
  function combo_refresh_ajax_cells() {
74
    $('[data-ajax-cell-url]').each(function(idx, elem) {
75
      var $elem = $(elem);
76
      var $div = $elem.find('> div');
77
      $div.empty();
78
      var msg = $(elem).data('ajax-cell-loading-message');
79
      $div.append($('<span class="loading-message">' + msg + '</span>'));
80
      combo_load_cell($div.find('span'), $elem.data('ajax-cell-url'));
81
    });
82
  }
83
  if (window.history.pushState) {
84
      /* set initial state */
85
      window.history.pushState("reload", "", window.location.href);
86
      $(window).on('popstate', function (event) {
87
        if (event.originalEvent.state == "reload") {
88
          combo_refresh_ajax_cells();
89
        }
90
      });
91
  }
92
  $(document).on('change', '.combo-parameters-cell-form select', function (e) {
93
    var $target = $(e.target);
94
    var value = $target.val();
95
    var name = $target[0].name;
96
    var new_qs = combo_modify_query_string(name, value);
97
    if (window.history.pushState) {
98
        window.history.pushState("reload", "", window.location.pathname + new_qs);
99
        combo_refresh_ajax_cells();
100
    } else {
101
        window.location.search = new_qs;
102
    }
103
  });
40 104
});
combo/public/templates/combo/parameters-cell.html
1
{% if form %}
2
    {% if title %}
3
    <h2>{% if url %}<a href="{{url}}">{% endif %}{{title}}{% if url %}</a>{% endif %}</h2>
4
    {% endif %}
5
    <form class="combo-parameters-cell-form" method="get">
6
      {{ form.choice }}
7
    </form>
8
    <!--
9
      Parameters: {{ parameters|pprint }}
10
      Choice: {{ form.cleaned_data.choice|pprint }}
11
    -->
12
{% endif %}
combo/public/templates/combo/placeholder.html
1
{% load combo %}
1
{% load combo i18n %}
2 2
{% for cell in cells %}
3 3
<div class="cell {{ cell.css_class_name }} {% if cell.slug %}{{cell.slug}}{% endif %}"
4 4
     data-ajax-cell-url="{{ site_base }}{% url 'combo-public-ajax-page-cell' page_pk=cell.page.id cell_reference=cell.get_reference %}"
5
     data-ajax-cell-loading-message="{% trans "Loading..." %}"
5 6
     {% if cell.ajax_refresh %}
6 7
     data-ajax-cell-refresh="{{ cell.ajax_refresh }}"
7 8
     {% endif %}><div>{% render_cell cell %}</div></div>
8
-