Skip to content

Commit

Permalink
Merge pull request #3869 from yakky/feature/structure_mode_permission
Browse files Browse the repository at this point in the history
Structure mode permission
  • Loading branch information
yakky committed Feb 21, 2015
2 parents d423a0b + b620d7b commit 19b5bad
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 16 deletions.
9 changes: 6 additions & 3 deletions cms/cms_toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,12 @@ def add_structure_mode_item(self, extra_classes=('cms_toolbar-item-cms-mode-swit
build_mode = self.toolbar.build_mode
build_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__BUILD')
edit_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
switcher = self.toolbar.add_button_list('Mode Switcher', side=self.toolbar.RIGHT, extra_classes=extra_classes)
switcher.add_button(_('Structure'), build_url, active=build_mode, disabled=not build_mode)
switcher.add_button(_('Content'), edit_url, active=not build_mode, disabled=build_mode)

if self.request.user.has_perm("cms.use_structure"):
switcher = self.toolbar.add_button_list('Mode Switcher', side=self.toolbar.RIGHT,
extra_classes=extra_classes)
switcher.add_button(_('Structure'), build_url, active=build_mode, disabled=not build_mode)
switcher.add_button(_('Content'), edit_url, active=not build_mode, disabled=build_mode)


@toolbar_pool.register
Expand Down
50 changes: 50 additions & 0 deletions cms/migrations/0010_migrate_use_structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType

from django.db import models, migrations


def forwards(apps, schema_editor):
ph_model = apps.get_model('cms', 'Placeholder')
page_model = apps.get_model('cms', 'Page')
try:
ph_ctype = ContentType.objects.get(app_label=ph_model._meta.app_label, model=ph_model._meta.model_name)
page_ctype = ContentType.objects.get(app_label=page_model._meta.app_label, model=page_model._meta.model_name)
permission, _ = Permission.objects.get_or_create(
codename='use_structure', content_type=ph_ctype, name=u"Can use Structure mode")
page_permission = Permission.objects.get(codename='change_page', content_type=page_ctype)
for user in get_user_model().objects.filter(is_superuser=False, is_staff=True):
if user.has_perm("cms.change_page"):
user.user_permissions.add(permission)
for group in Group.objects.all():
if page_permission in group.permissions.all():
group.permissions.add(permission)
except ContentType.DoesNotExist:
print(u'Cannot migrate users to use_structure permission, please add the permission manually')


def backwards(apps, schema_editor):
ph_model = apps.get_model('cms', 'Placeholder')
ph_ctype = ContentType.objects.get(app_label=ph_model._meta.app_label, model=ph_model._meta.model_name)
permission, _ = Permission.objects.get_or_create(
codename='use_structure', content_type=ph_ctype, name=u"Can use Structure mode")
for user in get_user_model().objects.filter(is_superuser=False, is_staff=True):
if user.has_perm("cms.use_structure"):
user.user_permissions.remove(permission)
for group in Group.objects.all():
if permission in group.permissions.all():
group.permissions.remove(permission)


class Migration(migrations.Migration):

dependencies = [
('cms', '0008_auto_20150208_2149'),
]

operations = [
migrations.RunPython(forwards, backwards)
]
3 changes: 3 additions & 0 deletions cms/models/placeholdermodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Placeholder(models.Model):

class Meta:
app_label = 'cms'
permissions = (
(u"use_structure", u"Can use Structure mode"),
)

def __str__(self):
return self.slot
Expand Down
232 changes: 232 additions & 0 deletions cms/south_migrations/0075_use_structure.py

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions cms/tests/apphooks.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import sys
from django.contrib.auth.models import Permission

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.core.urlresolvers import clear_url_caches, reverse
from django.test.utils import override_settings
from django.utils import six
Expand Down Expand Up @@ -562,7 +562,6 @@ def test_toolbar_multiple_supported_apps(self):
ROOT_URLCONF='cms.test_utils.project.placeholderapp_urls',
)
def test_toolbar_staff(self):
# Test that the toolbar contains edito mode switcher if placeholders are available
self.create_base_structure('Example1App', 'en')
ex1 = Example1.objects.create(char_1='1', char_2='2', char_3='3', char_4='4', date_field=now())
path = reverse('example_detail', kwargs={'pk': ex1.pk})
Expand All @@ -572,6 +571,7 @@ def test_toolbar_staff(self):
response = self.client.get(path+"?edit")
toolbar = CMSToolbar(response.context['request'])
toolbar.populate()
response.context['request'].user = self.user
placeholder_toolbar = PlaceholderToolbar(response.context['request'], toolbar, True, path)
placeholder_toolbar.populate()
placeholder_toolbar.init_placeholders_from_request()
Expand All @@ -596,6 +596,17 @@ def test_toolbar_staff(self):
response.context['request'].user = self.user
toolbar = CMSToolbar(response.context['request'])
toolbar.populate()
response.context['request'].user = self.user
placeholder_toolbar = PlaceholderToolbar(response.context['request'], toolbar, True, path)
placeholder_toolbar.populate()
placeholder_toolbar.init_placeholders_from_request()
placeholder_toolbar.add_structure_mode()
self.assertEqual(len(placeholder_toolbar.toolbar.get_right_items()), 0)

permission = Permission.objects.get(codename='use_structure')
self.user.user_permissions.add(permission)

response.context['request'].user = get_user_model().objects.get(pk=self.user.pk)
placeholder_toolbar = PlaceholderToolbar(response.context['request'], toolbar, True, path)
placeholder_toolbar.populate()
placeholder_toolbar.init_placeholders_from_request()
Expand All @@ -605,8 +616,8 @@ def test_toolbar_staff(self):
self.user = None


@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
class ApphooksPageLanguageUrlTestCase(CMSTestCase):
settings_overrides = {'ROOT_URLCONF': 'cms.test_utils.project.second_urls_for_apphook_tests'}

def setUp(self):
clear_app_resolvers()
Expand Down
25 changes: 20 additions & 5 deletions cms/tests/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django.core.urlresolvers import reverse

from cms.api import create_page, create_title, add_plugin
from cms.cms_toolbar import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK
from cms.cms_toolbar import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK, get_user_model
from cms.middleware.toolbar import ToolbarMiddleware
from cms.models import Page, UserSettings, PagePermission
from cms.toolbar.items import (ToolbarAPIMixin, LinkItem, ItemSearchResult,
Expand Down Expand Up @@ -233,15 +233,28 @@ def test_publish_button(self):

def test_no_publish_button(self):
page = create_page('test', 'nav_playground.html', 'en', published=True)
request = self.get_page_request(page, self.get_staff(), edit=True)
user = self.get_staff()
request = self.get_page_request(page, user, edit=True)
toolbar = CMSToolbar(request)
toolbar.populate()
toolbar.post_template_populate()
self.assertTrue(page.has_change_permission(request))
self.assertFalse(page.has_publish_permission(request))
self.assertTrue(toolbar.edit_mode)
items = toolbar.get_left_items() + toolbar.get_right_items()
# Logo + edit-mode + templates + page-menu + admin-menu + logout
# Logo + templates + page-menu + admin-menu + logout
self.assertEqual(len(items), 5)

# adding back structure mode permission
permission = Permission.objects.get(codename='use_structure')
user.user_permissions.add(permission)

request.user = get_user_model().objects.get(pk=user.pk)
toolbar = CMSToolbar(request)
toolbar.populate()
toolbar.post_template_populate()
items = toolbar.get_left_items() + toolbar.get_right_items()
# Logo + edit mode + templates + page-menu + admin-menu + logout
self.assertEqual(len(items), 6)

def test_no_change_button(self):
Expand Down Expand Up @@ -273,12 +286,14 @@ def test_button_consistency_staff(self):
en_toolbar = CMSToolbar(en_request)
en_toolbar.populate()
en_toolbar.post_template_populate()
self.assertEqual(len(en_toolbar.get_left_items() + en_toolbar.get_right_items()), 6)
# Logo + templates + page-menu + admin-menu + logout
self.assertEqual(len(en_toolbar.get_left_items() + en_toolbar.get_right_items()), 5)
de_request = self.get_page_request(cms_page, user, path='/de/', edit=True, lang_code='de')
de_toolbar = CMSToolbar(de_request)
de_toolbar.populate()
de_toolbar.post_template_populate()
self.assertEqual(len(de_toolbar.get_left_items() + de_toolbar.get_right_items()), 6)
# Logo + templates + page-menu + admin-menu + logout
self.assertEqual(len(de_toolbar.get_left_items() + de_toolbar.get_right_items()), 5)

@override_settings(CMS_PLACEHOLDER_CONF={'col_left': {'name': 'PPPP'}})
def test_placeholder_name(self):
Expand Down
28 changes: 24 additions & 4 deletions docs/topics/permissions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
Permissions
###########

In django CMS you can set two types of permissions:
In django CMS you can set three types of permissions:

1. View restrictions for restricting front-end view access to users
2. Page permissions for allowing staff users to only have rights on certain sections of certain sites
1. Page permissions for allowing staff users to only have rights on certain sections of certain sites
1. Mode permission which when left unset, restricts staff users to only editing, not adding new content

To enable these features, ``settings.py`` requires:
To enable features 1. and 2., ``settings.py`` requires:

CMS_PERMISSION = True

The third one is controlled by the "**Can use Structure mode**" Django permission.

*****************
View restrictions
*****************
Expand Down Expand Up @@ -77,9 +80,26 @@ Using the *Pages global permissions* model you can give a set of permissions to

.. note:: You always **must** set the sites managed py the global permissions, even if you only have one site.

.. _structure_mode_permissions:

********************
Edit mode permission
********************

.. versionchanged:: 3.1

django CMS uses **Structure** and **Content** modes for different type of content editing;
while the former allows full control over the plugins layout, positioning and to add new
plugins to the page, the latter only allow editing existing plugins.

From version 3.1 the specific permission "**Can use Structure mode**" exists to permit access
to Structure Mode. This allows defining a different level of permissions on the same content.

This permission also applies to ``PlaceholderField`` defined on models.

****************
File Permissions
================
****************

django CMS does not take care of and no responsibility for controlling access to files. Please make sure to use either
a prebuilt solution (like `django-filer <https://github.com/stefanfoulis/django-filer>`_) or to roll your own.
16 changes: 15 additions & 1 deletion docs/upgrade/3.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ the tree got corrupted because of transactional errors.
In 3.1 we replaced MPTT for MP (Materialized Path). MP is more efficient and has more error resistance then MPTT.
It should make the whole django CMS experience way better, faster and reliable.

Bofore upgrading
Before upgrading
----------------

Be sure to run ``manage.py cms fix-mptt`` before you upgrade.
Expand All @@ -44,3 +44,17 @@ Migrations packages has been renamed to the standard layout:

South 1.0.2 is now required to correctly handle the above layout; if you are upgrading from Django 1.7 / django CMS 3.0.x,
please remove the old migration path from ``MIGRATION_MODULES`` settings.

Structure mode permission
=========================

The new "**Can use Structure mode**" permission has been added. Without this permission, a
non-superuser cannot enter structure mode anymore: this allow more strict workflow which only
enable content update for certain users.
This change include data migration
that adds the new permission to any staff user or group with
`cms.change_page` permission; you can then later change this according to your permission
model and workflow.
Be aware that existing users cannot be migrated if running the django CMS
migrations for the first time in the project, because permissions are not already in place;
in this case users must be given the new permission manually.

0 comments on commit 19b5bad

Please sign in to comment.