From f0e1b7b6b2a8106424bc3559e5b663fc7e47a0e9 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 2 Jul 2014 11:41:47 +0100 Subject: [PATCH 001/155] Move/re-namespace existing users forms, templates, urls, views --- .../templates/wagtailadmin/pages/search_results.html | 2 +- .../templates/wagtailusers/{ => users}/create.html | 2 +- .../templates/wagtailusers/{ => users}/edit.html | 2 +- .../templates/wagtailusers/{ => users}/index.html | 6 +++--- .../templates/wagtailusers/{ => users}/list.html | 10 +++++----- .../templates/wagtailusers/{ => users}/results.html | 4 ++-- wagtail/wagtailusers/urls.py | 8 -------- wagtail/wagtailusers/urls/__init__.py | 0 wagtail/wagtailusers/urls/users.py | 8 ++++++++ wagtail/wagtailusers/views/users.py | 12 ++++++------ wagtail/wagtailusers/wagtail_hooks.py | 6 +++--- 11 files changed, 30 insertions(+), 30 deletions(-) rename wagtail/wagtailusers/templates/wagtailusers/{ => users}/create.html (96%) rename wagtail/wagtailusers/templates/wagtailusers/{ => users}/edit.html (96%) rename wagtail/wagtailusers/templates/wagtailusers/{ => users}/index.html (73%) rename wagtail/wagtailusers/templates/wagtailusers/{ => users}/list.html (68%) rename wagtail/wagtailusers/templates/wagtailusers/{ => users}/results.html (85%) delete mode 100644 wagtail/wagtailusers/urls.py create mode 100644 wagtail/wagtailusers/urls/__init__.py create mode 100644 wagtail/wagtailusers/urls/users.py diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/search_results.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/search_results.html index 932f7b082..bbb6f254d 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/search_results.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/search_results.html @@ -17,7 +17,7 @@ {% endif %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/create.html b/wagtail/wagtailusers/templates/wagtailusers/users/create.html similarity index 96% rename from wagtail/wagtailusers/templates/wagtailusers/create.html rename to wagtail/wagtailusers/templates/wagtailusers/users/create.html index 91cba6846..02e7ecec1 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/create.html +++ b/wagtail/wagtailusers/templates/wagtailusers/users/create.html @@ -13,7 +13,7 @@
  • {% trans "Roles" %}
  • -
    +
    {% csrf_token %}
    diff --git a/wagtail/wagtailusers/templates/wagtailusers/edit.html b/wagtail/wagtailusers/templates/wagtailusers/users/edit.html similarity index 96% rename from wagtail/wagtailusers/templates/wagtailusers/edit.html rename to wagtail/wagtailusers/templates/wagtailusers/users/edit.html index 1f0cc384f..22ae8b3a7 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/users/edit.html @@ -13,7 +13,7 @@
  • {% trans "Roles" %}
  • - +
    {% csrf_token %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/index.html b/wagtail/wagtailusers/templates/wagtailusers/users/index.html similarity index 73% rename from wagtail/wagtailusers/templates/wagtailusers/index.html rename to wagtail/wagtailusers/templates/wagtailusers/users/index.html index d2f3e9972..67589aaaf 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/index.html +++ b/wagtail/wagtailusers/templates/wagtailusers/users/index.html @@ -6,7 +6,7 @@ {% block extra_js %} +{% endblock %} + +{% block content %} + {% trans "groups" as groups_str %} + {% trans "Add a group" as add_a_group_str %} + {% include "wagtailadmin/shared/header.html" with title=groups_str add_link="wagtailusers_groups_create" add_text=add_a_group_str icon="group" search_url="wagtailusers_groups_index" %} + +
    +
    + {% include "wagtailusers/groups/results.html" %} +
    +
    +{% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/list.html b/wagtail/wagtailusers/templates/wagtailusers/groups/list.html new file mode 100644 index 000000000..33ccd001a --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/list.html @@ -0,0 +1,27 @@ +{% load i18n %} + + + + + + + + {% for group in groups %} + + + + {% endfor %} + +
    + {% trans "Name" %} + {% if ordering == "name" %} + + {% else %} + + {% endif %} +
    +

    + + {{ group.name }} +

    +
    diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/results.html b/wagtail/wagtailusers/templates/wagtailusers/groups/results.html new file mode 100644 index 000000000..ea3cc5650 --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/results.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if groups %} + + {% include "wagtailusers/groups/list.html" %} + + {% include "wagtailadmin/shared/pagination_nav.html" with items=groups is_searching=is_searching linkurl="wagtailusers_groups_index" %} +{% else %} + {% url 'wagtailusers_groups_create' as wagtailusers_create_group_url %} +

    {% blocktrans %}There are no groups configured. Why not add some?{% endblocktrans %}

    +{% endif %} diff --git a/wagtail/wagtailusers/urls/groups.py b/wagtail/wagtailusers/urls/groups.py new file mode 100644 index 000000000..4899eea12 --- /dev/null +++ b/wagtail/wagtailusers/urls/groups.py @@ -0,0 +1,8 @@ +from django.conf.urls import url +from wagtail.wagtailusers.views import groups + +urlpatterns = [ + url(r'^$', groups.index, name='wagtailusers_groups_index'), + url(r'^new/$', groups.create, name='wagtailusers_groups_create'), + url(r'^(\d+)/$', groups.edit, name='wagtailusers_groups_edit'), +] diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py new file mode 100644 index 000000000..15994f39e --- /dev/null +++ b/wagtail/wagtailusers/views/groups.py @@ -0,0 +1,114 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.contrib.auth.decorators import permission_required +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.contrib import messages +from django.db.models import Q +from django.utils.translation import ugettext as _ +from django.views.decorators.vary import vary_on_headers + +from wagtail.wagtailadmin.forms import SearchForm +from wagtail.wagtailusers.forms import GroupForm +from wagtail.wagtailcore.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME + +User = get_user_model() + +# Typically we would check the permission 'auth.change_user' for user +# management actions, but this may vary according to the AUTH_USER_MODEL +# setting +change_user_perm = "{0}.change_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()) + + +@permission_required(change_user_perm) +@vary_on_headers('X-Requested-With') +def index(request): + q = None + p = request.GET.get("p", 1) + is_searching = False + + if 'q' in request.GET: + form = SearchForm(request.GET, placeholder=_("Search groups")) + if form.is_valid(): + q = form.cleaned_data['q'] + + is_searching = True + groups = Group.objects.filter(name__icontains=q) + else: + form = SearchForm(placeholder=_("Search groups")) + + if not is_searching: + groups = Group.objects + + groups = groups.order_by('name') + + if 'ordering' in request.GET: + ordering = request.GET['ordering'] + + if ordering in ['name', 'username']: + if ordering != 'name': + users = users.order_by(ordering) + else: + ordering = 'name' + + paginator = Paginator(groups, 20) + + try: + groups = paginator.page(p) + except PageNotAnInteger: + groups = paginator.page(1) + except EmptyPage: + groups = paginator.page(paginator.num_pages) + + if request.is_ajax(): + return render(request, "wagtailusers/groups/results.html", { + 'groups': groups, + 'is_searching': is_searching, + 'query_string': q, + 'ordering': ordering, + }) + else: + return render(request, "wagtailusers/groups/index.html", { + 'search_form': form, + 'groups': groups, + 'is_searching': is_searching, + 'ordering': ordering, + 'query_string': q, + }) + +@permission_required(change_user_perm) +def create(request): + if request.POST: + form = GroupForm(request.POST) + if form.is_valid(): + group = form.save() + messages.success(request, _("Group '{0}' created.").format(group)) + return redirect('wagtailusers_groups_index') + else: + messages.error(request, _("The group could not be created due to errors.") ) + else: + form = GroupForm() + + return render(request, 'wagtailusers/groups/create.html', { + 'form': form, + }) + + +@permission_required(change_user_perm) +def edit(request, group_id): + group = get_object_or_404(Group, id=group_id) + if request.POST: + form = GroupForm(request.POST, instance=group) + if form.is_valid(): + group = form.save() + messages.success(request, _("Group '{0}' updated.").format(group)) + return redirect('wagtailusers_groups_index') + else: + messages.error(request, _("The group could not be saved due to errors.")) + else: + form = GroupForm(instance=group) + + return render(request, 'wagtailusers/groups/edit.html', { + 'group': group, + 'form': form, + }) diff --git a/wagtail/wagtailusers/wagtail_hooks.py b/wagtail/wagtailusers/wagtail_hooks.py index 6c03614b8..5a8195fa0 100644 --- a/wagtail/wagtailusers/wagtail_hooks.py +++ b/wagtail/wagtailusers/wagtail_hooks.py @@ -5,12 +5,13 @@ from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem -from wagtail.wagtailusers.urls import users +from wagtail.wagtailusers.urls import users, groups def register_admin_urls(): return [ url(r'^users/', include(users)), + url(r'^groups/', include(groups)), ] hooks.register('register_admin_urls', register_admin_urls) @@ -20,4 +21,7 @@ def construct_main_menu(request, menu_items): menu_items.append( MenuItem(_('Users'), urlresolvers.reverse('wagtailusers_users_index'), classnames='icon icon-user', order=600) ) + menu_items.append( + MenuItem(_('Groups'), urlresolvers.reverse('wagtailusers_groups_index'), classnames='icon icon-group', order=601) + ) hooks.register('construct_main_menu', construct_main_menu) From 7aac5d9a1267ed8da9f10410502473f0fe0b14c3 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 2 Jul 2014 12:29:22 +0100 Subject: [PATCH 004/155] Add tests for wagtail admin group views --- wagtail/wagtailusers/tests.py | 100 +++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index 6bb364ba4..ef52af3ff 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -1,6 +1,6 @@ from django.test import TestCase from django.core.urlresolvers import reverse -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailusers.models import UserProfile @@ -74,7 +74,7 @@ class TestUserEditView(TestCase, WagtailTestUtils): def get(self, params={}, user_id=None): return self.client.get(reverse('wagtailusers_users_edit', args=(user_id or self.test_user.id, )), params) - + def post(self, post_data={}, user_id=None): return self.client.post(reverse('wagtailusers_users_edit', args=(user_id or self.test_user.id, )), post_data) @@ -132,3 +132,99 @@ class TestUserProfileCreation(TestCase, WagtailTestUtils): self.assertIsInstance(UserProfile.get_for_user(self.test_user), UserProfile) # and get it from the db too self.assertEqual(UserProfile.objects.filter(user=self.test_user).count(), 1) + + +class TestGroupIndexView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + + def get(self, params={}): + return self.client.get(reverse('wagtailusers_groups_index'), params) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailusers/groups/index.html') + + def test_search(self): + response = self.get({'q': "Hello"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['query_string'], "Hello") + + def test_pagination(self): + pages = ['0', '1', '-1', '9999', 'Not a page'] + for page in pages: + response = self.get({'p': page}) + self.assertEqual(response.status_code, 200) + + +class TestGroupCreateView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + + def get(self, params={}): + return self.client.get(reverse('wagtailusers_groups_create'), params) + + def post(self, post_data={}): + return self.client.post(reverse('wagtailusers_groups_create'), post_data) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailusers/groups/create.html') + + def test_create(self): + response = self.post({ + 'name': "test group", + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + + # Check that the user was created + groups = Group.objects.filter(name='test group') + self.assertEqual(groups.count(), 1) + + +class TestGroupEditView(TestCase, WagtailTestUtils): + def setUp(self): + # Create a group to edit + self.test_group = Group.objects.create(name='test group') + + # Login + self.login() + + def get(self, params={}, group_id=None): + return self.client.get(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), params) + + def post(self, post_data={}, group_id=None): + return self.client.post(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), post_data) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailusers/groups/edit.html') + + def test_nonexistant_redirect(self): + self.assertEqual(self.get(group_id=100000).status_code, 404) + + def test_edit(self): + response = self.post({ + 'name': "test group edited", + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + + # Check that the group was edited + group = Group.objects.get(id=self.test_group.id) + self.assertEqual(group.name, 'test group edited') + + def test_edit_validation_error(self): + # Leave "name" field blank. This should give a validation error + response = self.post({ + 'name': "", + }) + + # Should not redirect to index + self.assertEqual(response.status_code, 200) From b7e82e1a5ab3498c5b7b166124e8e2700a861393 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 2 Jul 2014 12:32:49 +0100 Subject: [PATCH 005/155] Add css for groups menu item --- wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index 55e27b067..bd51a7595 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -193,6 +193,7 @@ img{ .menu-snippets &.menu-snippets, .menu-users &.menu-users, + .menu-groups &.menu-groups, .menu-snippets &.menu-snippets, .menu-documents &.menu-documents, .menu-images &.menu-images, @@ -792,4 +793,4 @@ footer, } .nav-main a, a{ @include transition(color 0.2s ease, background-color 0.2s ease); -} \ No newline at end of file +} From 6749f688b64ba9a7ccd88213a23d36403c3eae9e Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 2 Jul 2014 14:20:30 +0100 Subject: [PATCH 006/155] PEP-8 --- wagtail/wagtailusers/forms.py | 2 +- wagtail/wagtailusers/models.py | 18 +++++++++--------- wagtail/wagtailusers/views/groups.py | 4 ++-- wagtail/wagtailusers/views/users.py | 1 + 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index efb0ca016..81b352606 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -38,7 +38,7 @@ class UserCreationForm(BaseUserCreationForm): username = self.cleaned_data["username"] try: # When called from BaseUserCreationForm, the method fails if using a AUTH_MODEL_MODEL, - # This is because the following line tries to perform a lookup on + # This is because the following line tries to perform a lookup on # the default "auth_user" table. User._default_manager.get(username=username) except User.DoesNotExist: diff --git a/wagtail/wagtailusers/models.py b/wagtail/wagtailusers/models.py index eb0aa0c9e..5e63e6af3 100644 --- a/wagtail/wagtailusers/models.py +++ b/wagtail/wagtailusers/models.py @@ -9,19 +9,19 @@ class UserProfile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL) submitted_notifications = models.BooleanField( - default=True, - help_text=_("Receive notification when a page is submitted for moderation") - ) + default=True, + help_text=_("Receive notification when a page is submitted for moderation") + ) approved_notifications = models.BooleanField( - default=True, - help_text=_("Receive notification when your page edit is approved") - ) + default=True, + help_text=_("Receive notification when your page edit is approved") + ) rejected_notifications = models.BooleanField( - default=True, - help_text=_("Receive notification when your page edit is rejected") - ) + default=True, + help_text=_("Receive notification when your page edit is rejected") + ) @classmethod def get_for_user(cls, user): diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 15994f39e..07692adcf 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -4,7 +4,6 @@ from django.contrib.auth.models import Group from django.contrib.auth.decorators import permission_required from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages -from django.db.models import Q from django.utils.translation import ugettext as _ from django.views.decorators.vary import vary_on_headers @@ -47,7 +46,7 @@ def index(request): if ordering in ['name', 'username']: if ordering != 'name': - users = users.order_by(ordering) + groups = groups.order_by(ordering) else: ordering = 'name' @@ -76,6 +75,7 @@ def index(request): 'query_string': q, }) + @permission_required(change_user_perm) def create(request): if request.POST: diff --git a/wagtail/wagtailusers/views/users.py b/wagtail/wagtailusers/views/users.py index 5c89862e7..427106789 100644 --- a/wagtail/wagtailusers/views/users.py +++ b/wagtail/wagtailusers/views/users.py @@ -75,6 +75,7 @@ def index(request): 'query_string': q, }) + @permission_required(change_user_perm) def create(request): if request.POST: From ff7554c9f118c81a17c0540f0659d84255cbe8ff Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 2 Jul 2014 17:36:05 +0100 Subject: [PATCH 007/155] Customise permissions widget --- .../wagtailusers/formatted_permissions.html | 37 ++++++++++++++ .../templates/wagtailusers/groups/edit.html | 3 +- wagtail/wagtailusers/templatetags/__init__.py | 0 .../templatetags/wagtailusers_tags.py | 49 +++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html create mode 100644 wagtail/wagtailusers/templatetags/__init__.py create mode 100644 wagtail/wagtailusers/templatetags/wagtailusers_tags.py diff --git a/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html b/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html new file mode 100644 index 000000000..21855f282 --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html @@ -0,0 +1,37 @@ + + + + + + + + + {% for content_perms_dict in perms_array %} + + + + + + + + {% endfor %} +
    ObjectAddChangeDeleteOthers
    {{ content_perms_dict.object|capfirst }} + {% with content_perms_dict.add as perm_tuple %} + + {% endwith %} + + {% with content_perms_dict.change as perm_tuple %} + + {% endwith %} + + {% with content_perms_dict.delete as perm_tuple %} + + {% endwith %} + + {% for perm_tuple in content_perms_dict.others %} + + {% if not forloop.last %}
    {% endif %} + {% endfor %} +
    diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html index 541a803f5..a98076296 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html @@ -1,5 +1,6 @@ {% extends "wagtailadmin/base.html" %} {% load image_tags %} +{% load wagtailusers_tags %} {% load i18n %} {% block titletag %}{% trans "Editing" %} {{ group.name }}{% endblock %} {% block bodyclass %}menu-groups{% endblock %} @@ -13,7 +14,7 @@
      {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} - {% include "wagtailadmin/shared/field_as_li.html" with field=form.permissions %} + {% format_permissions permission_bound_field=form.permissions %}
    diff --git a/wagtail/wagtailusers/templatetags/__init__.py b/wagtail/wagtailusers/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py new file mode 100644 index 000000000..70245718a --- /dev/null +++ b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py @@ -0,0 +1,49 @@ +from django import template + +register = template.Library() + +@register.inclusion_tag('wagtailusers/formatted_permissions.html') +def format_permissions(permission_bound_field): + """ + Given a bound field with a queryset of Permission objects, constructs a + list of dictionaries: + + [ + { + 'object': name_of_some_content_object, + 'add': (add_permission_for_object, checked_str) + 'change': (change_permission_for_object, checked_str) + 'delete': (delete_permission_for_object, checked_str) + 'others': [ + (any_other_permission_for_object, checked_str), + ] + }, + ] + + and returns a table template formatted with this list. + + """ + permissions = permission_bound_field.field._queryset + # get a distinct list of the content types that these permissions relate to + content_type_ids = set(permissions.values_list('content_type_id', flat=True)) + initial = permission_bound_field.form.initial['permissions'] + + perms_array = [] + for content_type_id in content_type_ids: + content_perms = permissions.filter(content_type_id=content_type_id) + content_perms_dict = { + 'others': [] + } + for perm in content_perms: + content_perms_dict['object'] = perm.content_type.name + checked = 'checked="checked"' if perm.id in initial else '' + # identify the three main categories of permission, or bung in + # 'others' list + if perm.codename.split('_')[0] in ['add', 'change', 'delete']: + content_perms_dict[perm.codename.split('_')[0]] = (perm, checked) + else: + content_perms_dict['others'].append((perm, checked)) + perms_array.append(content_perms_dict) + return { + 'perms_array': perms_array, + } From 1ea1f9b020a62c8aa857328bdd0f88778ec5fa62 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 4 Jul 2014 10:59:34 +0100 Subject: [PATCH 008/155] Limit GroupForm permissions queryset to hook-registered permissions --- wagtail/wagtailusers/forms.py | 24 +++++++++++++++++++++++- wagtail/wagtailusers/wagtail_hooks.py | 10 ++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 81b352606..247d8ab62 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -2,8 +2,9 @@ from django import forms from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, Permission +from wagtail.wagtailadmin import hooks from wagtail.wagtailusers.models import UserProfile from wagtail.wagtailcore.models import UserPagePermissionsProxy @@ -138,6 +139,13 @@ class UserEditForm(forms.ModelForm): class GroupForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super(GroupForm, self).__init__(*args, **kwargs) + self.registered_permissions = Permission.objects.none() + for fn in hooks.get_hooks('register_permissions'): + self.registered_permissions = self.registered_permissions | fn() + self.fields['permissions'].queryset = self.registered_permissions + required_css_class = "required" error_messages = { @@ -164,6 +172,20 @@ class GroupForm(forms.ModelForm): return name raise forms.ValidationError(self.error_messages['duplicate_name']) + def save(self): + # We go back to the object to read (in order to reapply) the + # permissions which were set on this group, but which are not + # accessible in the wagtail admin interface, as otherwise these would + # be clobbered by this form. + try: + untouchable_permissions = self.instance.permissions.exclude(pk__in=self.registered_permissions) + except AttributeError: + # this form is not bound; we're probably creating a new group + untouchable_permissions = Permission.objects.none() + group = super(GroupForm, self).save() + group.permissions.add(*untouchable_permissions) + return group + class NotificationPreferencesForm(forms.ModelForm): def __init__(self, *args, **kwargs): diff --git a/wagtail/wagtailusers/wagtail_hooks.py b/wagtail/wagtailusers/wagtail_hooks.py index 5a8195fa0..874de8b5b 100644 --- a/wagtail/wagtailusers/wagtail_hooks.py +++ b/wagtail/wagtailusers/wagtail_hooks.py @@ -1,5 +1,7 @@ from django.conf.urls import include, url from django.core import urlresolvers +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailcore import hooks @@ -25,3 +27,11 @@ def construct_main_menu(request, menu_items): MenuItem(_('Groups'), urlresolvers.reverse('wagtailusers_groups_index'), classnames='icon icon-group', order=601) ) hooks.register('construct_main_menu', construct_main_menu) + + +def register_permissions(): + user_profile_content_types = ContentType.objects.filter(app_label='wagtailusers', model='userprofile') + auth_content_types = ContentType.objects.filter(app_label='auth', model__in=['group', 'user']) + relevant_content_types = user_profile_content_types | auth_content_types + return Permission.objects.filter(content_type__in=relevant_content_types) +hooks.register('register_permissions', register_permissions) From c6742cd69487dbe6660c09c7c959eebc340db4d7 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 4 Jul 2014 11:06:55 +0100 Subject: [PATCH 009/155] Register permissions for images, docs, and registered snippets --- wagtail/wagtaildocs/wagtail_hooks.py | 9 +++++++++ wagtail/wagtailimages/wagtail_hooks.py | 9 +++++++++ wagtail/wagtailsnippets/wagtail_hooks.py | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/wagtail/wagtaildocs/wagtail_hooks.py b/wagtail/wagtaildocs/wagtail_hooks.py index a6e00f8db..10622fbce 100644 --- a/wagtail/wagtaildocs/wagtail_hooks.py +++ b/wagtail/wagtaildocs/wagtail_hooks.py @@ -3,6 +3,8 @@ from django.conf.urls import include, url from django.core import urlresolvers from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import Permission from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem @@ -43,3 +45,10 @@ def editor_js(): urlresolvers.reverse('wagtaildocs_chooser') ) hooks.register('insert_editor_js', editor_js) + + +def register_permissions(): + document_content_type = ContentType.objects.get(app_label='wagtaildocs', model='document') + document_permissions = Permission.objects.filter(content_type = document_content_type) + return document_permissions +hooks.register('register_permissions', register_permissions) diff --git a/wagtail/wagtailimages/wagtail_hooks.py b/wagtail/wagtailimages/wagtail_hooks.py index 611e18990..912b6bbf4 100644 --- a/wagtail/wagtailimages/wagtail_hooks.py +++ b/wagtail/wagtailimages/wagtail_hooks.py @@ -3,6 +3,8 @@ from django.conf.urls import include, url from django.core import urlresolvers from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem @@ -43,3 +45,10 @@ def editor_js(): urlresolvers.reverse('wagtailimages_chooser') ) hooks.register('insert_editor_js', editor_js) + + +def register_permissions(): + image_content_type = ContentType.objects.get(app_label='wagtailimages', model='image') + image_permissions = Permission.objects.filter(content_type = image_content_type) + return image_permissions +hooks.register('register_permissions', register_permissions) diff --git a/wagtail/wagtailsnippets/wagtail_hooks.py b/wagtail/wagtailsnippets/wagtail_hooks.py index 501f29b31..36ebdaca4 100644 --- a/wagtail/wagtailsnippets/wagtail_hooks.py +++ b/wagtail/wagtailsnippets/wagtail_hooks.py @@ -3,12 +3,14 @@ from django.conf.urls import include, url from django.core import urlresolvers from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import Permission from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailsnippets import urls from wagtail.wagtailsnippets.permissions import user_can_edit_snippets +from wagtail.wagtailsnippets.models import get_snippet_content_types def register_admin_urls(): @@ -36,3 +38,10 @@ def editor_js(): urlresolvers.reverse('wagtailsnippets_choose_generic') ) hooks.register('insert_editor_js', editor_js) + + +def register_permissions(): + snippet_content_types = get_snippet_content_types() + snippet_permissions = Permission.objects.filter(content_type__in=snippet_content_types) + return snippet_permissions +hooks.register('register_permissions', register_permissions) From afe5283bef37dd55b866615eebb699e983c7e05a Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 4 Jul 2014 11:35:08 +0100 Subject: [PATCH 010/155] Register 'access wagtail admin' permission --- wagtail/wagtailadmin/wagtail_hooks.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 wagtail/wagtailadmin/wagtail_hooks.py diff --git a/wagtail/wagtailadmin/wagtail_hooks.py b/wagtail/wagtailadmin/wagtail_hooks.py new file mode 100644 index 000000000..d1171a2db --- /dev/null +++ b/wagtail/wagtailadmin/wagtail_hooks.py @@ -0,0 +1,7 @@ +from django.contrib.auth.models import Permission +from wagtail.wagtailadmin import hooks + + +def register_permissions(): + return Permission.objects.filter(content_type__app_label='wagtailadmin', codename='access_admin') +hooks.register('register_permissions', register_permissions) From 951f8ade47dc9fd0b696d4c57bc1454b998cd70f Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 4 Jul 2014 12:04:49 +0100 Subject: [PATCH 011/155] Split permissions table display into object permissions and other permissions --- .../wagtailusers/formatted_permissions.html | 17 ++++++--- .../templatetags/wagtailusers_tags.py | 38 +++++++++++-------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html b/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html index 21855f282..8122b0767 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html +++ b/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html @@ -1,12 +1,12 @@ +

    Object permissions

    - - {% for content_perms_dict in perms_array %} + {% for content_perms_dict in object_perms %} + + {% endfor %} +
    Object Add Change DeleteOthers
    {{ content_perms_dict.object|capfirst }} @@ -24,13 +24,18 @@ {% endwith %}
    +

    Other permissions

    + + {% for perm_tuple in other_perms %} + + {% endfor %} diff --git a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py index 70245718a..074700460 100644 --- a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py +++ b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py @@ -2,24 +2,28 @@ from django import template register = template.Library() + @register.inclusion_tag('wagtailusers/formatted_permissions.html') def format_permissions(permission_bound_field): """ Given a bound field with a queryset of Permission objects, constructs a - list of dictionaries: + list of dictionaries for 'objects': - [ + 'objects': [ { 'object': name_of_some_content_object, 'add': (add_permission_for_object, checked_str) 'change': (change_permission_for_object, checked_str) 'delete': (delete_permission_for_object, checked_str) - 'others': [ - (any_other_permission_for_object, checked_str), - ] }, ] + and a list of other permissions: + + 'others': [ + (any_non_add_change_delete_permission, checked_str), + ] + and returns a table template formatted with this list. """ @@ -28,22 +32,24 @@ def format_permissions(permission_bound_field): content_type_ids = set(permissions.values_list('content_type_id', flat=True)) initial = permission_bound_field.form.initial['permissions'] - perms_array = [] + object_perms = [] + other_perms = [] + for content_type_id in content_type_ids: content_perms = permissions.filter(content_type_id=content_type_id) - content_perms_dict = { - 'others': [] - } + content_perms_dict = {} for perm in content_perms: - content_perms_dict['object'] = perm.content_type.name checked = 'checked="checked"' if perm.id in initial else '' - # identify the three main categories of permission, or bung in - # 'others' list + # identify the three main categories of permission, and assign to + # the relevant dict key, else bung in the 'other_perms' list if perm.codename.split('_')[0] in ['add', 'change', 'delete']: + content_perms_dict['object'] = perm.content_type.name content_perms_dict[perm.codename.split('_')[0]] = (perm, checked) else: - content_perms_dict['others'].append((perm, checked)) - perms_array.append(content_perms_dict) + other_perms.append((perm, checked)) + if content_perms_dict: + object_perms.append(content_perms_dict) return { - 'perms_array': perms_array, - } + 'object_perms': object_perms, + 'other_perms': other_perms, + } From 7fcfff65bf825a1be6203fa2627e057d6cfd524a Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 14:05:34 +0100 Subject: [PATCH 012/155] De-hardcode the button text in page_chooser_panel --- .../wagtailadmin/edit_handlers/page_chooser_panel.html | 9 ++------- .../editorspicks/includes/editorspicks_form.html | 6 ++++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/page_chooser_panel.html b/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/page_chooser_panel.html index 68aa624bb..fbca951ca 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/page_chooser_panel.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/page_chooser_panel.html @@ -1,13 +1,8 @@ {% extends "wagtailadmin/edit_handlers/chooser_panel.html" %} -{% load i18n %} -{% comment %} - TODO: make it possible to specify button labels that are better tuned to the - particular use case: e.g. "Choose an author", "Choose a location" -{% endcomment %} {% block chosen_state_view %} {{ page.title }} {% endblock %} -{% block choose_another_button_label %}{% trans "Choose another page" %}{% endblock %} -{% block choose_button_label %}{% trans "Choose a page" %}{% endblock %} +{% block choose_another_button_label %}{{ choose_another_text_str }}{% endblock %} +{% block choose_button_label %}{{ choose_one_text_str }}{% endblock %} diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html index 9a45dbef0..055cdd4b0 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html @@ -10,10 +10,12 @@ {% trans "Editors pick" %}
    • + {% trans "Choose another page" as choose_another_text_str %} + {% trans "Choose a page" as choose_one_text_str %} {% if form.instance.page %} - {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True only %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} {% else %} - {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False only %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} {% endif %}
    • From 2e3d0fd03c51ff33808fe8b96fa58d1764a3079d Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 14:38:11 +0100 Subject: [PATCH 013/155] Add page permissions formset to group edit view --- wagtail/wagtailusers/forms.py | 14 +++++++++++++- .../templates/wagtailusers/groups/edit.html | 1 + wagtail/wagtailusers/views/groups.py | 16 ++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 247d8ab62..928e3b40b 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import Group, Permission from wagtail.wagtailadmin import hooks from wagtail.wagtailusers.models import UserProfile -from wagtail.wagtailcore.models import UserPagePermissionsProxy +from wagtail.wagtailcore.models import UserPagePermissionsProxy, GroupPagePermission User = get_user_model() @@ -187,6 +187,18 @@ class GroupForm(forms.ModelForm): return group +class GroupPagePermissionForm(forms.ModelForm): + class Meta: + model = GroupPagePermission + fields = ('page', 'permission_type') + + +class BaseGroupPagePermissionFormSet(forms.models.BaseInlineFormSet): + def __init__(self, *args, **kwargs): + super(BaseGroupPagePermissionFormSet, self).__init__(*args, **kwargs) + self.form = GroupPagePermissionForm + + class NotificationPreferencesForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(NotificationPreferencesForm, self).__init__(*args, **kwargs) diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html index a98076296..d079f5e79 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html @@ -15,6 +15,7 @@
        {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} {% format_permissions permission_bound_field=form.permissions %} + {{ formset }}
      diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 07692adcf..8867aaed4 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -6,9 +6,11 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages from django.utils.translation import ugettext as _ from django.views.decorators.vary import vary_on_headers +from django.forms.models import inlineformset_factory from wagtail.wagtailadmin.forms import SearchForm -from wagtail.wagtailusers.forms import GroupForm +from wagtail.wagtailusers.forms import GroupForm, BaseGroupPagePermissionFormSet +from wagtail.wagtailcore.models import GroupPagePermission from wagtail.wagtailcore.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME User = get_user_model() @@ -97,18 +99,28 @@ def create(request): @permission_required(change_user_perm) def edit(request, group_id): group = get_object_or_404(Group, id=group_id) + GroupPagePermissionFormSet = inlineformset_factory( + Group, + GroupPagePermission, + formset=BaseGroupPagePermissionFormSet, + extra=0 + ) if request.POST: form = GroupForm(request.POST, instance=group) - if form.is_valid(): + formset = GroupPagePermissionFormSet(request.POST, instance=group) + if form.is_valid() and formset.is_valid(): group = form.save() + formset.save() messages.success(request, _("Group '{0}' updated.").format(group)) return redirect('wagtailusers_groups_index') else: messages.error(request, _("The group could not be saved due to errors.")) else: form = GroupForm(instance=group) + formset = GroupPagePermissionFormSet(instance=group) return render(request, 'wagtailusers/groups/edit.html', { 'group': group, 'form': form, + 'formset': formset, }) From ab6987194f0e4190e0ade35f568c992be0f1f11e Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 14:43:39 +0100 Subject: [PATCH 014/155] Add inline chooser panel templates for group page permissions formset --- wagtail/wagtailusers/forms.py | 12 +++++++ .../templates/wagtailusers/groups/edit.html | 9 +++++- .../includes/page_permissions_form.html | 31 +++++++++++++++++++ .../includes/page_permissions_formset.html | 16 ++++++++++ .../includes/page_permissions_formset.js | 19 ++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html create mode 100644 wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html create mode 100644 wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 928e3b40b..cdcb561c5 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -188,6 +188,10 @@ class GroupForm(forms.ModelForm): class GroupPagePermissionForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super(GroupPagePermissionForm, self).__init__(*args, **kwargs) + self.fields['page'].widget = forms.HiddenInput() + class Meta: model = GroupPagePermission fields = ('page', 'permission_type') @@ -197,6 +201,14 @@ class BaseGroupPagePermissionFormSet(forms.models.BaseInlineFormSet): def __init__(self, *args, **kwargs): super(BaseGroupPagePermissionFormSet, self).__init__(*args, **kwargs) self.form = GroupPagePermissionForm + for form in self.forms: + form.fields['DELETE'].widget = forms.HiddenInput() + + @property + def empty_form(self): + empty_form = super(BaseGroupPagePermissionFormSet, self).empty_form + empty_form.fields['DELETE'].widget = forms.HiddenInput() + return empty_form class NotificationPreferencesForm(forms.ModelForm): diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html index d079f5e79..54b4b1d9b 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html @@ -15,9 +15,16 @@
        {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} {% format_permissions permission_bound_field=form.permissions %} - {{ formset }} + {% include "wagtailusers/groups/includes/page_permissions_formset.html" with formset=formset only %}
      {% endblock %} +{% block extra_js %} + {% include "wagtailadmin/pages/_editor_js.html" %} + + +{% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html new file mode 100644 index 000000000..bee5196a6 --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html @@ -0,0 +1,31 @@ +{% load i18n %} +{% comment %} + TODO - DRY this form with /wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html +{% endcomment %} +
    • diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html new file mode 100644 index 000000000..03cb2a19e --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html @@ -0,0 +1,16 @@ +{% load i18n %} +{{ formset.management_form }} +
        + {% for form in formset.forms %} + {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=form only %} + {% endfor %} +
      + + + +

      + {% trans "Add another page permission" %} +

      + diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js new file mode 100644 index 000000000..c9c93a48b --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js @@ -0,0 +1,19 @@ +(function() { + function fixPrefix(str) {return str;} + + var panel = InlinePanel({ + formsetPrefix: fixPrefix("id_{{ formset.prefix }}"), + emptyChildFormPrefix: fixPrefix("{{ formset.empty_form.prefix }}"), + + onAdd: function(fixPrefix) { + createPageChooser(fixPrefix('id_{{ formset.prefix }}-__prefix__-page'), 'wagtailcore.page', null); + } + }); + + {% for form in formset.forms %} + createPageChooser(fixPrefix('id_{{ formset.prefix }}-{{ forloop.counter0 }}-page'), 'wagtailcore.page', null); + panel.initChildControls('{{ formset.prefix }}-{{ forloop.counter0 }}'); + {% endfor %} + + panel.updateMoveButtonDisabledStates(); +})(); From e35f23e0f6ef47bdaca6b743f371bf12adb762a6 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 14:46:07 +0100 Subject: [PATCH 015/155] Add uniqueness constraint on GroupPagePermission, and error tags in template --- ...ess_constraint_on_group_page_permission.py | 110 ++++++++++++++++++ wagtail/wagtailcore/models.py | 3 + .../includes/page_permissions_form.html | 7 ++ 3 files changed, 120 insertions(+) create mode 100644 wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py diff --git a/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py b/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py new file mode 100644 index 000000000..93b867f0b --- /dev/null +++ b/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type'] + db.create_unique(u'wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) + + + def backwards(self, orm): + # Removing unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type'] + db.delete_unique(u'wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wagtailcore.grouppagepermission': { + 'Meta': {'unique_together': "(('group', 'page', 'permission_type'),)", 'object_name': 'GroupPagePermission'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': u"orm['wagtailcore.Page']"}), + 'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + u'wagtailcore.page': { + 'Meta': {'object_name': 'Page'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': u"orm['contenttypes.ContentType']"}), + 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'wagtailcore.pagerevision': { + 'Meta': {'object_name': 'PageRevision'}, + 'approved_go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'content_json': ('django.db.models.fields.TextField', [], {}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': u"orm['wagtailcore.Page']"}), + 'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'wagtailcore.pageviewrestriction': { + 'Meta': {'object_name': 'PageViewRestriction'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'view_restrictions'", 'to': u"orm['wagtailcore.Page']"}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'wagtailcore.site': { + 'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'}, + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}), + 'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': u"orm['wagtailcore.Page']"}) + } + } + + complete_apps = ['wagtailcore'] \ No newline at end of file diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 11b6fe7ec..fc2f88f75 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -955,6 +955,9 @@ class GroupPagePermission(models.Model): page = models.ForeignKey('Page', related_name='group_permissions') permission_type = models.CharField(max_length=20, choices=PAGE_PERMISSION_TYPE_CHOICES) + class Meta: + unique_together = ('group', 'page', 'permission_type') + class UserPagePermissionsProxy(object): """Helper object that encapsulates all the page permission rules that this user has diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html index bee5196a6..d43b1cdef 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html @@ -27,5 +27,12 @@ {{ form.id }} {{ form.ORDER }} {{ form.DELETE }} + {% if form.non_field_errors %} +

      + {% for error in form.non_field_errors %} + {{ error|escape }} + {% endfor %} +

      + {% endif %} From bc9ed8086de61db9c25fd9862dafc4499fb1dbf6 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 14:59:17 +0100 Subject: [PATCH 016/155] PEP-8 --- wagtail/wagtailcore/models.py | 22 +++++++++++----------- wagtail/wagtailusers/views/groups.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index fc2f88f75..8b337f180 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -68,14 +68,14 @@ class Site(models.Model): still be routed to a different hostname which is set as the default """ try: - hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause + hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause try: # find a Site matching this specific hostname - return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause + return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause except Site.MultipleObjectsReturned: # as there were more than one, try matching by port too - port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause - return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause + port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause + return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause except (Site.DoesNotExist, KeyError): # If no matching site exists, or request does not specify an HTTP_HOST (which # will often be the case for the Django test client), look for a catch-all Site. @@ -105,9 +105,9 @@ class Site(models.Model): raise ValidationError( {'is_default_site': [ _("%(hostname)s is already configured as the default site. You must unset that before you can save this site as default.") - % { 'hostname': default.hostname } - ]} - ) + % {'hostname': default.hostname} + ]} + ) # clear the wagtail_site_root_paths cache whenever Site records are updated def save(self, *args, **kwargs): @@ -158,6 +158,7 @@ def get_leaf_page_content_type_ids(): if not getattr(content_type.model_class(), 'subpage_types', None) ] + def get_navigable_page_content_type_ids(): warnings.warn(""" get_navigable_page_content_type_ids is deprecated, as it treats pages without an explicit subpage_types @@ -371,8 +372,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, indexed.Index SET url_path = %s || substring(url_path from %s) WHERE path LIKE %s AND id <> %s """ - cursor.execute(update_statement, - [new_url_path, len(old_url_path) + 1, self.path + '%', self.id]) + cursor.execute(update_statement, [new_url_path, len(old_url_path) + 1, self.path + '%', self.id]) @cached_property def specific(self): @@ -829,7 +829,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, indexed.Index def get_navigation_menu_items(): # Get all pages that appear in the navigation menu: ones which have children, # or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu) - pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path') + pages = Page.objects.filter(Q(depth=2) | Q(numchild__gt=0)).order_by('path') # Turn this into a tree structure: # tree_node = (page, children) @@ -1049,7 +1049,7 @@ class PagePermissionTester(object): self.user = user_perms.user self.user_perms = user_perms self.page = page - self.page_is_root = page.depth == 1 # Equivalent to page.is_root() + self.page_is_root = page.depth == 1 # Equivalent to page.is_root() if self.user.is_active and not self.user.is_superuser: self.permissions = set( diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 8867aaed4..1c6347239 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -87,7 +87,7 @@ def create(request): messages.success(request, _("Group '{0}' created.").format(group)) return redirect('wagtailusers_groups_index') else: - messages.error(request, _("The group could not be created due to errors.") ) + messages.error(request, _("The group could not be created due to errors.")) else: form = GroupForm() From c92b78fdfa843661e63a783c9d4ef61c564ec419 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 17:03:06 +0100 Subject: [PATCH 017/155] Extend inline chooser panel templates to group create views --- wagtail/wagtailusers/forms.py | 2 +- .../templates/wagtailusers/groups/create.html | 11 ++++++++++- .../wagtailusers/templatetags/wagtailusers_tags.py | 2 +- wagtail/wagtailusers/views/groups.py | 13 ++++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index cdcb561c5..06ae6bbbf 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -179,7 +179,7 @@ class GroupForm(forms.ModelForm): # be clobbered by this form. try: untouchable_permissions = self.instance.permissions.exclude(pk__in=self.registered_permissions) - except AttributeError: + except ValueError: # this form is not bound; we're probably creating a new group untouchable_permissions = Permission.objects.none() group = super(GroupForm, self).save() diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html index dd9f34806..5ea5fd4bd 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html @@ -1,5 +1,6 @@ {% extends "wagtailadmin/base.html" %} {% load image_tags %} +{% load wagtailusers_tags %} {% load i18n %} {% block titletag %}{% trans "Add group" %}{% endblock %} {% block bodyclass %}menu-groups{% endblock %} @@ -13,9 +14,17 @@
        {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} - {% include "wagtailadmin/shared/field_as_li.html" with field=form.permissions %} + {% format_permissions permission_bound_field=form.permissions %} + {% include "wagtailusers/groups/includes/page_permissions_formset.html" with formset=formset only %}
      {% endblock %} +{% block extra_js %} + {% include "wagtailadmin/pages/_editor_js.html" %} + + +{% endblock %} diff --git a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py index 074700460..cee2f722e 100644 --- a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py +++ b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py @@ -30,7 +30,7 @@ def format_permissions(permission_bound_field): permissions = permission_bound_field.field._queryset # get a distinct list of the content types that these permissions relate to content_type_ids = set(permissions.values_list('content_type_id', flat=True)) - initial = permission_bound_field.form.initial['permissions'] + initial = permission_bound_field.form.initial.get('permissions', []) object_perms = [] other_perms = [] diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 1c6347239..796b678f3 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -80,19 +80,30 @@ def index(request): @permission_required(change_user_perm) def create(request): + GroupPagePermissionFormSet = inlineformset_factory( + Group, + GroupPagePermission, + formset=BaseGroupPagePermissionFormSet, + extra=0 + ) if request.POST: form = GroupForm(request.POST) - if form.is_valid(): + formset = GroupPagePermissionFormSet(request.POST) + if form.is_valid() and formset.is_valid(): group = form.save() + formset.instance = group + formset.save() messages.success(request, _("Group '{0}' created.").format(group)) return redirect('wagtailusers_groups_index') else: messages.error(request, _("The group could not be created due to errors.")) else: form = GroupForm() + formset = GroupPagePermissionFormSet() return render(request, 'wagtailusers/groups/create.html', { 'form': form, + 'formset': formset, }) From 21f2fe76d0a336bb03dce15d173acd9eb64d3caa Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Mon, 7 Jul 2014 17:06:53 +0100 Subject: [PATCH 018/155] Update wagtailusers.tests to supply formset.management_form POST data --- wagtail/wagtailusers/tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index ef52af3ff..dd0ff00c7 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -166,6 +166,13 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): return self.client.get(reverse('wagtailusers_groups_create'), params) def post(self, post_data={}): + post_defaults = { + u'page_permissions-TOTAL_FORMS': [u'0'], + u'page_permissions-MAX_NUM_FORMS': [u'1000'], + u'page_permissions-INITIAL_FORMS': [u'0'], + } + for k, v in post_defaults.iteritems(): + post_data[k] = post_data.get(k, v) return self.client.post(reverse('wagtailusers_groups_create'), post_data) def test_simple(self): @@ -198,6 +205,14 @@ class TestGroupEditView(TestCase, WagtailTestUtils): return self.client.get(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), params) def post(self, post_data={}, group_id=None): + post_defaults = { + u'name': 'test group', + u'page_permissions-TOTAL_FORMS': [u'0'], + u'page_permissions-MAX_NUM_FORMS': [u'1000'], + u'page_permissions-INITIAL_FORMS': [u'0'], + } + for k, v in post_defaults.iteritems(): + post_data[k] = post_data.get(k, v) return self.client.post(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), post_data) def test_simple(self): From 045644abc148b5fcbfddd9e62c1290e791763a93 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Tue, 8 Jul 2014 10:51:23 +0100 Subject: [PATCH 019/155] Require change_group_perm for access to groups views --- wagtail/wagtailusers/views/groups.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 796b678f3..6515bea43 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -1,5 +1,4 @@ from django.shortcuts import render, redirect, get_object_or_404 -from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.contrib.auth.decorators import permission_required from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @@ -11,17 +10,11 @@ from django.forms.models import inlineformset_factory from wagtail.wagtailadmin.forms import SearchForm from wagtail.wagtailusers.forms import GroupForm, BaseGroupPagePermissionFormSet from wagtail.wagtailcore.models import GroupPagePermission -from wagtail.wagtailcore.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME -User = get_user_model() - -# Typically we would check the permission 'auth.change_user' for user -# management actions, but this may vary according to the AUTH_USER_MODEL -# setting -change_user_perm = "{0}.change_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()) +change_group_perm = "auth.change_group" -@permission_required(change_user_perm) +@permission_required(change_group_perm) @vary_on_headers('X-Requested-With') def index(request): q = None @@ -78,7 +71,7 @@ def index(request): }) -@permission_required(change_user_perm) +@permission_required(change_group_perm) def create(request): GroupPagePermissionFormSet = inlineformset_factory( Group, @@ -107,7 +100,7 @@ def create(request): }) -@permission_required(change_user_perm) +@permission_required(change_group_perm) def edit(request, group_id): group = get_object_or_404(Group, id=group_id) GroupPagePermissionFormSet = inlineformset_factory( From 79d80b5c592d663569469907ae13209154aa323d Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Tue, 8 Jul 2014 12:49:38 +0100 Subject: [PATCH 020/155] Amend GroupForm.save(), so as not to blat non-user-facing django permissions --- wagtail/wagtailusers/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 06ae6bbbf..953d6b071 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -179,9 +179,10 @@ class GroupForm(forms.ModelForm): # be clobbered by this form. try: untouchable_permissions = self.instance.permissions.exclude(pk__in=self.registered_permissions) + bool(untouchable_permissions) # force this to be evaluated, as it's about to change except ValueError: # this form is not bound; we're probably creating a new group - untouchable_permissions = Permission.objects.none() + untouchable_permissions = [] group = super(GroupForm, self).save() group.permissions.add(*untouchable_permissions) return group From 5aee0f01ba79f7d0fdded57068af5a5e49a16db5 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Tue, 8 Jul 2014 12:59:47 +0100 Subject: [PATCH 021/155] Add tests for group, permission and page-permission editing --- wagtail/wagtailusers/tests.py | 205 +++++++++++++++++++++++++++++++--- 1 file changed, 189 insertions(+), 16 deletions(-) diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index dd0ff00c7..1f7cce925 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -1,9 +1,11 @@ from django.test import TestCase from django.core.urlresolvers import reverse -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User, Group, Permission from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailadmin import hooks from wagtail.wagtailusers.models import UserProfile +from wagtail.wagtailcore.models import Page, GroupPagePermission class TestUserIndexView(TestCase, WagtailTestUtils): @@ -180,10 +182,8 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailusers/groups/create.html') - def test_create(self): - response = self.post({ - 'name': "test group", - }) + def test_create_group(self): + response = self.post({'name': "test group"}) # Should redirect back to index self.assertRedirects(response, reverse('wagtailusers_groups_index')) @@ -192,11 +192,58 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): groups = Group.objects.filter(name='test group') self.assertEqual(groups.count(), 1) + def test_group_create_adding_permissions(self): + response = self.post({ + 'name': "test group", + u'page_permissions-0-id': [u''], + u'page_permissions-0-page': [u'1'], + u'page_permissions-0-permission_type': [u'publish'], + u'page_permissions-1-id': [u''], + u'page_permissions-1-page': [u'1'], + u'page_permissions-1-permission_type': [u'edit'], + u'page_permissions-TOTAL_FORMS': [u'2'], + }) + + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + # The test group now exists, with two page permissions + new_group = Group.objects.get(name='test group') + self.assertEqual(new_group.page_permissions.all().count(), 2) + + def test_duplicate_page_permissions_error(self): + # Try to submit duplicate page permission entries + response = self.post({ + 'name': "test group", + u'page_permissions-0-id': [u''], + u'page_permissions-0-page': [u'1'], + u'page_permissions-0-permission_type': [u'publish'], + u'page_permissions-1-id': [u''], + u'page_permissions-1-page': [u'1'], + u'page_permissions-1-permission_type': [u'publish'], + u'page_permissions-TOTAL_FORMS': [u'2'], + }) + + self.assertEqual(response.status_code, 200) + # the second form should have errors + self.assertEqual(bool(response.context['formset'].errors[0]), False) + self.assertEqual(bool(response.context['formset'].errors[1]), True) + class TestGroupEditView(TestCase, WagtailTestUtils): def setUp(self): # Create a group to edit self.test_group = Group.objects.create(name='test group') + self.root_page = Page.objects.get(id=1) + self.root_add_permission = GroupPagePermission.objects.create(page=self.root_page, + permission_type='add', + group=self.test_group) + # Get the hook-registered permissions, and add one to this group + self.registered_permissions = Permission.objects.none() + for fn in hooks.get_hooks('register_permissions'): + self.registered_permissions = self.registered_permissions | fn() + self.existing_permission = self.registered_permissions.order_by('pk')[0] + self.another_permission = self.registered_permissions.order_by('pk')[1] + + self.test_group.permissions.add(self.existing_permission) # Login self.login() @@ -207,26 +254,38 @@ class TestGroupEditView(TestCase, WagtailTestUtils): def post(self, post_data={}, group_id=None): post_defaults = { u'name': 'test group', - u'page_permissions-TOTAL_FORMS': [u'0'], + u'permissions': [self.existing_permission.id], + u'page_permissions-TOTAL_FORMS': [u'1'], u'page_permissions-MAX_NUM_FORMS': [u'1000'], - u'page_permissions-INITIAL_FORMS': [u'0'], + u'page_permissions-INITIAL_FORMS': [u'1'], # as we have one page permission already + u'page_permissions-0-id': [self.root_add_permission.id], + u'page_permissions-0-page': [self.root_add_permission.page.id], + u'page_permissions-0-permission_type': [self.root_add_permission.permission_type] } for k, v in post_defaults.iteritems(): post_data[k] = post_data.get(k, v) return self.client.post(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), post_data) + def add_non_registered_perm(self): + # Some groups may have django permissions assigned that are not + # hook-registered as part of the wagtail interface. We need to ensure + # that these permissions are not overwritten by our views. + # Tests that use this method are testing the aforementioned + # functionality. + self.non_registered_perms = Permission.objects.exclude(id__in=self.registered_permissions) + self.non_registered_perm = self.non_registered_perms[0] + self.test_group.permissions.add(self.non_registered_perm) + def test_simple(self): response = self.get() self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailusers/groups/edit.html') - def test_nonexistant_redirect(self): + def test_nonexistant_group_redirect(self): self.assertEqual(self.get(group_id=100000).status_code, 404) - def test_edit(self): - response = self.post({ - 'name': "test group edited", - }) + def test_group_edit(self): + response = self.post({'name': "test group edited"}) # Should redirect back to index self.assertRedirects(response, reverse('wagtailusers_groups_index')) @@ -235,11 +294,125 @@ class TestGroupEditView(TestCase, WagtailTestUtils): group = Group.objects.get(id=self.test_group.id) self.assertEqual(group.name, 'test group edited') - def test_edit_validation_error(self): + def test_group_edit_validation_error(self): # Leave "name" field blank. This should give a validation error - response = self.post({ - 'name': "", - }) + response = self.post({'name': ""}) # Should not redirect to index self.assertEqual(response.status_code, 200) + + def test_group_edit_adding_page_permissions(self): + # The test group has one page permission to begin with + self.assertEqual(self.test_group.page_permissions.count(), 1) + response = self.post({ + u'page_permissions-1-id': [u''], + u'page_permissions-1-page': [u'1'], + u'page_permissions-1-permission_type': [u'publish'], + u'page_permissions-2-id': [u''], + u'page_permissions-2-page': [u'1'], + u'page_permissions-2-permission_type': [u'edit'], + u'page_permissions-TOTAL_FORMS': [u'3'], + }) + + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + # The test group now has three page permissions + self.assertEqual(self.test_group.page_permissions.count(), 3) + + def test_group_edit_deleting_page_permissions(self): + # The test group has one page permissions to begin with + self.assertEqual(self.test_group.page_permissions.count(), 1) + + response = self.post({ + u'page_permissions-0-DELETE': [u'1'], + }) + + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + # The test group now has zero page permissions + self.assertEqual(self.test_group.page_permissions.count(), 0) + + def test_group_edit_loads_with_page_permissions_shown(self): + # The test group has one page permission to begin with + self.assertEqual(self.test_group.page_permissions.count(), 1) + + response = self.get() + + self.assertEqual(response.context['formset'].management_form['INITIAL_FORMS'].value(), 1) + self.assertEqual(response.context['formset'].forms[0].instance, self.root_add_permission) + + root_edit_perm = GroupPagePermission.objects.create(page=self.root_page, + permission_type='edit', + group=self.test_group) + + # The test group now has two page permissions + self.assertEqual(self.test_group.page_permissions.count(), 2) + + # Reload the page and check the form instances + response = self.get() + self.assertEqual(response.context['formset'].management_form['INITIAL_FORMS'].value(), 2) + self.assertEqual(response.context['formset'].forms[0].instance, self.root_add_permission) + self.assertEqual(response.context['formset'].forms[1].instance, root_edit_perm) + + def test_duplicate_page_permissions_error(self): + # Try to submit duplicate page permission entries + response = self.post({ + u'page_permissions-1-id': [u''], + u'page_permissions-1-page': [self.root_add_permission.page.id], + u'page_permissions-1-permission_type': [self.root_add_permission.permission_type], + u'page_permissions-TOTAL_FORMS': [u'2'], + }) + + self.assertEqual(response.status_code, 200) + # the second form should have errors + self.assertEqual(bool(response.context['formset'].errors[0]), False) + self.assertEqual(bool(response.context['formset'].errors[1]), True) + + def test_group_add_registered_django_permissions(self): + # The test group has one django permission to begin with + self.assertEqual(self.test_group.permissions.count(), 1) + response = self.post({ + 'permissions': [self.existing_permission.id, self.another_permission.id] + }) + self.assertRedirects(response, reverse('wagtailusers_groups_index')) + self.assertEqual(self.test_group.permissions.count(), 2) + + def test_group_form_includes_non_registered_permissions_in_initial_data(self): + self.add_non_registered_perm() + original_permissions = self.test_group.permissions.all() + self.assertEqual(original_permissions.count(), 2) + + response = self.get() + # See that the form is set up with the correct initial data + self.assertEqual(response.context['form'].initial.get('permissions'), list(original_permissions.values_list('id', flat=True))) + + def test_group_retains_non_registered_permissions_when_editing(self): + self.add_non_registered_perm() + original_permissions = list(self.test_group.permissions.all()) # list() to force evaluation + + # submit the form with no changes (only submitting the exsisting + # permission, as in the self.post function definition) + self.post() + + # See that the group has the same permissions as before + self.assertEqual(list(self.test_group.permissions.all()), original_permissions) + self.assertEqual(self.test_group.permissions.count(), 2) + + def test_group_retains_non_registered_permissions_when_adding(self): + self.add_non_registered_perm() + # Add a second registered permission + self.post({ + 'permissions': [self.existing_permission.id, self.another_permission.id] + }) + + # See that there are now three permissions in total + self.assertEqual(self.test_group.permissions.count(), 3) + # ...including the non-registered one + self.assertIn(self.non_registered_perm, self.test_group.permissions.all()) + + def test_group_retains_non_registered_permissions_when_deleting(self): + self.add_non_registered_perm() + # Delete all registered permissions + self.post({'permissions': []}) + + # See that the non-registered permission is still there + self.assertEqual(self.test_group.permissions.count(), 1) + self.assertEqual(self.test_group.permissions.all()[0], self.non_registered_perm) From 2e8030d6d6836adabc238236790cf1bc20a3072e Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Tue, 8 Jul 2014 14:59:36 +0100 Subject: [PATCH 022/155] Respond to deprecation warning following merge from master --- wagtail/wagtailusers/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 953d6b071..e0c8c88c5 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model from django.contrib.auth.models import Group, Permission -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailusers.models import UserProfile from wagtail.wagtailcore.models import UserPagePermissionsProxy, GroupPagePermission From b0c094f3775b88dafd42825b17913f53c2585f61 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 9 Jul 2014 12:13:31 +0100 Subject: [PATCH 023/155] More deprecation-warning-induced changes --- wagtail/wagtailadmin/wagtail_hooks.py | 2 +- wagtail/wagtailusers/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/wagtail_hooks.py b/wagtail/wagtailadmin/wagtail_hooks.py index d1171a2db..2211a138d 100644 --- a/wagtail/wagtailadmin/wagtail_hooks.py +++ b/wagtail/wagtailadmin/wagtail_hooks.py @@ -1,5 +1,5 @@ from django.contrib.auth.models import Permission -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks def register_permissions(): diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index 1f7cce925..6453fac85 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse from django.contrib.auth.models import User, Group, Permission from wagtail.tests.utils import WagtailTestUtils -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailusers.models import UserProfile from wagtail.wagtailcore.models import Page, GroupPagePermission From 37c1a3db270a31ed3580e765bc9712070dc58f0f Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 9 Jul 2014 12:15:13 +0100 Subject: [PATCH 024/155] Python 3-related changes --- ...ess_constraint_on_group_page_permission.py | 69 ++++++++-------- wagtail/wagtailusers/tests.py | 80 ++++++++++--------- 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py b/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py index 93b867f0b..0e56c6e73 100644 --- a/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py +++ b/wagtail/wagtailcore/migrations/0006_add_uniqueness_constraint_on_group_page_permission.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from south.utils import datetime_utils as datetime from south.db import db from south.v2 import SchemaMigration @@ -9,70 +10,70 @@ class Migration(SchemaMigration): def forwards(self, orm): # Adding unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type'] - db.create_unique(u'wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) + db.create_unique('wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) def backwards(self, orm): # Removing unique constraint on 'GroupPagePermission', fields ['group', 'page', 'permission_type'] - db.delete_unique(u'wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) + db.delete_unique('wagtailcore_grouppagepermission', ['group_id', 'page_id', 'permission_type']) models = { - u'auth.group': { + 'auth.group': { 'Meta': {'object_name': 'Group'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, - u'auth.user': { + 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, - u'contenttypes.contenttype': { + 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - u'wagtailcore.grouppagepermission': { + 'wagtailcore.grouppagepermission': { 'Meta': {'unique_together': "(('group', 'page', 'permission_type'),)", 'object_name': 'GroupPagePermission'}, - 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': u"orm['auth.Group']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': u"orm['wagtailcore.Page']"}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': "orm['wagtailcore.Page']"}), 'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}) }, - u'wagtailcore.page': { + 'wagtailcore.page': { 'Meta': {'object_name': 'Page'}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': u"orm['contenttypes.ContentType']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['contenttypes.ContentType']"}), 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), 'expire_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 'expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': "orm['auth.User']"}), 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), @@ -81,30 +82,30 @@ class Migration(SchemaMigration): 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) }, - u'wagtailcore.pagerevision': { + 'wagtailcore.pagerevision': { 'Meta': {'object_name': 'PageRevision'}, 'approved_go_live_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 'content_json': ('django.db.models.fields.TextField', [], {}), 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': u"orm['wagtailcore.Page']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['wagtailcore.Page']"}), 'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) }, - u'wagtailcore.pageviewrestriction': { + 'wagtailcore.pageviewrestriction': { 'Meta': {'object_name': 'PageViewRestriction'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'view_restrictions'", 'to': u"orm['wagtailcore.Page']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'view_restrictions'", 'to': "orm['wagtailcore.Page']"}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}) }, - u'wagtailcore.site': { + 'wagtailcore.site': { 'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'}, 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}), - 'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': u"orm['wagtailcore.Page']"}) + 'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': "orm['wagtailcore.Page']"}) } } - complete_apps = ['wagtailcore'] \ No newline at end of file + complete_apps = ['wagtailcore'] diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py index 6453fac85..b5ad6ca32 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.test import TestCase from django.core.urlresolvers import reverse from django.contrib.auth.models import User, Group, Permission @@ -6,6 +7,7 @@ from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore import hooks from wagtail.wagtailusers.models import UserProfile from wagtail.wagtailcore.models import Page, GroupPagePermission +import six class TestUserIndexView(TestCase, WagtailTestUtils): @@ -169,11 +171,11 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): def post(self, post_data={}): post_defaults = { - u'page_permissions-TOTAL_FORMS': [u'0'], - u'page_permissions-MAX_NUM_FORMS': [u'1000'], - u'page_permissions-INITIAL_FORMS': [u'0'], + 'page_permissions-TOTAL_FORMS': ['0'], + 'page_permissions-MAX_NUM_FORMS': ['1000'], + 'page_permissions-INITIAL_FORMS': ['0'], } - for k, v in post_defaults.iteritems(): + for k, v in six.iteritems(post_defaults): post_data[k] = post_data.get(k, v) return self.client.post(reverse('wagtailusers_groups_create'), post_data) @@ -195,13 +197,13 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): def test_group_create_adding_permissions(self): response = self.post({ 'name': "test group", - u'page_permissions-0-id': [u''], - u'page_permissions-0-page': [u'1'], - u'page_permissions-0-permission_type': [u'publish'], - u'page_permissions-1-id': [u''], - u'page_permissions-1-page': [u'1'], - u'page_permissions-1-permission_type': [u'edit'], - u'page_permissions-TOTAL_FORMS': [u'2'], + 'page_permissions-0-id': [''], + 'page_permissions-0-page': ['1'], + 'page_permissions-0-permission_type': ['publish'], + 'page_permissions-1-id': [''], + 'page_permissions-1-page': ['1'], + 'page_permissions-1-permission_type': ['edit'], + 'page_permissions-TOTAL_FORMS': ['2'], }) self.assertRedirects(response, reverse('wagtailusers_groups_index')) @@ -213,13 +215,13 @@ class TestGroupCreateView(TestCase, WagtailTestUtils): # Try to submit duplicate page permission entries response = self.post({ 'name': "test group", - u'page_permissions-0-id': [u''], - u'page_permissions-0-page': [u'1'], - u'page_permissions-0-permission_type': [u'publish'], - u'page_permissions-1-id': [u''], - u'page_permissions-1-page': [u'1'], - u'page_permissions-1-permission_type': [u'publish'], - u'page_permissions-TOTAL_FORMS': [u'2'], + 'page_permissions-0-id': [''], + 'page_permissions-0-page': ['1'], + 'page_permissions-0-permission_type': ['publish'], + 'page_permissions-1-id': [''], + 'page_permissions-1-page': ['1'], + 'page_permissions-1-permission_type': ['publish'], + 'page_permissions-TOTAL_FORMS': ['2'], }) self.assertEqual(response.status_code, 200) @@ -253,16 +255,16 @@ class TestGroupEditView(TestCase, WagtailTestUtils): def post(self, post_data={}, group_id=None): post_defaults = { - u'name': 'test group', - u'permissions': [self.existing_permission.id], - u'page_permissions-TOTAL_FORMS': [u'1'], - u'page_permissions-MAX_NUM_FORMS': [u'1000'], - u'page_permissions-INITIAL_FORMS': [u'1'], # as we have one page permission already - u'page_permissions-0-id': [self.root_add_permission.id], - u'page_permissions-0-page': [self.root_add_permission.page.id], - u'page_permissions-0-permission_type': [self.root_add_permission.permission_type] + 'name': 'test group', + 'permissions': [self.existing_permission.id], + 'page_permissions-TOTAL_FORMS': ['1'], + 'page_permissions-MAX_NUM_FORMS': ['1000'], + 'page_permissions-INITIAL_FORMS': ['1'], # as we have one page permission already + 'page_permissions-0-id': [self.root_add_permission.id], + 'page_permissions-0-page': [self.root_add_permission.page.id], + 'page_permissions-0-permission_type': [self.root_add_permission.permission_type] } - for k, v in post_defaults.iteritems(): + for k, v in six.iteritems(post_defaults): post_data[k] = post_data.get(k, v) return self.client.post(reverse('wagtailusers_groups_edit', args=(group_id or self.test_group.id, )), post_data) @@ -305,13 +307,13 @@ class TestGroupEditView(TestCase, WagtailTestUtils): # The test group has one page permission to begin with self.assertEqual(self.test_group.page_permissions.count(), 1) response = self.post({ - u'page_permissions-1-id': [u''], - u'page_permissions-1-page': [u'1'], - u'page_permissions-1-permission_type': [u'publish'], - u'page_permissions-2-id': [u''], - u'page_permissions-2-page': [u'1'], - u'page_permissions-2-permission_type': [u'edit'], - u'page_permissions-TOTAL_FORMS': [u'3'], + 'page_permissions-1-id': [''], + 'page_permissions-1-page': ['1'], + 'page_permissions-1-permission_type': ['publish'], + 'page_permissions-2-id': [''], + 'page_permissions-2-page': ['1'], + 'page_permissions-2-permission_type': ['edit'], + 'page_permissions-TOTAL_FORMS': ['3'], }) self.assertRedirects(response, reverse('wagtailusers_groups_index')) @@ -323,7 +325,7 @@ class TestGroupEditView(TestCase, WagtailTestUtils): self.assertEqual(self.test_group.page_permissions.count(), 1) response = self.post({ - u'page_permissions-0-DELETE': [u'1'], + 'page_permissions-0-DELETE': ['1'], }) self.assertRedirects(response, reverse('wagtailusers_groups_index')) @@ -355,10 +357,10 @@ class TestGroupEditView(TestCase, WagtailTestUtils): def test_duplicate_page_permissions_error(self): # Try to submit duplicate page permission entries response = self.post({ - u'page_permissions-1-id': [u''], - u'page_permissions-1-page': [self.root_add_permission.page.id], - u'page_permissions-1-permission_type': [self.root_add_permission.permission_type], - u'page_permissions-TOTAL_FORMS': [u'2'], + 'page_permissions-1-id': [''], + 'page_permissions-1-page': [self.root_add_permission.page.id], + 'page_permissions-1-permission_type': [self.root_add_permission.permission_type], + 'page_permissions-TOTAL_FORMS': ['2'], }) self.assertEqual(response.status_code, 200) From 10a2984cde1026d1dbfb9a03bcd262f1942fb4d3 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 11 Jul 2014 11:46:18 +0100 Subject: [PATCH 025/155] More granular group view permissions checks --- wagtail/wagtailusers/views/groups.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 6515bea43..008a8cb0b 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -1,6 +1,6 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.models import Group -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import permission_required, user_passes_test from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages from django.utils.translation import ugettext as _ @@ -11,10 +11,15 @@ from wagtail.wagtailadmin.forms import SearchForm from wagtail.wagtailusers.forms import GroupForm, BaseGroupPagePermissionFormSet from wagtail.wagtailcore.models import GroupPagePermission -change_group_perm = "auth.change_group" + +def user_has_group_model_perm(user): + for verb in ['add', 'change', 'delete']: + if user.has_perm('auth.%s_group' % verb): + return True + return False -@permission_required(change_group_perm) +@user_passes_test(user_has_group_model_perm) @vary_on_headers('X-Requested-With') def index(request): q = None @@ -71,7 +76,7 @@ def index(request): }) -@permission_required(change_group_perm) +@permission_required('auth.add_group') def create(request): GroupPagePermissionFormSet = inlineformset_factory( Group, @@ -100,7 +105,7 @@ def create(request): }) -@permission_required(change_group_perm) +@permission_required('auth.change_group') def edit(request, group_id): group = get_object_or_404(Group, id=group_id) GroupPagePermissionFormSet = inlineformset_factory( From 2543478dc3e1d4ba995056dfedb274757a425471 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 11 Jul 2014 11:46:51 +0100 Subject: [PATCH 026/155] Implement group delete functionality --- .../wagtailusers/groups/confirm_delete.html | 33 +++++++++++++++++++ .../templates/wagtailusers/groups/edit.html | 6 +++- wagtail/wagtailusers/urls/groups.py | 1 + wagtail/wagtailusers/views/groups.py | 14 ++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 wagtail/wagtailusers/templates/wagtailusers/groups/confirm_delete.html diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/confirm_delete.html b/wagtail/wagtailusers/templates/wagtailusers/groups/confirm_delete.html new file mode 100644 index 000000000..c86ab250f --- /dev/null +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/confirm_delete.html @@ -0,0 +1,33 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n %} +{% block titletag %}{% trans "Delete group" %}{% endblock %} +{% block bodyclass %}menu-groups{% endblock %} + +{% block content %} + {% trans "Delete group" as del_str %} + {% include "wagtailadmin/shared/header.html" with title=del_str subtitle=group.name icon="group" %} + +
      +
      +

      + {% blocktrans with group_user_count=group.user_set.count group_name=group.name %}The group '{{ group_name }}' has {{ group_user_count }} users assigned.{% endblocktrans %} +

      +

      + {% if group.user_set.count %} + If you delete the group, those users will lose the permissions + granted to them by membership of this group, but retain + permissions they have through membership of other groups. + {% else %} + It is probably safe to delete. + {% endif %} +

      +
      +
      +

      {% trans "Are you sure you want to delete this group?" %}

      +
      + {% csrf_token %} + + +
      +
      +{% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html index 54b4b1d9b..7ef34d89d 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html @@ -16,7 +16,11 @@ {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} {% format_permissions permission_bound_field=form.permissions %} {% include "wagtailusers/groups/includes/page_permissions_formset.html" with formset=formset only %} -
    • +
    • + {% if perms.auth.delete_group %} + {% trans "Delete group" %} + {% endif %} +
    diff --git a/wagtail/wagtailusers/urls/groups.py b/wagtail/wagtailusers/urls/groups.py index 4899eea12..be3ba3db9 100644 --- a/wagtail/wagtailusers/urls/groups.py +++ b/wagtail/wagtailusers/urls/groups.py @@ -5,4 +5,5 @@ urlpatterns = [ url(r'^$', groups.index, name='wagtailusers_groups_index'), url(r'^new/$', groups.create, name='wagtailusers_groups_create'), url(r'^(\d+)/$', groups.edit, name='wagtailusers_groups_edit'), + url(r'^(\d+)/delete/$', groups.delete, name='wagtailusers_groups_delete'), ] diff --git a/wagtail/wagtailusers/views/groups.py b/wagtail/wagtailusers/views/groups.py index 008a8cb0b..07a475c77 100644 --- a/wagtail/wagtailusers/views/groups.py +++ b/wagtail/wagtailusers/views/groups.py @@ -133,3 +133,17 @@ def edit(request, group_id): 'form': form, 'formset': formset, }) + + +@permission_required('auth.delete_group') +def delete(request, group_id): + group = get_object_or_404(Group, id=group_id) + + if request.POST: + group.delete() + messages.success(request, _("Group '{0}' deleted.").format(group.name)) + return redirect('wagtailusers_groups_index') + + return render(request, "wagtailusers/groups/confirm_delete.html", { + 'group': group, + }) From 10037fd4cfa23086fb55a2f851de01355487ac49 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 11 Jul 2014 11:51:08 +0100 Subject: [PATCH 027/155] Templates folder structure tidy --- .../{ => groups/includes}/formatted_permissions.html | 0 wagtail/wagtailusers/templatetags/wagtailusers_tags.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename wagtail/wagtailusers/templates/wagtailusers/{ => groups/includes}/formatted_permissions.html (100%) diff --git a/wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/formatted_permissions.html similarity index 100% rename from wagtail/wagtailusers/templates/wagtailusers/formatted_permissions.html rename to wagtail/wagtailusers/templates/wagtailusers/groups/includes/formatted_permissions.html diff --git a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py index cee2f722e..8924f51c0 100644 --- a/wagtail/wagtailusers/templatetags/wagtailusers_tags.py +++ b/wagtail/wagtailusers/templatetags/wagtailusers_tags.py @@ -3,7 +3,7 @@ from django import template register = template.Library() -@register.inclusion_tag('wagtailusers/formatted_permissions.html') +@register.inclusion_tag('wagtailusers/groups/includes/formatted_permissions.html') def format_permissions(permission_bound_field): """ Given a bound field with a queryset of Permission objects, constructs a From 8903d1e74134d8c504cf0575f714bd669d9313c4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 11 Jul 2014 16:17:03 +0100 Subject: [PATCH 028/155] Elasticsearch: Cast __in lookup values to list This makes sure lazily-evaluated lists (like querysets) are evaluated so they can be converted to JSON --- wagtail/wagtailsearch/backends/elasticsearch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailsearch/backends/elasticsearch.py b/wagtail/wagtailsearch/backends/elasticsearch.py index 55f1971a2..45b15ffca 100644 --- a/wagtail/wagtailsearch/backends/elasticsearch.py +++ b/wagtail/wagtailsearch/backends/elasticsearch.py @@ -205,7 +205,7 @@ class ElasticSearchQuery(object): if lookup == 'in': return { 'terms': { - field_index_name: value, + field_index_name: list(value), } } From 439dd600f41c18663386c065563db4c0f0528845 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 18 Jul 2014 10:20:03 +0100 Subject: [PATCH 029/155] ongoing work to make new settings menu work --- .../static/wagtailadmin/js/core.js | 6 + .../static/wagtailadmin/scss/core.scss | 130 ++++++++---------- .../wagtailadmin/shared/main_nav.html | 18 +++ 3 files changed, 83 insertions(+), 71 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js index 85c4ca31e..980658efb 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js @@ -21,6 +21,12 @@ $(function(){ } }); + $('.nav-main a').on('click', function(){ + $('.nav-wrapper').toggleClass('submenu-active') + $(this).closest('li').toggleClass('active'); + return false + }) + // Enable swishy section navigation menu $('.explorer').addClass('dl-menuwrapper').dlmenu({ animationClasses : { diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index 55e27b067..1ee9af912 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -163,6 +163,7 @@ img{ } .nav-main{ + $selected-highlight:darken($color-grey-1, 10%); top: 43px; bottom: 0px; @@ -176,15 +177,15 @@ img{ } ul{ - border-top:1px solid $color-grey-1-1; + border-top:1px solid rgba(100,100,100,0.2); } li{ - border-bottom:1px solid $color-grey-1-1; + border-bottom:1px solid rgba(100,100,100,0.2); position:relative; - &.selected{ - background-color:$color-grey-1; + &.selected, &.active{ + background:$selected-highlight; } a:before{ @@ -198,7 +199,7 @@ img{ .menu-images &.menu-images, .menu-search &.menu-search, .menu-explorer &.menu-explorer{ - background:darken($color-grey-1, 10%); + background:$selected-highlight; a{ color:white; @@ -228,76 +229,60 @@ img{ } } - .avatar{ - display:none; - } + .submenu{ + @include transition(width 0.2s ease); + background:$selected-highlight; + position:fixed; + height:100%; + width:0; + top:0; + left:150px; + padding:0; + overflow:auto; + max-height:100%; + border-right:1px solid rgba(0,0,0,0.1); + @include box-shadow(5px 0 5px rgba(0,0,0,0.1)); - /* search form */ - #menu-search{ - position:relative; - - .fields{ - @include transition(background-color 0.2s ease); - border:0; - li{ - border:0; - } - } - .field{ - padding:0; - color: #AAA; + h2{ + padding:0.2em 0; + font-weight:500; + text-transform:none; + text-align:center; &:before{ - position:absolute; - left:0.75em; - top:0.45em; - } - } - .submit{ - @include visuallyhidden(); - } - label{ - font-weight:normal; - -webkit-font-smoothing: auto; - line-height:inherit; - text-transform:uppercase; - padding: 0.9em 1.2em 0.9em 3.7em; - color: #AAA; - font-size:0.95em; - } - input{ - float:left; - margin-top:-1000px; - text-transform:uppercase; - font-size:1em; - padding: 0.9em 1.2em 0.9em 3.5em; - border:0; - border-radius:0; - background-color:transparent; - line-height:inherit; - } - - &:hover{ - .field, input{ - color:white; + font-size:4em; + display:block; + text-align:center; + margin:0 0 0.2em 0; + width:100%; } } - &.focussed{ - label{ - display:none; + a{ + text-transform:none; + padding:1em 0; + white-space:normal; + padding-left:3em; + + &:before{ + margin-left:-1.5em; } - .fields{ - background-color:$color-grey-4; - } - .field{ - color: $color-grey-1; - } - input{ - margin-top:0px; - color:$color-grey-1; + + &:hover{ + background-color:rgba(100,100,100,0.2); } } + + + } + + li.active .submenu{ + width:150px; + padding:0 1.5em 1.5em 1.5em; + } + + .avatar{ + display:none; } } @@ -551,6 +536,9 @@ footer{ .content-wrapper{ z-index:3; } +.submenu{ + z-index:6; +} footer, .logo{ z-index:100; } @@ -661,7 +649,7 @@ footer, .logo{ .nav-main{ position:absolute; top: 125px; - margin-bottom: 116px; /* WARNING: magic number - the height of the .footer */ + margin-bottom: 116px; /* WARNING - magic number - the height of the .footer */ .footer{ padding-top:1em; @@ -762,7 +750,10 @@ footer, .logo{ z-index:$explorer-z-index; } .nav-wrapper{ - z-index:auto; /* allows overspill of messages banner onto left menu, but also explorer to spill over main content */ + z-index:auto; /* allows overspill of messages banner onto left menu, but also explorer to spill over main content */ + } + .nav-wrapper.submenu-active{ + z-index:5; } } @@ -770,9 +761,6 @@ footer, .logo{ .wrapper{ max-width:$breakpoint-desktop-larger; } - .nav-wrapper .inner{ - background:$color-grey-1; - } footer{ width:90em; diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html index ce06d895c..3c8e012d6 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html @@ -6,6 +6,24 @@ {{ menu_item.render_html }} {% endfor %} +
  • + Top level item + + +
  • +
    {{ perm_tuple.0.name }} - {% for perm_tuple in content_perms_dict.others %} - {% if not forloop.last %}
    {% endif %} - {% endfor %}
    - - - - - - - {% for content_perms_dict in object_perms %} - - - - - - - {% endfor %} -
    ObjectAddChangeDelete
    {{ content_perms_dict.object|capfirst }} - {% with content_perms_dict.add as perm_tuple %} - - {% endwith %} - - {% with content_perms_dict.change as perm_tuple %} - - {% endwith %} - - {% with content_perms_dict.delete as perm_tuple %} - - {% endwith %} -
    -

    Other permissions

    - - {% for perm_tuple in other_perms %} - - - + + + + + + + + + + + + + {% for content_perms_dict in object_perms %} + + + - - {% endfor %} + {% endwith %} + + + + + {% endfor %} + +
    {{ perm_tuple.0.name }} -
    {% trans "Name" %}{% trans "Add" %}{% trans "Change" %}{% trans "Delete" %}

    {{ content_perms_dict.object|capfirst }}

    + {% with content_perms_dict.add as perm_tuple %} - -
    + {% with content_perms_dict.change as perm_tuple %} + + {% endwith %} + + {% with content_perms_dict.delete as perm_tuple %} + + {% endwith %} +
    + +

    {% trans "Other permissions" %}

    + + + + + + + + + + + {% for perm_tuple in other_perms %} + + + + + {% endfor %} +
    {% trans "Name" %}
    {{ perm_tuple.0.name }} + +
    diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html index 03cb2a19e..ccfffaf42 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html @@ -1,5 +1,8 @@ {% load i18n %} -{{ formset.management_form }} +

    Page permissions

    + +{{ formset.management_form }}{# what is this? #} +
      {% for form in formset.forms %} {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=form only %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/list.html b/wagtail/wagtailusers/templates/wagtailusers/groups/list.html index 33ccd001a..e72fc554f 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/list.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/list.html @@ -17,7 +17,6 @@

      - {{ group.name }}

      From b310fcad017c171d6e99643cdf67aad1d851e575 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 18 Jul 2014 16:04:50 +0100 Subject: [PATCH 032/155] styled list of page permissions to more resemble other permissions above --- wagtail/wagtailcore/models.py | 6 +- .../static/wagtailusers/scss/groups_edit.scss | 9 +++ .../templates/wagtailusers/groups/create.html | 8 +++ .../templates/wagtailusers/groups/edit.html | 11 +++- .../includes/page_permissions_form.html | 56 +++++++------------ .../includes/page_permissions_formset.html | 35 ++++++++++-- 6 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 wagtail/wagtailusers/static/wagtailusers/scss/groups_edit.scss diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 8b337f180..569c2e84e 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -944,9 +944,9 @@ class PageRevision(models.Model): PAGE_PERMISSION_TYPE_CHOICES = [ - ('add', 'Add'), - ('edit', 'Edit'), - ('publish', 'Publish'), + ('add', 'Add / edit pages you own'), + ('edit', 'Add / edit any page'), + ('publish', 'Publish pages'), ] diff --git a/wagtail/wagtailusers/static/wagtailusers/scss/groups_edit.scss b/wagtail/wagtailusers/static/wagtailusers/scss/groups_edit.scss new file mode 100644 index 000000000..85a13f071 --- /dev/null +++ b/wagtail/wagtailusers/static/wagtailusers/scss/groups_edit.scss @@ -0,0 +1,9 @@ +.listing { + .field label{ + display:none; + } + input,select,textarea{ + font-size:1em; + } + +} \ No newline at end of file diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html index b675694dc..0184871cd 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html @@ -2,8 +2,16 @@ {% load image_tags %} {% load wagtailusers_tags %} {% load i18n %} + {% block titletag %}{% trans "Add group" %}{% endblock %} {% block bodyclass %}menu-groups{% endblock %} + +{% block extra_css %} + {% compress css %} + + {% endcompress %} +{% endblock %} + {% block content %} {% trans "Add group" as add_group_str %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html index 68403bd55..3616f7d5a 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/edit.html @@ -2,8 +2,17 @@ {% load image_tags %} {% load wagtailusers_tags %} {% load i18n %} +{% load compress %} + {% block titletag %}{% trans "Editing" %} {{ group.name }}{% endblock %} {% block bodyclass %}menu-groups{% endblock %} + +{% block extra_css %} + {% compress css %} + + {% endcompress %} +{% endblock %} + {% block content %} {% trans "Editing" as editing_str %} @@ -12,7 +21,7 @@
      {% csrf_token %} - +
        {% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html index d43b1cdef..cf74e6ca4 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html @@ -1,38 +1,24 @@ {% load i18n %} -{% comment %} - TODO - DRY this form with /wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html -{% endcomment %} - diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html index ccfffaf42..e552e8e38 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html @@ -3,14 +3,37 @@ {{ formset.management_form }}{# what is this? #} -
          - {% for form in formset.forms %} - {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=form only %} - {% endfor %} -
        + + + + + + + + + + + + + {% for form in formset.forms %} + + {% if form.non_field_errors %} +

        + {% for error in form.non_field_errors %} + {{ error|escape }} + {% endfor %} +

        + {% endif %} + {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=form only %} + + {% endfor %} + +
        PagePermission type

        From 1be972a7bcba011ce4ce0fbc81a13db844a5c226 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 18 Jul 2014 16:05:33 +0100 Subject: [PATCH 033/155] wording update --- wagtail/wagtailcore/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 569c2e84e..ad6d941a0 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -944,9 +944,9 @@ class PageRevision(models.Model): PAGE_PERMISSION_TYPE_CHOICES = [ - ('add', 'Add / edit pages you own'), - ('edit', 'Add / edit any page'), - ('publish', 'Publish pages'), + ('add', 'Add/edit pages you own'), + ('edit', 'Add/edit any page'), + ('publish', 'Publish any page'), ] From c60cdac6b486e3a5b27a0822c00ea9d3fb004874 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 18 Jul 2014 16:10:41 +0100 Subject: [PATCH 034/155] styling updates --- .../wagtailusers/groups/includes/page_permissions_form.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html index cf74e6ca4..2521b11fe 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_form.html @@ -1,8 +1,8 @@ {% load i18n %} - {% trans "Choose a different root page" as choose_another_text_str %} - {% trans "Choose a root page" as choose_one_text_str %} + {% trans "Edit page" as choose_another_text_str %} + {% trans "Choose page" as choose_one_text_str %} {% if form.instance.page %} {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} @@ -19,6 +19,6 @@ - + From 17d839281f27aeb83d31f5f2c750a5b64e0764aa Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Fri, 18 Jul 2014 16:11:50 +0100 Subject: [PATCH 035/155] styling updates --- wagtail/wagtailusers/templates/wagtailusers/groups/create.html | 1 + .../wagtailusers/groups/includes/page_permissions_formset.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html index 0184871cd..f623488e3 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/create.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/create.html @@ -2,6 +2,7 @@ {% load image_tags %} {% load wagtailusers_tags %} {% load i18n %} +{% load compress %} {% block titletag %}{% trans "Add group" %}{% endblock %} {% block bodyclass %}menu-groups{% endblock %} diff --git a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html index e552e8e38..98a60a8c3 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html +++ b/wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.html @@ -37,6 +37,6 @@

        - {% trans "Add another page permission" %} + {% trans "Add a page permission" %}

        From e8dd039dce2c6f62d84c0e99de4ec574ecd0d46c Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 24 Jul 2014 11:10:42 +0100 Subject: [PATCH 036/155] ongoing tweaks --- .../static/wagtailadmin/js/core.js | 7 +++++-- .../static/wagtailadmin/scss/core.scss | 21 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js index 980658efb..51056d446 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/core.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/core.js @@ -22,9 +22,12 @@ $(function(){ }); $('.nav-main a').on('click', function(){ - $('.nav-wrapper').toggleClass('submenu-active') $(this).closest('li').toggleClass('active'); - return false + + if($(this).closest('li').find('.submenu')){ + $('.nav-wrapper').toggleClass('submenu-active') + return false + } }) // Enable swishy section navigation menu diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss index 5866a84c1..4768ef9e4 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/core.scss @@ -229,20 +229,33 @@ img{ } } + li.active > a{ + color:$color-orange; + } + + li.active .submenu{ + @include transition(width 0.2s ease); + } + .submenu{ @include transition(width 0.2s ease); background:$selected-highlight; position:fixed; height:100%; width:0; + padding:0; top:0; left:150px; - padding:0; overflow:auto; max-height:100%; border-right:1px solid rgba(0,0,0,0.1); @include box-shadow(5px 0 5px rgba(0,0,0,0.1)); + h2,ul{ + float:right; + width:150px; + } + h2{ padding:0.2em 0; font-weight:500; @@ -274,14 +287,16 @@ img{ background-color:rgba(100,100,100,0.2); } } - - } li.active .submenu{ width:150px; padding:0 1.5em 1.5em 1.5em; } + li .submenu{ + width:0; + padding:0; + } .avatar{ display:none; From 35d17ab2120d2e5b41f78ee714231de87c8e8450 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 14 Aug 2014 11:36:12 +0100 Subject: [PATCH 037/155] Refactor logic for registering menu items into a Menu class, so that it can be re-used for the settings menu --- wagtail/wagtailadmin/menu.py | 43 +++++++++++++------ .../templatetags/wagtailadmin_tags.py | 11 ++--- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/wagtail/wagtailadmin/menu.py b/wagtail/wagtailadmin/menu.py index 34a8c792f..ecad1bcbb 100644 --- a/wagtail/wagtailadmin/menu.py +++ b/wagtail/wagtailadmin/menu.py @@ -8,10 +8,9 @@ try: except ImportError: from django.forms.util import flatatt -from django.conf import settings -from django.forms import MediaDefiningClass +from django.forms import MediaDefiningClass, Media from django.utils.text import slugify -from django.utils.html import format_html, format_html_join +from django.utils.html import format_html from wagtail.wagtailcore import hooks @@ -42,15 +41,31 @@ class MenuItem(with_metaclass(MediaDefiningClass)): self.name, self.url, self.classnames, self.attr_string, self.label) -_master_menu_item_list = None -def get_master_menu_item_list(): - """ - Return the list of menu items registered with the 'register_admin_menu_item' hook. - This is the "master list" because the final admin menu may vary per request - according to the value of is_shown() and the construct_main_menu hook. - """ - global _master_menu_item_list - if _master_menu_item_list is None: - _master_menu_item_list = [fn() for fn in hooks.get_hooks('register_admin_menu_item')] +class Menu(object): + def __init__(self, hook_name): + self.hook_name = hook_name + # _registered_menu_items will be populated on first access to the + # registered_menu_items property. We can't populate it in __init__ because + # we can't rely on all hooks modules to have been imported at the point that + # we create the admin_menu and settings_menu instances + self._registered_menu_items = None - return _master_menu_item_list + @property + def registered_menu_items(self): + if self._registered_menu_items is None: + self._registered_menu_items = [fn() for fn in hooks.get_hooks(self.hook_name)] + return self._registered_menu_items + + def menu_items_for_request(self, request): + return [item for item in self.registered_menu_items if item.is_shown(request)] + + @property + def media(self): + media = Media() + for item in self.registered_menu_items: + media += item.media + return media + + +admin_menu = Menu(hook_name='register_admin_menu_item') +settings_menu = Menu(hook_name='register_settings_menu_item') diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index da764a0c5..c30a3b54c 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -2,12 +2,11 @@ from __future__ import unicode_literals from django.conf import settings from django import template -from django.forms import Media from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy, PageViewRestriction from wagtail.wagtailcore.utils import camelcase_to_underscore -from wagtail.wagtailadmin.menu import get_master_menu_item_list +from wagtail.wagtailadmin.menu import admin_menu register = template.Library() @@ -30,7 +29,7 @@ def explorer_subnav(nodes): @register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True) def main_nav(context): request = context['request'] - menu_items = [item for item in get_master_menu_item_list() if item.is_shown(request)] + menu_items = admin_menu.menu_items_for_request(request) for fn in hooks.get_hooks('construct_main_menu'): fn(request, menu_items) @@ -42,11 +41,7 @@ def main_nav(context): @register.simple_tag def main_nav_js(): - media = Media() - for item in get_master_menu_item_list(): - media += item.media - - return media['js'] + return admin_menu.media['js'] @register.filter("ellipsistrim") From 229e111253db224320f5d7d832cf33c93249826f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 14 Aug 2014 14:38:44 +0100 Subject: [PATCH 038/155] Provide a render_html method on Menu, and use that rather than looping over menu_items in the template --- wagtail/wagtailadmin/menu.py | 24 +++++++++++++++---- .../wagtailadmin/shared/main_nav.html | 4 +--- .../templatetags/wagtailadmin_tags.py | 6 +---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/wagtail/wagtailadmin/menu.py b/wagtail/wagtailadmin/menu.py index ecad1bcbb..29d774e03 100644 --- a/wagtail/wagtailadmin/menu.py +++ b/wagtail/wagtailadmin/menu.py @@ -11,6 +11,7 @@ except ImportError: from django.forms import MediaDefiningClass, Media from django.utils.text import slugify from django.utils.html import format_html +from django.utils.safestring import mark_safe from wagtail.wagtailcore import hooks @@ -42,8 +43,9 @@ class MenuItem(with_metaclass(MediaDefiningClass)): class Menu(object): - def __init__(self, hook_name): - self.hook_name = hook_name + def __init__(self, register_hook_name, construct_hook_name=None): + self.register_hook_name = register_hook_name + self.construct_hook_name = construct_hook_name # _registered_menu_items will be populated on first access to the # registered_menu_items property. We can't populate it in __init__ because # we can't rely on all hooks modules to have been imported at the point that @@ -53,7 +55,7 @@ class Menu(object): @property def registered_menu_items(self): if self._registered_menu_items is None: - self._registered_menu_items = [fn() for fn in hooks.get_hooks(self.hook_name)] + self._registered_menu_items = [fn() for fn in hooks.get_hooks(self.register_hook_name)] return self._registered_menu_items def menu_items_for_request(self, request): @@ -66,6 +68,18 @@ class Menu(object): media += item.media return media + def render_html(self, request): + menu_items = self.menu_items_for_request(request) -admin_menu = Menu(hook_name='register_admin_menu_item') -settings_menu = Menu(hook_name='register_settings_menu_item') + # provide a hook for modifying the menu, if construct_hook_name has been set + if self.construct_hook_name: + for fn in hooks.get_hooks(self.construct_hook_name): + fn(request, menu_items) + + rendered_menu_items = [item.render_html() for item in sorted(menu_items, key=lambda i: i.order)] + + return mark_safe(''.join(rendered_menu_items)) + + +admin_menu = Menu(register_hook_name='register_admin_menu_item', construct_hook_name='construct_main_menu') +settings_menu = Menu(register_hook_name='register_settings_menu_item') diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html index ce06d895c..5f797e695 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/shared/main_nav.html @@ -2,9 +2,7 @@ {% load i18n %}
      diff --git a/wagtail/wagtailsites/urls.py b/wagtail/wagtailsites/urls.py index 7509e1431..96e74a0b2 100644 --- a/wagtail/wagtailsites/urls.py +++ b/wagtail/wagtailsites/urls.py @@ -6,5 +6,6 @@ urlpatterns = [ url(r'^$', views.index, name='wagtailsites_index'), url(r'^new/$', views.create, name='wagtailsites_create'), url(r'^(\d+)/$', views.edit, name='wagtailsites_edit'), + url(r'^(\d+)/delete/$', views.delete, name='wagtailsites_delete'), ] diff --git a/wagtail/wagtailsites/views.py b/wagtail/wagtailsites/views.py index af3c87c45..9ca449f39 100644 --- a/wagtail/wagtailsites/views.py +++ b/wagtail/wagtailsites/views.py @@ -52,3 +52,17 @@ def edit(request, site_id): 'site': site, 'form': form, }) + + +@permission_required('site.delete_site') +def delete(request, site_id): + site = get_object_or_404(Site, id=site_id) + + if request.POST: + site.delete() + messages.success(request, _("Site '{0}' deleted.").format(site.hostname)) + return redirect('wagtailsites_index') + + return render(request, "wagtailsites/confirm_delete.html", { + 'site': site, + }) From f4afab58d63007c8b221f3b794046c9fd7dfbeb9 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 9 Jul 2014 16:55:41 +0100 Subject: [PATCH 121/155] More granular site perms, including hiding the Add/Delete buttons when not permitted --- wagtail/wagtailsites/templates/wagtailsites/edit.html | 6 +++++- .../wagtailsites/templates/wagtailsites/index.html | 8 ++++++-- wagtail/wagtailsites/views.py | 11 +++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailsites/templates/wagtailsites/edit.html b/wagtail/wagtailsites/templates/wagtailsites/edit.html index 49100874e..50350cd70 100644 --- a/wagtail/wagtailsites/templates/wagtailsites/edit.html +++ b/wagtail/wagtailsites/templates/wagtailsites/edit.html @@ -15,7 +15,11 @@ {% include "wagtailadmin/shared/field_as_li.html" with field=form.port %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %} -
    • {% trans "Delete site" %}
    • +
    • + {% if perms.site.delete_site %} + {% trans "Delete site" %} + {% endif %} +
    diff --git a/wagtail/wagtailsites/templates/wagtailsites/index.html b/wagtail/wagtailsites/templates/wagtailsites/index.html index 6ced6a2c4..af2cc9b52 100644 --- a/wagtail/wagtailsites/templates/wagtailsites/index.html +++ b/wagtail/wagtailsites/templates/wagtailsites/index.html @@ -3,8 +3,12 @@ {% block titletag %}{% trans "Sites" %}{% endblock %} {% block content %} {% trans "Sites" as sites_str %} - {% trans "Add a site" as add_a_site_str %} - {% include "wagtailadmin/shared/header.html" with title=sites_str add_link="wagtailsites_create" add_text=add_a_site_str icon="radio-empty" %} + {% if perms.site.add_site %} + {% trans "Add a site" as add_a_site_str %} + {% include "wagtailadmin/shared/header.html" with title=sites_str add_link="wagtailsites_create" add_text=add_a_site_str icon="radio-empty" %} + {% else %} + {% include "wagtailadmin/shared/header.html" with title=sites_str icon="radio-empty" %} + {% endif %}
    diff --git a/wagtail/wagtailsites/views.py b/wagtail/wagtailsites/views.py index 9ca449f39..b7d8b988d 100644 --- a/wagtail/wagtailsites/views.py +++ b/wagtail/wagtailsites/views.py @@ -1,13 +1,20 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import permission_required, user_passes_test from django.utils.translation import ugettext as _ from wagtail.wagtailcore.models import Site from wagtail.wagtailsites.forms import SiteForm -@permission_required('site.change_site') +def user_has_site_model_perm(user): + for verb in ['add', 'change', 'delete']: + if user.has_perm('site.%s_site' % verb): + return True + return False + + +@user_passes_test(user_has_site_model_perm) def index(request): sites = Site.objects.all() return render(request, 'wagtailsites/index.html', { From 6d8344d7efb8ae809244d3c7dfc63e91ea701efa Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 11 Jul 2014 09:24:50 +0100 Subject: [PATCH 122/155] Add wagtailsites tests Conflicts: runtests.py --- wagtail/tests/settings.py | 1 + wagtail/wagtailsites/tests.py | 254 ++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 wagtail/wagtailsites/tests.py diff --git a/wagtail/tests/settings.py b/wagtail/tests/settings.py index e9c1172b4..713d3c45d 100644 --- a/wagtail/tests/settings.py +++ b/wagtail/tests/settings.py @@ -65,6 +65,7 @@ INSTALLED_APPS = [ 'wagtail.wagtaildocs', 'wagtail.wagtailsnippets', 'wagtail.wagtailusers', + 'wagtail.wagtailsites', 'wagtail.wagtailimages', 'wagtail.wagtailembeds', 'wagtail.wagtailsearch', diff --git a/wagtail/wagtailsites/tests.py b/wagtail/wagtailsites/tests.py new file mode 100644 index 000000000..aa061c125 --- /dev/null +++ b/wagtail/wagtailsites/tests.py @@ -0,0 +1,254 @@ +from __future__ import unicode_literals +from django.test import TestCase +from django.core.urlresolvers import reverse + +from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailcore.models import Site, Page +import six + + +class TestSiteIndexView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.home_page = Page.objects.get(id=2) + + def get(self, params={}): + return self.client.get(reverse('wagtailsites_index'), params) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsites/index.html') + + def test_pagination(self): + pages = ['0', '1', '-1', '9999', 'Not a page'] + for page in pages: + response = self.get({'p': page}) + self.assertEqual(response.status_code, 200) + + +class TestSiteCreateView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.home_page = Page.objects.get(id=2) + self.localhost = Site.objects.all()[0] + + def get(self, params={}): + return self.client.get(reverse('wagtailsites_create'), params) + + def post(self, post_data={}): + return self.client.post(reverse('wagtailsites_create'), post_data) + + def create_site(self, hostname='testsite', port=80, is_default_site=False, root_page=None): + root_page = root_page or self.home_page + Site.objects.create( + hostname=hostname, + port=port, + is_default_site=is_default_site, + root_page=root_page) + + def test_default_fixtures_present(self): + # we should have loaded with a single site + self.assertEqual(self.localhost.hostname, 'localhost') + self.assertEqual(self.localhost.port, 80) + self.assertEqual(self.localhost.is_default_site, True) + self.assertEqual(self.localhost.root_page, self.home_page) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsites/create.html') + + def test_create(self): + response = self.post({ + 'hostname': "testsite", + 'port': "80", + 'root_page': str(self.home_page.id), + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + + # Check that the site was created + self.assertEqual(Site.objects.filter(hostname='testsite').count(), 1) + + def test_duplicate_defaults_not_allowed(self): + response = self.post({ + 'hostname': "also_default", + 'port': "80", + 'is_default_site': "on", + 'root_page': str(self.home_page.id), + }) + + # Should return the form with errors + self.assertEqual(response.status_code, 200) + self.assertEqual(bool(response.context['form'].errors), True) + + # Check that the site was not created + sites = Site.objects.filter(hostname='also_default') + self.assertEqual(sites.count(), 0) + + def test_duplicate_hostnames_on_different_ports_allowed(self): + response = self.post({ + 'hostname': "localhost", + 'port': "8000", + 'root_page': str(self.home_page.id), + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + + # Check that the site was created + self.assertEqual(Site.objects.filter(hostname='localhost').count(), 2) + + def test_duplicate_hostnames_on_same_port_not_allowed(self): + # Confirm there's one localhost already + self.assertEqual(Site.objects.filter(hostname='localhost').count(), 1) + + response = self.post({ + 'hostname': "localhost", + 'port': "80", + 'root_page': str(self.home_page.id), + }) + + # Should return the form with errors + self.assertEqual(response.status_code, 200) + self.assertEqual(bool(response.context['form'].errors), True) + + # Check that the site was not created, still only one localhost entry + self.assertEqual(Site.objects.filter(hostname='localhost').count(), 1) + + +class TestSiteEditView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.home_page = Page.objects.get(id=2) + self.localhost = Site.objects.all()[0] + + def get(self, params={}, site_id=None): + return self.client.get(reverse('wagtailsites_edit', args=(site_id or self.localhost.id, )), params) + + def post(self, post_data={}, site_id=None): + site_id = site_id or self.localhost.id + site = Site.objects.get(id=site_id) + post_defaults = { + 'hostname': site.hostname, + 'port': site.port, + 'root_page': site.root_page.id, + } + for k, v in six.iteritems(post_defaults): + post_data[k] = post_data.get(k, v) + if 'default' in post_data: + if post_data['default']: # only include the is_default_site key if we want to set it + post_data['is_default_site'] = 'on' + elif site.is_default_site: + post_data['is_default_site'] = 'on' + return self.client.post(reverse('wagtailsites_edit', args=(site_id,)), post_data) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsites/edit.html') + + def test_nonexistant_redirect(self): + self.assertEqual(self.get(site_id=100000).status_code, 404) + + def test_edit(self): + edited_hostname = 'edited' + response = self.post({ + 'hostname': edited_hostname, + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + + # Check that the site was edited + self.assertEqual(Site.objects.get(id=self.localhost.id).hostname, edited_hostname) + + def test_changing_the_default_site_workflow(self): + # First create a second, non-default, site + second_site = Site.objects.create( + hostname="not_yet_default", + port=80, + is_default_site=False, + root_page=self.home_page) + + # Make the original default no longer default + response = self.post( + { + 'default': False, + }, + site_id=self.localhost.id + ) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + # Check that the site is no longer default + self.assertEqual(Site.objects.get(id=self.localhost.id).is_default_site, False) + + # Now make the second site default + response = self.post( + { + 'default': True, + }, + site_id=second_site.id + ) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + # Check that the second site is now set as default + self.assertEqual(Site.objects.get(id=second_site.id).is_default_site, True) + + def test_making_a_second_site_the_default_not_allowed(self): + second_site = Site.objects.create( + hostname="also_default", + port=80, + is_default_site=False, + root_page=self.home_page) + response = self.post( + { + 'default': True, + }, + site_id=second_site.id + ) + + # Should return the form with errors + self.assertEqual(response.status_code, 200) + self.assertEqual(bool(response.context['form'].errors), True) + + # Check that the site was not editd + + self.assertEqual(Site.objects.get(id=second_site.id).is_default_site, False) + + +class TestSiteDeleteView(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.home_page = Page.objects.get(id=2) + self.localhost = Site.objects.all()[0] + + def get(self, params={}, site_id=None): + return self.client.get(reverse('wagtailsites_delete', args=(site_id or self.localhost.id, )), params) + + def post(self, post_data={}, site_id=None): + return self.client.post(reverse('wagtailsites_delete', args=(site_id or self.localhost.id, )), post_data) + + def test_simple(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsites/confirm_delete.html') + + def test_nonexistant_redirect(self): + self.assertEqual(self.get(site_id=100000).status_code, 404) + + def test_posting_deletes_site(self): + response = self.post({ + 'trivial_key': 'trivial_value' + }) + + # Should redirect back to index + self.assertRedirects(response, reverse('wagtailsites_index')) + + # Check that the site was edited + with self.assertRaises(Site.DoesNotExist): + Site.objects.get(id=self.localhost.id) From a5fbb74782de153c7e2f95b9dd9f70595bcbc27c Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 11 Jul 2014 10:30:45 +0100 Subject: [PATCH 123/155] Add page chooser panel for site form root page field --- wagtail/wagtailsites/forms.py | 4 ++ .../templates/wagtailsites/create.html | 17 ++++++- .../templates/wagtailsites/edit.html | 17 ++++++- .../wagtailsites/includes/site_form.html | 49 +++++++++++++++++++ .../wagtailsites/includes/site_form.js | 5 ++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html create mode 100644 wagtail/wagtailsites/templates/wagtailsites/includes/site_form.js diff --git a/wagtail/wagtailsites/forms.py b/wagtail/wagtailsites/forms.py index 1ff227a10..cb133f70c 100644 --- a/wagtail/wagtailsites/forms.py +++ b/wagtail/wagtailsites/forms.py @@ -4,6 +4,10 @@ from wagtail.wagtailcore.models import Site class SiteForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super(SiteForm, self).__init__(*args, **kwargs) + self.fields['root_page'].widget = forms.HiddenInput() + required_css_class = "required" class Meta: diff --git a/wagtail/wagtailsites/templates/wagtailsites/create.html b/wagtail/wagtailsites/templates/wagtailsites/create.html index c5c8be005..f7a0d1a94 100644 --- a/wagtail/wagtailsites/templates/wagtailsites/create.html +++ b/wagtail/wagtailsites/templates/wagtailsites/create.html @@ -13,10 +13,25 @@
      {% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.port %} - {% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %} + + {% trans "Choose a different root page" as choose_another_text_str %} + {% trans "Choose a root page" as choose_one_text_str %} + {% if form.instance.root_page %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} + {% else %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} + {% endif %} + {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
    {% endblock %} +{% block extra_js %} + {% include "wagtailadmin/pages/_editor_js.html" %} + + +{% endblock %} diff --git a/wagtail/wagtailsites/templates/wagtailsites/edit.html b/wagtail/wagtailsites/templates/wagtailsites/edit.html index 50350cd70..ade3b3c74 100644 --- a/wagtail/wagtailsites/templates/wagtailsites/edit.html +++ b/wagtail/wagtailsites/templates/wagtailsites/edit.html @@ -13,7 +13,15 @@
      {% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.port %} - {% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %} + + {% trans "Choose a different root page" as choose_another_text_str %} + {% trans "Choose a root page" as choose_one_text_str %} + {% if form.instance.root_page %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} + {% else %} + {% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %} + {% endif %} + {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
    • {% if perms.site.delete_site %} @@ -24,3 +32,10 @@
    {% endblock %} +{% block extra_js %} + {% include "wagtailadmin/pages/_editor_js.html" %} + + +{% endblock %} diff --git a/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html b/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html new file mode 100644 index 000000000..7c3309307 --- /dev/null +++ b/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html @@ -0,0 +1,49 @@ ++{% load i18n %} ++{{ formset.management_form }} ++
      ++ {% for form in formset.forms %} ++ {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=fo ++ {% endfor %} ++
    ++ ++ {% endblock %} From ba10c7b45caeea5744cddc41dd1fd440b4b5f70f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 1 Oct 2014 09:48:30 +0100 Subject: [PATCH 129/155] remove unused and corrupted site_form.html --- .../wagtailsites/includes/site_form.html | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html diff --git a/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html b/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html deleted file mode 100644 index 7c3309307..000000000 --- a/wagtail/wagtailsites/templates/wagtailsites/includes/site_form.html +++ /dev/null @@ -1,49 +0,0 @@ -+{% load i18n %} -+{{ formset.management_form }} -+
      -+ {% for form in formset.forms %} -+ {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=fo -+ {% endfor %} -+
    -+ -+