Projet

Général

Profil

0001-general-revamp-cell-visibility-options-17001.patch

Frédéric Péters, 29 octobre 2018 12:21

Télécharger (16,9 ko)

Voir les différences:

Subject: [PATCH] general: revamp cell visibility options (#17001)

 combo/data/models.py                          | 42 ++++---------
 combo/manager/forms.py                        | 61 +++++++++++++++++++
 combo/manager/static/css/combo.manager.css    |  4 ++
 .../templates/combo/cell_visibility.html      | 18 +++++-
 combo/manager/templates/combo/page_view.html  |  3 +-
 combo/manager/views.py                        |  4 +-
 tests/test_manager.py                         | 51 +++++++++++++---
 tests/test_public.py                          | 53 +++++++++++++++-
 8 files changed, 193 insertions(+), 43 deletions(-)
combo/data/models.py
65 65

  
66 66

  
67 67
def element_is_visible(element, user=None):
68
    if hasattr(element, 'restricted_to_unlogged') and element.restricted_to_unlogged:
69
        return bool(user is None or user.is_anonymous())
70 68
    if element.public:
69
        if getattr(element, 'restricted_to_unlogged', None) is True:
70
            return (user is None or user.is_anonymous())
71 71
        return True
72
    if user is None:
73
        return False
74
    if user.is_anonymous():
72
    if user is None or user.is_anonymous():
75 73
        return False
76 74
    if user.is_superuser:
77 75
        return True
78 76
    page_groups = element.groups.all()
79 77
    if not page_groups:
80
        # user is logged in, no group restriction in place
81
        return True
82
    return len(set(page_groups).intersection(user.groups.all())) > 0
78
        groups_ok = True
79
    else:
80
        groups_ok = len(set(page_groups).intersection(user.groups.all())) > 0
81
    if getattr(element, 'restricted_to_unlogged', None) is True:
82
        return not(groups_ok)
83
    return groups_ok
83 84

  
84 85

  
85 86
class Placeholder(object):
......
478 479
    extra_css_class = models.CharField(_('Extra classes for CSS styling'), max_length=100, blank=True)
479 480

  
480 481
    public = models.BooleanField(_('Public'), default=True)
482
    # restricted_to_unlogged is actually an invert switch, it is used for mark
483
    # a cell as only visibile to unlogged users but also, when groups are set,
484
    # to mark the cell as visible to all but those groups.
481 485
    restricted_to_unlogged = models.BooleanField(
482 486
            _('Restrict to unlogged users'), default=False)
483 487
    groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
......
632 636
        return model_forms.modelform_factory(self.__class__,
633 637
                fields=['slug', 'extra_css_class'])
634 638

  
635
    def get_visibility_form_class(self):
636
        # generate a form with the 'public' model attribute inverted to create
637
        # a 'Private' checkbox.
638
        class OppositeBooleanField(forms.BooleanField):
639
            def prepare_value(self, value):
640
                return not value  # toggle the value when loaded from the model
641

  
642
            def to_python(self, value):
643
                value = super(OppositeBooleanField, self).to_python(value)
644
                return not value
645

  
646
        def formfield_callback(field):
647
            if field.name == 'public':
648
                return OppositeBooleanField(
649
                        label=_('Restrict to logged-in users'),
650
                        required=False)
651
            return field.formfield()
652

  
653
        return model_forms.modelform_factory(self.__class__,
654
                fields=['restricted_to_unlogged', 'public', 'groups'],
655
                formfield_callback=formfield_callback)
656

  
657 639
    def get_extra_manager_context(self):
658 640
        return {}
659 641

  
combo/manager/forms.py
16 16

  
17 17
from django import forms
18 18
from django.conf import settings
19
from django.contrib.auth.models import Group
19 20
from django.core.exceptions import ValidationError
20 21
from django.template.loader import get_template, TemplateDoesNotExist
21 22
from django.utils.translation import ugettext_lazy as _
......
99 100
        fields = ('exclude_from_navigation',)
100 101

  
101 102

  
103
def get_groups_as_choices():
104
    return [(x.id, x.name) for x in Group.objects.all().order_by('name')]
105

  
106

  
107
class CellVisibilityForm(forms.Form):
108
    visibility = forms.ChoiceField(
109
            label=_('Visibility'),
110
            choices=[('all', _('Everybody')),
111
                     ('logged', _('Logged users')),
112
                     ('unlogged', _('Unlogged users')),
113
                     ('groups-any', _('Users with one of these groups')),
114
                     ('groups-none', _('Users with none of these groups'))])
115
    groups = forms.MultipleChoiceField(
116
            label=_('Groups'),
117
            required=False,
118
            choices=get_groups_as_choices)
119

  
120
    def __init__(self, *args, **kwargs):
121
        self.instance = instance = kwargs.pop('instance')
122
        initial = kwargs.get('initial')
123
        initial['groups'] = [x.id for x in instance.groups.all()]
124
        if instance.public:
125
            if instance.restricted_to_unlogged:
126
                initial['visibility'] = 'unlogged'
127
            else:
128
                initial['visibility'] = 'all'
129
        else:
130
            initial['visibility'] = 'logged'
131
            if initial['groups']:
132
                if instance.restricted_to_unlogged:
133
                    initial['visibility'] = 'groups-none'
134
                else:
135
                    initial['visibility'] = 'groups-any'
136
        super(CellVisibilityForm, self).__init__(*args, **kwargs)
137

  
138
    def save(self):
139
        if self.cleaned_data['visibility'] == 'all':
140
            self.instance.public = True
141
            self.instance.restricted_to_unlogged = False
142
            self.instance.groups = []
143
        elif self.cleaned_data['visibility'] == 'logged':
144
            self.instance.public = False
145
            self.instance.restricted_to_unlogged = False
146
            self.instance.groups = []
147
        elif self.cleaned_data['visibility'] == 'unlogged':
148
            self.instance.public = True
149
            self.instance.restricted_to_unlogged = True
150
            self.instance.groups = []
151
        elif self.cleaned_data['visibility'] == 'groups-any':
152
            self.instance.public = False
153
            self.instance.restricted_to_unlogged = False
154
            self.instance.groups = self.cleaned_data['groups']
155
        elif self.cleaned_data['visibility'] == 'groups-none':
156
            self.instance.public = False
157
            self.instance.restricted_to_unlogged = True
158
            self.instance.groups = self.cleaned_data['groups']
159
        self.instance.save()
160
        return self.instance
161

  
162

  
102 163
class SiteImportForm(forms.Form):
103 164
    site_json = forms.FileField(label=_('Site Export File'))
combo/manager/static/css/combo.manager.css
83 83
	font-family: FontAwesome;
84 84
}
85 85

  
86
div.cell h3 span.visibility-summary.visibility-off::before {
87
	content: "\f070"; /* fa-eye-slash */
88
	font-family: FontAwesome;
89
}
86 90

  
87 91
div.cell h3 span.visibility-summary {
88 92
	max-width: 30%;
combo/manager/templates/combo/cell_visibility.html
7 7

  
8 8
{% block content %}
9 9

  
10
<form method="post" enctype="multipart/form-data">
10
<form method="post" enctype="multipart/form-data" id="visibility-form">
11 11
  {% csrf_token %}
12 12
  {{ form.as_p }}
13 13
  <div class="buttons">
......
18 18
    <a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a>
19 19
    {% endif %}
20 20
  </div>
21
  <script>
22
  $(function() {
23
    var $form = $('#visibility-form');
24
    $form.find('select').first().on('change', function() {
25
      var val = $(this).val();
26
      var $groups = $form.find('select').last().parent('p');
27
      if (val == 'groups-on' || val == 'groups-off') {
28
        $groups.show();
29
      } else {
30
        $groups.hide();
31
      }
32
    });
33
    $form.find('select').first().trigger('change');
34
  });
35
  </script>
21 36
</form>
37

  
22 38
{% endblock %}
combo/manager/templates/combo/page_view.html
107 107
                        <i>{{cell.get_additional_label|default_if_none:""}}</i></span>
108 108
                </span>
109 109
                {% if not cell.public %}
110
                  <span class="visibility-summary" title="{% trans 'Restricted visibility' %}">
110
                  <span class="visibility-summary
111
                    {% if cell.restricted_to_unlogged %}visibility-off{% endif %}" title="{% trans 'Restricted visibility' %}">
111 112
                  {% if cell.groups.all|length %}
112 113
                  {% for group in cell.groups.all %}{{group.name}}{% if not forloop.last %}, {% endif %}{% endfor %}
113 114
                  {% else %}
combo/manager/views.py
40 40
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm,
41 41
        PageEditRedirectionForm, PageSelectTemplateForm, PageEditSlugForm,
42 42
        PageEditPictureForm, PageEditExcludeFromNavigationForm,
43
        PageEditDescriptionForm)
