0001-general-revamp-cell-visibility-options-17001.patch
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 |
- |