From 83c8db1ab53ccdb3617aeb41f59e940e2bb16348 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 6 Jun 2017 12:17:33 +0100 Subject: [PATCH] Add admin interface for setting collection view restrictions --- wagtail/wagtailadmin/forms.py | 28 ++++++-- .../collection_privacy/ancestor_privacy.html | 8 +++ .../collection_privacy/set_privacy.html | 19 +++++ .../collection_privacy/set_privacy.js | 27 +++++++ .../collection_privacy/set_privacy_done.js | 4 ++ .../collections/_privacy_switch.html | 14 ++++ .../wagtailadmin/collections/edit.html | 12 ++++ .../templates/wagtailadmin/generic/edit.html | 1 + .../templatetags/wagtailadmin_tags.py | 25 ++++++- wagtail/wagtailadmin/urls/collections.py | 3 +- .../wagtailadmin/views/collection_privacy.py | 72 +++++++++++++++++++ wagtail/wagtailadmin/views/collections.py | 1 + 12 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.html create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.js create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy_done.js create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collections/_privacy_switch.html create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/collections/edit.html create mode 100644 wagtail/wagtailadmin/views/collection_privacy.py diff --git a/wagtail/wagtailadmin/forms.py b/wagtail/wagtailadmin/forms.py index cc921defb..4cd689427 100644 --- a/wagtail/wagtailadmin/forms.py +++ b/wagtail/wagtailadmin/forms.py @@ -21,7 +21,9 @@ from taggit.managers import TaggableManager from wagtail.wagtailadmin import widgets from wagtail.wagtailcore.models import ( - Collection, GroupCollectionPermission, Page, PageViewRestriction + BaseViewRestriction, + Collection, CollectionViewRestriction, GroupCollectionPermission, + Page, PageViewRestriction ) @@ -192,29 +194,43 @@ class CopyForm(forms.Form): return cleaned_data -class PageViewRestrictionForm(forms.ModelForm): +class BaseViewRestrictionForm(forms.ModelForm): restriction_type = forms.ChoiceField( - label=ugettext_lazy("Visibility"), choices=PageViewRestriction.RESTRICTION_CHOICES, + label=ugettext_lazy("Visibility"), choices=BaseViewRestriction.RESTRICTION_CHOICES, widget=forms.RadioSelect) def __init__(self, *args, **kwargs): - super(PageViewRestrictionForm, self).__init__(*args, **kwargs) + super(BaseViewRestrictionForm, self).__init__(*args, **kwargs) self.fields['groups'].widget = forms.CheckboxSelectMultiple() self.fields['groups'].queryset = Group.objects.all() def clean_password(self): password = self.cleaned_data.get('password') - if self.cleaned_data.get('restriction_type') == PageViewRestriction.PASSWORD and not password: + if self.cleaned_data.get('restriction_type') == BaseViewRestriction.PASSWORD and not password: raise forms.ValidationError(_("This field is required."), code='invalid') return password def clean_groups(self): groups = self.cleaned_data.get('groups') - if self.cleaned_data.get('restriction_type') == PageViewRestriction.GROUPS and not groups: + if self.cleaned_data.get('restriction_type') == BaseViewRestriction.GROUPS and not groups: raise forms.ValidationError(_("Please select at least one group."), code='invalid') return groups + class Meta: + model = BaseViewRestriction + fields = ('restriction_type', 'password', 'groups') + + +class CollectionViewRestrictionForm(BaseViewRestrictionForm): + + class Meta: + model = CollectionViewRestriction + fields = ('restriction_type', 'password', 'groups') + + +class PageViewRestrictionForm(BaseViewRestrictionForm): + class Meta: model = PageViewRestriction fields = ('restriction_type', 'password', 'groups') diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html new file mode 100644 index 000000000..0e9a26a76 --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html @@ -0,0 +1,8 @@ +{% load i18n %} +{% trans "Collection privacy" as title_str %} +{% include "wagtailadmin/shared/header.html" with title=title_str icon="no-view" %} + +
+

{% trans "This collection has been made private by a parent collection." %}

+

{% trans "You can edit the privacy settings on:" %} {{ collection_with_restriction.name }} +

diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.html b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.html new file mode 100644 index 000000000..4c0b6fb15 --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.html @@ -0,0 +1,19 @@ +{% load i18n %} +{% trans "Collection privacy" as title_str %} +{% include "wagtailadmin/shared/header.html" with title=title_str icon="no-view" %} + +
+

{% trans "Privacy settings determine who is able to view documents in this collection." %}