43
        PageEditDescriptionForm, CellVisibilityForm)
44 44

  
45 45

  
46 46
class HomepageView(ListView):
......
412 412
    template_name = 'combo/cell_visibility.html'
413 413

  
414 414
    def get_form_class(self):
415
        return self.object.get_visibility_form_class()
415
        return CellVisibilityForm
416 416

  
417 417
page_cell_visibility = PageCellVisibilityView.as_view()
418 418

  
tests/test_manager.py
9 9
from django.core.files.storage import default_storage
10 10
from django.core.urlresolvers import reverse
11 11
from django.conf import settings
12
from django.contrib.auth.models import User
12
from django.contrib.auth.models import User, Group
13 13
from django.template import TemplateSyntaxError
14 14
from django.test import override_settings
15 15
from django.utils.http import urlencode
......
442 442
    app = login(app)
443 443
    resp = app.get('/manage/pages/%s/' % page.id)
444 444
    resp = resp.click(href='/data_textcell-%s/visibility' % cell.id)
445
    assert resp.form['cdata_textcell-%s-restricted_to_unlogged' % cell.id].checked is False
446
    assert resp.form['cdata_textcell-%s-public' % cell.id].checked is False
447
    resp.form['cdata_textcell-%s-public' % cell.id].checked = True
445
    assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'all'
