0004-add-ParametersCell-9596.patch
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 |
- |