+
+ {% csrf_token %} +
    + {% include "wagtailadmin/shared/field_as_li.html" with field=form.restriction_type %} + {% include "wagtailadmin/shared/field_as_li.html" with field=form.password li_classes="password-field" %} +
+
    + {% include "wagtailadmin/shared/field_as_li.html" with field=form.groups %} +
+ +
+ +
diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.js b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.js new file mode 100644 index 000000000..045c31b4d --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy.js @@ -0,0 +1,27 @@ +function(modal) { + $('form', modal.body).submit(function() { + modal.postForm(this.action, $(this).serialize()); + return false; + }); + + var restrictionTypePasswordField = $("input[name='restriction_type'][value='password']", modal.body); + var restrictionTypeGroupsField = $("input[name='restriction_type'][value='groups']", modal.body); + var passwordField = $(".password-field", modal.body); + var groupsFields = $('#groups-fields', modal.body); + + function refreshFormFields() { + if (restrictionTypePasswordField.is(':checked')) { + passwordField.show(); + groupsFields.hide(); + } else if (restrictionTypeGroupsField.is(':checked')){ + passwordField.hide(); + groupsFields.show(); + } else { + passwordField.hide(); + groupsFields.hide(); + } + } + refreshFormFields(); + + $("input[name='restriction_type']", modal.body).change(refreshFormFields); +} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy_done.js b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy_done.js new file mode 100644 index 000000000..dafbc7b36 --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collection_privacy/set_privacy_done.js @@ -0,0 +1,4 @@ +function(modal) { + modal.respond('setPermission', {% if is_public %}true{% else %}false{% endif %}); + modal.close(); +} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collections/_privacy_switch.html b/wagtail/wagtailadmin/templates/wagtailadmin/collections/_privacy_switch.html new file mode 100644 index 000000000..e5ad100ba --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collections/_privacy_switch.html @@ -0,0 +1,14 @@ +{% load i18n wagtailadmin_tags %} + +{% test_collection_is_public collection as is_public %} + +{% if not collection.is_root %} +
+ {% trans "Privacy" %} + + {# labels are shown/hidden in CSS according to the 'private' / 'public' class on view-permission-indicator #} + {% trans 'Public' %} + {% trans 'Private' %} + +
+{% endif %} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/collections/edit.html b/wagtail/wagtailadmin/templates/wagtailadmin/collections/edit.html new file mode 100644 index 000000000..ed2086681 --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/collections/edit.html @@ -0,0 +1,12 @@ +{% extends "wagtailadmin/generic/edit.html" %} +{% load static %} + +{% block before_form %} + {% include "wagtailadmin/collections/_privacy_switch.html" with collection=object collection_perms=collection_perms only %} +{% endblock %} + +{% block extra_js %} + {{ block.super }} + + +{% endblock %} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/generic/edit.html b/wagtail/wagtailadmin/templates/wagtailadmin/generic/edit.html index 3de5b4e51..1a64ee314 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/generic/edit.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/generic/edit.html @@ -8,6 +8,7 @@ {% include "wagtailadmin/shared/header.html" with title=view.page_title subtitle=view.get_page_subtitle icon=view.header_icon %}
+ {% block before_form %}{% endblock %}
{% csrf_token %} diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index 9ed378bde..cc0ef4582 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -17,7 +17,11 @@ from wagtail.wagtailadmin.menu import admin_menu from wagtail.wagtailadmin.navigation import get_explorable_root_page from wagtail.wagtailadmin.search import admin_search_areas from wagtail.wagtailcore import hooks -from wagtail.wagtailcore.models import Page, PageViewRestriction, UserPagePermissionsProxy +from wagtail.wagtailcore.models import ( + CollectionViewRestriction, + Page, PageViewRestriction, + UserPagePermissionsProxy +) from wagtail.wagtailcore.utils import cautious_slugify as _cautious_slugify from wagtail.wagtailcore.utils import camelcase_to_underscore, escape_script @@ -133,6 +137,25 @@ def page_permissions(context, page): return context['user_page_permissions'].for_page(page) +@assignment_tag(takes_context=True) +def test_collection_is_public(context, collection): + """ + Usage: {% test_collection_is_public collection as is_public %} + Sets 'is_public' to True iff there are no collection view restrictions in place + on this collection. + Caches the list of collection view restrictions in the context, to avoid repeated + DB queries on repeated calls. + """ + if 'all_collection_view_restrictions' not in context: + context['all_collection_view_restrictions'] = CollectionViewRestriction.objects.select_related('collection').values_list( + 'collection__name', flat=True + ) + + is_private = collection.name in context['all_collection_view_restrictions'] + + return not is_private + + @assignment_tag(takes_context=True) def test_page_is_public(context, page): """ diff --git a/wagtail/wagtailadmin/urls/collections.py b/wagtail/wagtailadmin/urls/collections.py index 34a4cfec3..f016eb487 100644 --- a/wagtail/wagtailadmin/urls/collections.py +++ b/wagtail/wagtailadmin/urls/collections.py @@ -2,11 +2,12 @@ from __future__ import absolute_import, unicode_literals from django.conf.urls import url -from wagtail.wagtailadmin.views import collections +from wagtail.wagtailadmin.views import collection_privacy, collections urlpatterns = [ url(r'^$', collections.Index.as_view(), name='index'), url(r'^add/$', collections.Create.as_view(), name='add'), url(r'^(\d+)/$', collections.Edit.as_view(), name='edit'), url(r'^(\d+)/delete/$', collections.Delete.as_view(), name='delete'), + url(r'^(\d+)/privacy/$', collection_privacy.set_privacy, name='set_privacy'), ] diff --git a/wagtail/wagtailadmin/views/collection_privacy.py b/wagtail/wagtailadmin/views/collection_privacy.py new file mode 100644 index 000000000..8e537ed2d --- /dev/null +++ b/wagtail/wagtailadmin/views/collection_privacy.py @@ -0,0 +1,72 @@ +from __future__ import absolute_import, unicode_literals + +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404 + +from wagtail.wagtailadmin.forms import CollectionViewRestrictionForm +from wagtail.wagtailadmin.modal_workflow import render_modal_workflow +from wagtail.wagtailcore.models import Collection, CollectionViewRestriction +from wagtail.wagtailcore.permissions import collection_permission_policy + + +def set_privacy(request, collection_id): + collection = get_object_or_404(Collection, id=collection_id) + if not collection_permission_policy.user_has_permission(request.user, 'change'): + raise PermissionDenied + + # fetch restriction records in depth order so that ancestors appear first + restrictions = collection.get_view_restrictions().order_by('collection__depth') + if restrictions: + restriction = restrictions[0] + restriction_exists_on_ancestor = (restriction.collection != collection) + else: + restriction = None + restriction_exists_on_ancestor = False + + if request.method == 'POST': + form = CollectionViewRestrictionForm(request.POST, instance=restriction) + if form.is_valid() and not restriction_exists_on_ancestor: + if form.cleaned_data['restriction_type'] == CollectionViewRestriction.NONE: + # remove any existing restriction + if restriction: + restriction.delete() + else: + restriction = form.save(commit=False) + restriction.collection = collection + form.save() + + return render_modal_workflow( + request, None, 'wagtailadmin/collection_privacy/set_privacy_done.js', { + 'is_public': (form.cleaned_data['restriction_type'] == 'none') + } + ) + + else: # request is a GET + if not restriction_exists_on_ancestor: + if restriction: + form = CollectionViewRestrictionForm(instance=restriction) + else: + # no current view restrictions on this collection + form = CollectionViewRestrictionForm(initial={ + 'restriction_type': 'none' + }) + + if restriction_exists_on_ancestor: + # display a message indicating that there is a restriction at ancestor level - + # do not provide the form for setting up new restrictions + return render_modal_workflow( + request, 'wagtailadmin/collection_privacy/ancestor_privacy.html', None, + { + 'collection_with_restriction': restriction.collection, + } + ) + else: + # no restriction set at ancestor level - can set restrictions here + return render_modal_workflow( + request, + 'wagtailadmin/collection_privacy/set_privacy.html', + 'wagtailadmin/collection_privacy/set_privacy.js', { + 'collection': collection, + 'form': form, + } + ) diff --git a/wagtail/wagtailadmin/views/collections.py b/wagtail/wagtailadmin/views/collections.py index 1c87d18e5..6c8aa6b69 100644 --- a/wagtail/wagtailadmin/views/collections.py +++ b/wagtail/wagtailadmin/views/collections.py @@ -49,6 +49,7 @@ class Edit(EditView): permission_policy = collection_permission_policy model = Collection form_class = CollectionForm + template_name = 'wagtailadmin/collections/edit.html' success_message = ugettext_lazy("Collection '{0}' updated.") error_message = ugettext_lazy("The collection could not be saved due to errors.") delete_item_label = ugettext_lazy("Delete collection")