446
    resp.form['cdata_textcell-%s-visibility' % cell.id] = 'logged'
448 447
    resp = resp.form.submit('submit')
449 448
    assert TextCell.objects.get(id=cell.id).public is False
449
    assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
450
    assert TextCell.objects.get(id=cell.id).groups.count() == 0
450 451

  
451 452
    resp = app.get('/manage/pages/%s/' % page.id)
452 453
    resp = resp.click(href='/data_textcell-%s/visibility' % cell.id)
453
    assert resp.form['cdata_textcell-%s-restricted_to_unlogged' % cell.id].checked is False
454
    assert resp.form['cdata_textcell-%s-public' % cell.id].checked is True
455
    resp.form['cdata_textcell-%s-public' % cell.id].checked = False
456
    resp.form['cdata_textcell-%s-restricted_to_unlogged' % cell.id].checked = True
454
    assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'logged'
455
    resp.form['cdata_textcell-%s-visibility' % cell.id] = 'unlogged'
457 456
    resp = resp.form.submit('submit')
458 457
    assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is True
459 458
    assert TextCell.objects.get(id=cell.id).public is True
459
    assert TextCell.objects.get(id=cell.id).groups.count() == 0
460

  
461
    resp = app.get('/manage/pages/%s/' % page.id)
462
    resp = resp.click(href='/data_textcell-%s/visibility' % cell.id)
463
    assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'unlogged'
464
    resp.form['cdata_textcell-%s-visibility' % cell.id] = 'all'
465
    resp = resp.form.submit('submit')
466
    assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
467
    assert TextCell.objects.get(id=cell.id).public is True
468
    assert TextCell.objects.get(id=cell.id).groups.count() == 0
469

  
470
    group1 = Group(name='A group')
471
    group1.save()
472
    group2 = Group(name='Another group')
473
    group2.save()
474

  
475
    resp = app.get('/manage/pages/%s/' % page.id)
