Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'hotfix/2.1.3' into develop

Conflicts:
	cms/test_utils/testcases.py
	cms/tests/menu.py
	cms/tests/multilingual.py
	cms/tests/placeholder.py
	cms/tests/plugins.py
	cms/tests/toolbar.py
  • Loading branch information...
commit 7b2adb30ce2cb8a3106e82b4bb34eda92293dbae 2 parents fb4b976 + 9d61907
@ojii ojii authored
View
1  AUTHORS
@@ -57,6 +57,7 @@ Contributors (in alphabetical order):
* Gerard Świderski
* homebrew79
* hysia
+* Iacopo Spalletti
* Ian Lewis
* indexofire
* Ionel Cristian Maries
View
9 CHANGELOG.txt
@@ -43,4 +43,11 @@
==== 2.1.2 (2011-02-16) ====
- Fixed issues with the CSRF fix from 2.1.1.
-- Updated translation files from transifex.
+- Updated translation files from transifex.
+
+==== 2.1.3 (2011-02-22) ====
+
+- Fixed a serious security issue in PlaceholderAdmin
+- Fixed bug with submenus showing pages that are not 'in_navigation' (#716, thanks to Iacopo Spalletti for the patch)
+- Fixed PlaceholderField not respecting limits in CMS_PLACEHOLDER_CONF (thanks to Ben Hockey for reporting this)
+- Fixed the double-monkeypatch check for url reversing (thanks to Benjamin Wohlwend for the patch)
View
2  cms/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-VERSION = (2, 1, 2, 'final')
+VERSION = (2, 1, 3, 'final')
if VERSION[-1] != "final": # pragma: no cover
__version__ = '.'.join(map(str, VERSION))
else: # pragma: no cover
View
19 cms/admin/pageadmin.py
@@ -1085,6 +1085,9 @@ def change_innavigation(self, request, page_id):
@create_on_success
def add_plugin(self, request):
+ '''
+ Could be either a page or a parent - if it's a parent we get the page via parent.
+ '''
if 'history' in request.path or 'recover' in request.path:
return HttpResponse(str("error"))
if request.method == "POST":
@@ -1119,14 +1122,20 @@ def add_plugin(self, request):
parent = get_object_or_404(CMSPlugin, pk=parent_id)
placeholder = parent.placeholder
page = get_page_from_placeholder_if_exists(placeholder)
+ if not page: # Make sure we do have a page
+ raise Http404
language = parent.language
position = None
# placeholder (non-page) add-plugin
else:
- position = None
- language = request.POST['language'] or get_language_from_request(request)
- if page and not page.has_change_permission(request):
- return HttpResponseForbidden(unicode(_("You do not have permission to change this page")))
+ # do NOT allow non-page placeholders to use this method, they
+ # should use their respective admin!
+ raise Http404
+
+ if not page.has_change_permission(request):
+ # we raise a 404 instead of 403 for a slightly improved security
+ # and to be consistent with placeholder admin
+ raise Http404
# Sanity check to make sure we're not getting bogus values from JavaScript:
if not language or not language in [ l[0] for l in settings.LANGUAGES ]:
@@ -1186,7 +1195,7 @@ def edit_plugin(self, request, plugin_id):
page = get_page_from_placeholder_if_exists(cms_plugin.placeholder)
instance, plugin_admin = cms_plugin.get_plugin_instance(self.admin_site)
if page and not page.has_change_permission(request):
- raise PermissionDenied
+ raise Http404
else:
# history view with reversion
from reversion.models import Version
View
137 cms/admin/placeholderadmin.py
@@ -122,29 +122,66 @@ def get_urls(self):
return url_patterns + super(PlaceholderAdmin, self).get_urls()
def add_plugin(self, request):
+ # only allow POST
if request.method != "POST":
raise Http404
plugin_type = request.POST['plugin_type']
placeholder_id = request.POST.get('placeholder', None)
position = None
language = get_language_from_request(request)
- if not placeholder_id:
+ parent = None
+ # check if we got a placeholder (id)
+ if placeholder_id:
+ placeholder = get_object_or_404(Placeholder, pk=placeholder_id)
+ else: # else get the parent_id
parent_id = request.POST.get('parent_id', None)
- if not parent_id:
+ if not parent_id: # if we get neither a placeholder nor a parent, bail out
raise Http404
parent = get_object_or_404(CMSPlugin, pk=parent_id)
- plugin = CMSPlugin(language=language, plugin_type=plugin_type,
- position=position, parent=parent, placeholder=parent.placeholder)
- else:
- placeholder = get_object_or_404(Placeholder, pk=placeholder_id)
- plugin = CMSPlugin(language=language, plugin_type=plugin_type,
- position=position, placeholder=placeholder)
+ placeholder = parent.placeholder
+
+ # check add permissions on placeholder
+ if not placeholder.has_add_permission(request):
+ raise Http404
+
+ # check the limits defined in CMS_PLACEHOLDER_CONF for this placeholder
+ limits = settings.CMS_PLACEHOLDER_CONF.get(placeholder.slot, {}).get('limits', None)
+ if limits:
+ count = placeholder.cmsplugin_set.count()
+ global_limit = limits.get("global", None)
+ type_limit = limits.get(plugin_type, None)
+ # check the global limit first
+ if global_limit and count >= global_limit:
+ return HttpResponseBadRequest(
+ "This placeholder already has the maximum number of plugins."
+ )
+ elif type_limit: # then check the type specific limit
+ type_count = CMSPlugin.objects.filter(
+ language=language, placeholder=placeholder, plugin_type=plugin_type
+ ).count()
+ if type_count >= type_limit:
+ return HttpResponseBadRequest(
+ "This placeholder already has the maximum number (%s) "
+ "of %s plugins." % (type_limit, plugin_type)
+ )
+
+ # actually add the plugin
+ plugin = CMSPlugin(language=language, plugin_type=plugin_type,
+ position=position, placeholder=placeholder, parent=parent)
plugin.save()
+
+ # returns it's ID as response
return HttpResponse(str(plugin.pk))
def edit_plugin(self, request, plugin_id):
plugin_id = int(plugin_id)
+ # get the plugin to edit of bail out
cms_plugin = get_object_or_404(CMSPlugin, pk=plugin_id)
+
+ # check that the user has permission to change this plugin
+ if not cms_plugin.placeholder.has_change_permission(request):
+ raise Http404
+
instance, plugin_admin = cms_plugin.get_plugin_instance(self.admin_site)
plugin_admin.cms_plugin_instance = cms_plugin
@@ -185,46 +222,72 @@ def edit_plugin(self, request, plugin_id):
return response
def move_plugin(self, request):
- if request.method == "POST":
- pos = 0
- if 'ids' in request.POST:
- for id in request.POST['ids'].split("_"):
- plugin = CMSPlugin.objects.get(pk=id)
- if plugin.position != pos:
- plugin.position = pos
- plugin.save()
- pos += 1
- elif 'plugin_id' in request.POST:
- plugin = CMSPlugin.objects.get(pk=int(request.POST['plugin_id']))
- placeholder = plugin.placeholder
- # plugin positions are 0 based, so just using count here should give us 'last_position + 1'
- position = CMSPlugin.objects.filter(placeholder=placeholder).count()
- plugin.position = position
- plugin.save()
- else:
- HttpResponse(str("error"))
- return HttpResponse(str("ok"))
- else:
+ # only allow POST
+ if request.method != "POST":
return HttpResponse(str("error"))
+ pos = 0
+ if 'ids' in request.POST: # multiple plugins
+ whitelisted_placeholders = []
+ for id in request.POST['ids'].split("_"):
+ plugin = CMSPlugin.objects.get(pk=id)
+
+ # check the permissions for *each* plugin, but cache them locally
+ # per placeholder
+ if plugin.placeholder.pk not in whitelisted_placeholders:
+ if plugin.placeholder.has_change_permission(request):
+ whitelisted_placeholders.append(plugin.placeholder.pk)
+ else:
+ raise Http404
+
+ # actually do the moving
+ if plugin.position != pos:
+ plugin.position = pos
+ plugin.save()
+ pos += 1
+ elif 'plugin_id' in request.POST: # single plugin moving
+ plugin = CMSPlugin.objects.get(pk=int(request.POST['plugin_id']))
- def remove_plugin(self, request):
- if request.method == "POST":
- plugin_id = request.POST['plugin_id']
- plugin = get_object_or_404(CMSPlugin, pk=plugin_id)
+ # check permissions
+ if not plugin.placeholder.has_change_permission(request):
+ raise Http404
+
placeholder = plugin.placeholder
- plugin.delete_with_public()
- plugin_name = unicode(plugin_pool.get_plugin(plugin.plugin_type).name)
- comment = _(u"%(plugin_name)s plugin at position %(position)s in %(placeholder)s was deleted.") % {'plugin_name':plugin_name, 'position':plugin.position, 'placeholder':plugin.placeholder}
- return HttpResponse("%s,%s" % (plugin_id, comment))
- raise Http404
+ # plugin positions are 0 based, so just using count here should give us 'last_position + 1'
+ position = CMSPlugin.objects.filter(placeholder=placeholder).count()
+ plugin.position = position
+ plugin.save()
+ else:
+ HttpResponse(str("error"))
+ return HttpResponse(str("ok"))
+
+ def remove_plugin(self, request):
+ if request.method != "POST": # only allow POST
+ raise Http404
+ plugin_id = request.POST['plugin_id']
+ plugin = get_object_or_404(CMSPlugin, pk=plugin_id)
+
+ # check the permissions!
+ if not plugin.placeholder.has_delete_permission(request):
+ raise Http404
+
+ plugin.delete_with_public()
+ plugin_name = unicode(plugin_pool.get_plugin(plugin.plugin_type).name)
+ comment = _(u"%(plugin_name)s plugin at position %(position)s in %(placeholder)s was deleted.") % {'plugin_name':plugin_name, 'position':plugin.position, 'placeholder':plugin.placeholder}
+ return HttpResponse("%s,%s" % (plugin_id, comment))
def copy_plugins(self, request):
+ # only allow POST
if request.method != "POST":
raise Http404
placeholder_id = request.POST['placeholder']
placeholder = get_object_or_404(Placeholder, pk=placeholder_id)
+ # check permissions
+ if not placeholder.has_add_permission(request):
+ raise Http404
+ # the placeholder actions are responsible for copying, they should return
+ # a list of plugins if successful.
plugins = placeholder.actions.copy(
target_placeholder=placeholder,
source_language=request.POST['copy_from'],
View
11 cms/models/__init__.py
@@ -37,6 +37,8 @@ def remove_current_root(url):
return url
def monkeypatch_reverse():
+ if hasattr(django.core.urlresolvers.reverse, 'cms_monkeypatched'):
+ return
django.core.urlresolvers.old_reverse = django.core.urlresolvers.reverse
def new_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
@@ -62,13 +64,10 @@ def new_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, cur
raise e
url = remove_current_root(url)
return url
-
+
+ new_reverse.cms_monkeypatched = True
django.core.urlresolvers.reverse = new_reverse
validate_dependencies()
validate_settings()
-
-monkeypatched = False
-if not monkeypatched:
- monkeypatch_reverse()
- monkeypatched = True
+monkeypatch_reverse()
View
54 cms/models/placeholdermodel.py
@@ -11,17 +11,40 @@ class Placeholder(models.Model):
slot = models.CharField(_("slot"), max_length=50, db_index=True, editable=False)
default_width = models.PositiveSmallIntegerField(_("width"), null=True, editable=False)
- def __unicode__(self):
- return self.slot
-
class Meta:
app_label = 'cms'
- def has_change_permission(self, request):
- opts = self._meta
+ def __unicode__(self):
+ return self.slot
+
+ def _get_permission(self, request, key):
+ """
+ Generic method to check the permissions for a request for a given key,
+ the key can be: 'add', 'change' or 'delete'.
+ """
if request.user.is_superuser:
return True
- return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
+ found = False
+ # check all attached models for change permissions
+ for model in self._get_attached_models():
+ opts = model._meta
+ perm_accessor = getattr(opts, 'get_%s_permission' % key)
+ perm_code = '%s.%s' % (opts.app_label, perm_accessor())
+ # if they don't have the permission for this attached model, bail out
+ if not request.user.has_perm(perm_code):
+ return False
+ else:
+ found = True
+ return found
+
+ def has_change_permission(self, request):
+ return self._get_permission(request, 'change')
+
+ def has_add_permission(self, request):
+ return self._get_permission(request, 'add')
+
+ def has_delete_permission(self, request):
+ return self._get_permission(request, 'delete')
def render(self, context, width):
from cms.plugin_rendering import render_placeholder
@@ -37,6 +60,18 @@ def get_media(self, request, context):
return reduce(operator.add, media_classes)
return Media()
+ def _get_attached_fields(self):
+ """
+ Returns an ITERATOR of all non-cmsplugin reverse foreign key related fields.
+ """
+ from cms.models import CMSPlugin
+ for rel in self._meta.get_all_related_objects():
+ if isinstance(rel.model, CMSPlugin):
+ continue
+ field = getattr(self, rel.get_accessor_name())
+ if field.count():
+ yield rel.field
+
def _get_attached_field(self):
from cms.models import CMSPlugin
if not hasattr(self, '_attached_field_cache'):
@@ -60,6 +95,13 @@ def _get_attached_model(self):
if field:
return field.model
return None
+
+ def _get_attached_models(self):
+ """
+ Returns a list of models of attached to this placeholder.
+ """
+ return [field.model for field in self._get_attached_fields()]
+
def get_plugins_list(self):
return list(self.get_plugins())
View
9 cms/test_utils/testcases.py
@@ -92,6 +92,15 @@ def get_superuser(self):
admin.set_password("admin")
admin.save()
return admin
+
+ def get_staff_user_with_no_permissions(self):
+ """
+ Used in security tests
+ """
+ staff = User(username="staff", is_staff=True, is_active=True)
+ staff.set_password("staff")
+ staff.save()
+ return staff
def get_new_page_data(self, parent_id=''):
page_data = {'title':'test page %d' % self.counter,
View
21 cms/tests/__init__.py
@@ -2,26 +2,27 @@
from cms.tests.admin import AdminTestCase
from cms.tests.apphooks import ApphooksTestCase
from cms.tests.docs import DocsTestCase
-from cms.tests.menu import FixturesMenuTests, MenuTests, AdvancedSoftrootTests
+from cms.tests.forms import FormsTestCase
+from cms.tests.mail import MailTestCase
+from cms.tests.middleware import MiddlewareTestCase
+from cms.tests.multilingual import MultilingualTestCase
+from cms.tests.menu import FixturesMenuTests, MenuTests, AdvancedSoftrootTests, ShowSubMenuCheck
from cms.tests.navextender import NavExtenderTestCase
from cms.tests.nonroot import NonRootCase
from cms.tests.page import PagesTestCase, NoAdminPageTests
from cms.tests.permmod import PermissionModeratorTestCase
-from cms.tests.placeholder import PlaceholderTestCase, PlaceholderActionTests
-from cms.tests.placeholder import PlaceholderModelTests
+from cms.tests.placeholder import (PlaceholderModelTests, PlaceholderAdminTest,
+ PlaceholderTestCase, PlaceholderActionTests)
from cms.tests.plugins import PluginManyToManyTestCase, PluginsTestCase
+from cms.tests.publisher import PublisherTestCase
from cms.tests.rendering import RenderingTestCase
from cms.tests.reversion_tests import ReversionTestCase
-from cms.tests.site import SiteTestCase
-from cms.tests.urlutils import UrlutilsTestCase
-from cms.tests.publisher import PublisherTestCase
-from cms.tests.multilingual import MultilingualTestCase
-from cms.tests.mail import MailTestCase
from cms.tests.settings import SettingsTests
-from cms.tests.forms import FormsTestCase
+from cms.tests.site import SiteTestCase
from cms.tests.toolbar import ToolbarTests
-from cms.tests.middleware import MiddlewareTestCase
+from cms.tests.urlutils import UrlutilsTestCase
from cms.tests.views import ViewTests
+from cms.tests.security import SecurityTests
try:
from cms.tests.javascript import JavascriptTestCase
except ImportError:
View
25 cms/tests/menu.py
@@ -717,3 +717,28 @@ def test_02_top_in_nav(self):
])
]
self.assertTreeQuality(soft_root, mock_tree, 'title', 'level')
+
+
+class ShowSubMenuCheck(BaseMenuTest):
+ """
+ Tree from fixture:
+
+ + P1
+ | + P2
+ | + P3
+ + P4
+ | + P5
+ + P6
+ + P7 (not in menu)
+ + P8
+ """
+ fixtures = ['menus-sub.json']
+
+ def test_01_show_submenu(self):
+ context = self.get_context('/test-page-6/')
+ # test standard show_menu
+ tpl = Template("{% load menu_tags %}{% show_sub_menu %}")
+ tpl.render(context)
+ nodes = context['children']
+ self.assertEqual(len(nodes), 1)
+ self.assertEqual(nodes[0].id, 8)
View
13 cms/tests/multilingual.py
@@ -62,3 +62,16 @@ def test_02_multilingual_page(self):
placeholder = public.placeholders.all()[0]
self.assertEqual(placeholder.cmsplugin_set.filter(language='de').count(), 1)
self.assertEqual(placeholder.cmsplugin_set.filter(language='en').count(), 1)
+
+ def test_03_multiple_reverse_monkeypatch(self):
+ """
+ This test is not very well behaved, every following
+ test that uses reverse will fail with a RuntimeException.
+ """
+ from cms.models import monkeypatch_reverse
+ monkeypatch_reverse()
+ monkeypatch_reverse()
+ try:
+ reverse('pages-root')
+ except RuntimeError:
+ self.fail('maximum recursion depth exceeded')
View
66 cms/tests/placeholder.py
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
+from __future__ import with_statement
from cms.exceptions import DuplicatePlaceholderWarning
from cms.models.placeholdermodel import Placeholder
from cms.test_utils.testcases import CMSTestCase
+from cms.test_utils.testcases import CMSTestCase
+from cms.test_utils.util.context_managers import SettingsOverride, UserLoginContext
from cms.utils.placeholder import PlaceholderNoAction, MLNGPlaceholderActions
from cms.utils.plugins import get_placeholders
from django.conf import settings
@@ -232,3 +235,66 @@ def test_05_excercise_get_attached_field_name(self):
ph = Placeholder.objects.create(slot='test', default_width=300)
result = ph._get_attached_field_name()
self.assertEqual(result, None) # Simple PH - no field name
+
+
+class PlaceholderAdminTest(CMSTestCase):
+ placeholderconf = {'test': {
+ 'limits': {
+ 'global': 2,
+ 'TextPlugin': 1,
+ }
+ }
+ }
+ def get_placeholder(self):
+ return Placeholder.objects.create(slot='test')
+
+ def get_admin(self):
+ admin.autodiscover()
+ return admin.site._registry[Example1]
+
+ def get_post_request(self, data):
+ request = self.get_request()
+ request.POST._mutable = True
+ request.POST.update(data)
+ request.POST._mutable = False
+ request.method = 'POST'
+ request.environ['METHOD'] = 'POST'
+ return request
+
+ def test_01_test_global_limit(self):
+ placeholder = self.get_placeholder()
+ admin = self.get_admin()
+ data = {
+ 'plugin_type': 'LinkPlugin',
+ 'placeholder': placeholder.pk,
+ 'language': 'en',
+ }
+ superuser = self.get_superuser()
+ with UserLoginContext(self, superuser):
+ with SettingsOverride(CMS_PLACEHOLDER_CONF=self.placeholderconf):
+ request = self.get_post_request(data)
+ response = admin.add_plugin(request) # first
+ self.assertEqual(response.status_code, 200)
+ response = admin.add_plugin(request) # second
+ self.assertEqual(response.status_code, 200)
+ response = admin.add_plugin(request) # third
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.content, "This placeholder already has the maximum number of plugins.")
+
+ def test_02_test_type_limit(self):
+ placeholder = self.get_placeholder()
+ admin = self.get_admin()
+ data = {
+ 'plugin_type': 'TextPlugin',
+ 'placeholder': placeholder.pk,
+ 'language': 'en',
+ }
+ superuser = self.get_superuser()
+ with UserLoginContext(self, superuser):
+ with SettingsOverride(CMS_PLACEHOLDER_CONF=self.placeholderconf):
+ request = self.get_post_request(data)
+ response = admin.add_plugin(request) # first
+ self.assertEqual(response.status_code, 200)
+ response = admin.add_plugin(request) # second
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.content, "This placeholder already has the maximum number (1) of TextPlugin plugins.")
View
25 cms/tests/plugins.py
@@ -9,22 +9,23 @@
from cms.plugins.googlemap.models import GoogleMap
from cms.plugins.inherit.models import InheritPagePlaceholder
from cms.plugins.text.models import Text
-from cms.plugins.text.utils import plugin_tags_to_id_list, \
- plugin_tags_to_admin_html
-from cms.test_utils.testcases import CMSTestCase, URL_CMS_PAGE, URL_CMS_PAGE_ADD, \
- URL_CMS_PLUGIN_ADD, URL_CMS_PLUGIN_EDIT, URL_CMS_PAGE_CHANGE, \
- URL_CMS_PLUGIN_REMOVE
from cms.test_utils.util.context_managers import SettingsOverride
-
+from cms.plugins.text.utils import (plugin_tags_to_id_list,
+ plugin_tags_to_admin_html)
+from cms.test_utils.testcases import (CMSTestCase, URL_CMS_PAGE, URL_CMS_PAGE_ADD,
+ URL_CMS_PLUGIN_ADD, URL_CMS_PLUGIN_EDIT, URL_CMS_PAGE_CHANGE,
+ URL_CMS_PLUGIN_REMOVE)
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.urlresolvers import reverse
from django.forms.widgets import Media
from django.template import RequestContext
from project.pluginapp.models import Article, Section
from project.pluginapp.plugins.manytomany_rel.models import ArticlePluginModel
import os
+
@@ -86,8 +87,10 @@ def get_request(self, *args, **kwargs):
request = super(PluginsTestBaseCase, self).get_request(*args, **kwargs)
request.placeholder_media = Media()
return request
-
+
+
class PluginsTestCase(PluginsTestBaseCase):
+
def test_01_add_edit_plugin(self):
"""
@@ -290,12 +293,8 @@ def test_05_remove_plugin_not_associated_to_page(self):
'parent': int(response.content)
}
response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
-
- plugin_data = {
- 'plugin_id': int(response.content)
- }
- response = response.client.post(URL_CMS_PLUGIN_REMOVE, plugin_data)
- self.assertEquals(response.status_code, 200)
+ # no longer allowed for security reasons
+ self.assertEqual(response.status_code, 404)
def test_07_register_plugin_twice_should_raise(self):
number_of_plugins_before = len(plugin_pool.get_all_plugins())
View
175 cms/tests/security.py
@@ -0,0 +1,175 @@
+from cms.models.pluginmodel import CMSPlugin
+from cms.plugins.text.models import Text
+from cms.test_utils.testcases import (CMSTestCase, URL_CMS_PLUGIN_ADD,
+ URL_CMS_PLUGIN_EDIT, URL_CMS_PLUGIN_REMOVE)
+from django.conf import settings
+from django.core.urlresolvers import reverse
+
+
+
+class SecurityTests(CMSTestCase):
+ """
+ Test security issues by trying some naive requests to add/alter/delete data.
+ """
+ def get_data(self):
+ page = self.create_page()
+ placeholder = page.placeholders.get(slot='body')
+ superuser = self.get_superuser()
+ staff = self.get_staff_user_with_no_permissions()
+ return page, placeholder, superuser, staff
+
+ def test_add(self):
+ """
+ Test adding a plugin to a *PAGE*.
+ """
+ page, placeholder, superuser, staff = self.get_data()
+ plugin_data = {
+ 'plugin_type':"TextPlugin",
+ 'language':settings.LANGUAGES[0][0],
+ 'placeholder':page.placeholders.get(slot="body").pk,
+ }
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+ # log the user out and post the plugin data to the cms add-plugin URL.
+ self.client.logout()
+ response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+
+ def test_edit(self):
+ """
+ Test editing a *PAGE* plugin
+ """
+ page, placeholder, superuser, staff = self.get_data()
+ # create the plugin using a superuser
+ plugin_id = self.add_plugin(superuser, page, placeholder, 'en', 'body')
+ plugin_data = {
+ 'plugin_id': plugin_id,
+ 'body': 'newbody',
+ }
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body') # check the body is as expected.
+ # log the user out, try to edit the plugin
+ self.client.logout()
+ url = URL_CMS_PLUGIN_EDIT + '%s/' % plugin.pk
+ response = self.client.post(url, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(url, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+
+ def test_delete(self):
+ """
+ Test deleting a *PAGE* plugin
+ """
+ page, placeholder, superuser, staff = self.get_data()
+ plugin_id = self.add_plugin(superuser, page, placeholder, 'en', 'body')
+ plugin_data = {
+ 'plugin_id': plugin_id,
+ }
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ # log the user out, try to remove the plugin
+ self.client.logout()
+ response = self.client.post(URL_CMS_PLUGIN_REMOVE, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ self.assertEqual(CMSPlugin.objects.count(), 1)
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(URL_CMS_PLUGIN_REMOVE, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(CMSPlugin.objects.count(), 1)
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+
+ def test_add_ph(self):
+ """
+ Test adding a *NON PAGE* plugin
+ """
+ page, placeholder, superuser, staff = self.get_data()
+ plugin_data = {
+ 'plugin_type':"TextPlugin",
+ 'language':settings.LANGUAGES[0][0],
+ 'placeholder':page.placeholders.get(slot="body").pk,
+ }
+ url = reverse('admin:placeholderapp_example1_add_plugin')
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+ # log the user out and try to add a plugin using PlaceholderAdmin
+ self.client.logout()
+ response = self.client.post(url, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(url, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(CMSPlugin.objects.count(), 0)
+
+ def test_edit_ph(self):
+ """
+ Test editing a *NON PAGE* plugin
+ """
+ page, placeholder, superuser, staff = self.get_data()
+ plugin_id = self.add_plugin(superuser, page, placeholder, 'en', 'body')
+ url = reverse('admin:placeholderapp_example1_edit_plugin', args=(plugin_id,))
+ plugin_data = {
+ 'body': 'newbody',
+ 'language': 'en',
+ }
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ # log the user out and try to edit a plugin using PlaceholderAdmin
+ self.client.logout()
+ response = self.client.post(url, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(url, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+
+ def test_delete_ph(self):
+ page, placeholder, superuser, staff = self.get_data()
+ plugin_id = self.add_plugin(superuser, page, placeholder, 'en', 'body')
+ plugin_data = {
+ 'plugin_id': plugin_id,
+ }
+ plugin = Text.objects.get(pk=plugin_id)
+ self.assertEqual(plugin.body, 'body')
+ url = reverse('admin:placeholderapp_example1_remove_plugin')
+ # log the user out and try to remove a plugin using PlaceholderAdmin
+ self.client.logout()
+ response = self.client.post(url, plugin_data)
+ # since the user is not logged in, they should be prompted to log in.
+ self.assertTemplateUsed(response, 'admin/login.html')
+ self.assertEqual(CMSPlugin.objects.count(), 1)
+ # now log a staff user without permissions in and do the same as above.
+ self.client.login(username='staff', password='staff')
+ response = self.client.post(url, plugin_data)
+ # the user is logged in and the security check fails, so it should 404.
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(CMSPlugin.objects.count(), 1)
View
9 menus/templatetags/menu_tags.py
@@ -22,8 +22,15 @@ def cut_after(node, levels, removed):
removed.extend(node.children)
node.children = []
else:
+ removed_local = []
for n in node.children:
- cut_after(n, levels - 1, removed)
+ if n.visible:
+ cut_after(n, levels - 1, removed)
+ else:
+ removed_local.append(n)
+ for n in removed_local:
+ node.children.remove(n)
+ removed.extend(removed_local)
def remove(node, removed):
removed.append(node)
View
1  setup.py
@@ -48,7 +48,6 @@
install_requires=[
'Django>=1.2',
'django-classy-tags>=0.2.2',
- 'PIL>=1.1.6',
'south>=0.7.2',
'django-mptt>=0.4.2',
],
View
1  tests/project/fixtures/menus-sub.json
@@ -0,0 +1 @@
+[{"pk": 1, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 2, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 3, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 4, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 5, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 6, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 7, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 8, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 9, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 10, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 11, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 12, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 13, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 14, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 15, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 16, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 17, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 18, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 19, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 20, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 21, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 22, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 23, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 24, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 25, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 26, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 27, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 28, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 29, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 30, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 31, "model": "cms.placeholder", "fields": {"slot": "right-column", "default_width": null}}, {"pk": 32, "model": "cms.placeholder", "fields": {"slot": "body", "default_width": null}}, {"pk": 1, "model": "cms.page", "fields": {"rght": 6, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:09", "lft": 1, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 1, "placeholders": [1, 2], "limit_visibility_in_menu": null, "parent": null, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:09", "publisher_public": null, "level": 0, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 2, "model": "cms.page", "fields": {"rght": 5, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:09", "lft": 2, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 1, "placeholders": [3, 4], "limit_visibility_in_menu": null, "parent": 1, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:09", "publisher_public": null, "level": 1, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 3, "model": "cms.page", "fields": {"rght": 4, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:10", "lft": 3, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 1, "placeholders": [5, 6], "limit_visibility_in_menu": null, "parent": 2, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:10", "publisher_public": null, "level": 2, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 4, "model": "cms.page", "fields": {"rght": 4, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:10", "lft": 1, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 2, "placeholders": [7, 8], "limit_visibility_in_menu": null, "parent": null, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:10", "publisher_public": null, "level": 0, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 5, "model": "cms.page", "fields": {"rght": 3, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:11", "lft": 2, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 2, "placeholders": [9, 10], "limit_visibility_in_menu": null, "parent": 4, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:11", "publisher_public": null, "level": 1, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 6, "model": "cms.page", "fields": {"rght": 6, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:12", "lft": 1, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 3, "placeholders": [11, 12], "limit_visibility_in_menu": null, "parent": null, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:12", "publisher_public": null, "level": 0, "changed_by": "admin", "publisher_is_draft": true, "published": true}}, {"pk": 7, "model": "cms.page", "fields": {"rght": 3, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:12", "lft": 2, "in_navigation": false, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 3, "placeholders": [13, 14], "limit_visibility_in_menu": null, "parent": 6, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:12", "publisher_public": null, "level": 1, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 8, "model": "cms.page", "fields": {"rght": 5, "navigation_extenders": null, "site": 1, "creation_date": "2011-01-22 17:07:13", "lft": 4, "in_navigation": true, "reverse_id": null, "login_required": false, "created_by": "script", "publication_end_date": null, "moderator_state": 0, "template": "nav_playground.html", "tree_id": 3, "placeholders": [15, 16], "limit_visibility_in_menu": null, "parent": 6, "publisher_state": 1, "soft_root": false, "publication_date": "2011-01-22 17:07:13", "publisher_public": null, "level": 1, "changed_by": "script", "publisher_is_draft": true, "published": true}}, {"pk": 1, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 1", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:09", "page": 1, "path": "test-page-1", "meta_description": null, "slug": "test-page-1"}}, {"pk": 2, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 2", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:10", "page": 2, "path": "test-page-1/test-page-2", "meta_description": null, "slug": "test-page-2"}}, {"pk": 3, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 3", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:10", "page": 3, "path": "test-page-1/test-page-2/test-page-3", "meta_description": null, "slug": "test-page-3"}}, {"pk": 4, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 4", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:11", "page": 4, "path": "test-page-4", "meta_description": null, "slug": "test-page-4"}}, {"pk": 5, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 5", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:12", "page": 5, "path": "test-page-4/test-page-5", "meta_description": null, "slug": "test-page-5"}}, {"pk": 6, "model": "cms.title", "fields": {"menu_title": "", "redirect": "", "meta_keywords": "", "page_title": "", "language": "en", "title": "test page 6", "has_url_overwrite": false, "application_urls": "", "creation_date": "2011-01-22 17:07:12", "page": 6, "path": "test-page-6", "meta_description": "", "slug": "test-page-6"}}, {"pk": 7, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 7", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:13", "page": 7, "path": "test-page-6/test-page-7", "meta_description": null, "slug": "test-page-7"}}, {"pk": 8, "model": "cms.title", "fields": {"menu_title": null, "redirect": null, "meta_keywords": null, "page_title": null, "language": "en", "title": "test page 8", "has_url_overwrite": false, "application_urls": null, "creation_date": "2011-01-22 17:07:13", "page": 8, "path": "test-page-6/test-page-8", "meta_description": null, "slug": "test-page-8"}}]
Please sign in to comment.
Something went wrong with that request. Please try again.