476
    resp = resp.click(href='/data_textcell-%s/visibility' % cell.id)
477
    resp.form['cdata_textcell-%s-visibility' % cell.id] = 'groups-any'
478
    resp.form['cdata_textcell-%s-groups' % cell.id] = [group1.id]
479
    resp = resp.form.submit('submit')
480
    assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
481
    assert TextCell.objects.get(id=cell.id).public is False
482
    assert TextCell.objects.get(id=cell.id).groups.count() == 1
483
    assert TextCell.objects.get(id=cell.id).groups.all()[0].name == 'A group'
484

  
485
    resp = app.get('/manage/pages/%s/' % page.id)
486
    resp = resp.click(href='/data_textcell-%s/visibility' % cell.id)
487
    resp.form['cdata_textcell-%s-visibility' % cell.id] = 'groups-none'
488
    resp.form['cdata_textcell-%s-groups' % cell.id] = [group2.id]
489
    resp = resp.form.submit('submit')
490
    assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is True
491
    assert TextCell.objects.get(id=cell.id).public is False
492
    assert TextCell.objects.get(id=cell.id).groups.count() == 1
493
    assert TextCell.objects.get(id=cell.id).groups.all()[0].name == 'Another group'
494

  
460 495

  
461 496
def test_edit_cell_options(app, admin_user):
462 497
    Page.objects.all().delete()
tests/test_public.py
8 8
import os
9 9

  
10 10
from django.conf import settings
11
from django.contrib.auth.models import User
11
from django.contrib.auth.models import User, Group
12 12
from django.core.urlresolvers import reverse
13 13
from django.db import connection
14 14
from django.utils.http import quote
......
25 25

  
26 26
from .test_manager import admin_user, login
27 27

  
28
@pytest.fixture
29
def normal_user():
30
    try:
31
        user = User.objects.get(username='normal-user')
32
        user.groups = []
33
    except User.DoesNotExist:
34
        user = User.objects.create_user(username='normal-user', email='', password='normal-user')
35
    return user
36

  
28 37
def test_missing_index(app):
29 38
    Page.objects.all().delete()
30 39
    resp = app.get('/', status=200)
......
64 73
    resp = app.get('/', status=200)
65 74
    assert not 'Foobar' in resp.text
66 75

  
76
def test_page_contents_group_presence(app, normal_user):
77
    group = Group(name='plop')
78
    group.save()
79
    Page.objects.all().delete()
80
    page = Page(title='Home', slug='index', template_name='standard')
81
    page.save()
82
    cell = TextCell(page=page, placeholder='content', text='Foobar', order=0,
83
            public=False)
84
    cell.save()
85
    cell.groups = [group]
86
    resp = app.get('/', status=200)
87
    assert 'Foobar' not in resp.text
88

  
89
    app = login(app, username='normal-user', password='normal-user')
90
    resp = app.get('/', status=200)
91
    assert 'Foobar' not in resp.text
92

  
93
    normal_user.groups = [group]
94
    resp = app.get('/', status=200)
95
    assert 'Foobar' in resp.text
96

  
97
def test_page_contents_group_absence(app, normal_user):
98
    group = Group(name='plop')
99
    group.save()
100
    Page.objects.all().delete()
101
    page = Page(title='Home', slug='index', template_name='standard')
102
    page.save()
103
    cell = TextCell(page=page, placeholder='content', text='Foobar', order=0,
104
            public=False, restricted_to_unlogged=True)
105
    cell.save()
106
    cell.groups = [group]
107
    resp = app.get('/', status=200)
108
    assert 'Foobar' not in resp.text
109

  
110
    app = login(app, username='normal-user', password='normal-user')
111
    resp = app.get('/', status=200)
112
    assert 'Foobar' in resp.text
113

  
114
    normal_user.groups = [group]
115
    resp = app.get('/', status=200)
116
    assert 'Foobar' not in resp.text
117

  
67 118
def test_page_footer_acquisition(app):
68 119
    Page.objects.all().delete()
69 120
    page = Page(title='Home', slug='index', template_name='standard')
70
-