Repository: django-cms/django-cms

Issue:
show_placeholder tag uses wrong Placeholder class for exception handling
In `cms_tags.py`, the `_show_placeholder_for_page` function attempts to catch `Placeholder.DoesNotExist` (as it should). However, in that module the name `Placeholder` is already in use by a template node instead of the `Placeholder` model. This causes the `except Placeholder.DoesNotExist` handler to raise an `AttributeError` since the template node class named `Placeholder` defined in that file has no `DoesNotExist` attribute (and shouldn't).

The fix is simple: import `cms.models.Placeholder` under a different name and use it for the `except` block.

Pull request will be forthcoming.



Discussion:

Resolving patch:
diff --git a/cms/templatetags/cms_tags.py b/cms/templatetags/cms_tags.py
--- a/cms/templatetags/cms_tags.py
+++ b/cms/templatetags/cms_tags.py
@@ -3,7 +3,7 @@
 from classytags.core import Options, Tag
 from classytags.helpers import InclusionTag
 from classytags.parser import Parser
-from cms.models import Page
+from cms.models import Page, Placeholder as PlaceholderModel
 from cms.plugin_rendering import render_plugins, render_placeholder
 from cms.plugins.utils import get_plugins
 from cms.utils import get_language_from_request
@@ -89,19 +89,19 @@ def _get_page_by_untyped_arg(page_lookup, request, site_id):
 class PageUrl(InclusionTag):
     template = 'cms/content.html'
     name = 'page_url'
-    
+
     options = Options(
         Argument('page_lookup'),
         Argument('lang', required=False, default=None),
         Argument('site', required=False, default=None),
     )
-    
+
     def get_context(self, context, page_lookup, lang, site):
         site_id = get_site_id(site)
         request = context.get('request', False)
         if not request:
             return {'content': ''}
-    
+
         if request.current_page == "dummy":
             return {'content': ''}
         if lang is None:
@@ -155,8 +155,8 @@ def parse_blocks(self):
             if getattr(bit, 'value', bit.var.value) == 'or':
                 return super(PlaceholderParser, self).parse_blocks()
         return
-                
-    
+
+
 class PlaceholderOptions(Options):
     def get_parser_class(self):
         return PlaceholderParser
@@ -216,12 +216,12 @@ def render_tag(self, context, name, extra_bits, nodelist=None):
         page = request.current_page
         if not page or page == 'dummy':
             return ''
-        
+
         content = get_placeholder_content(context, request, page, name, inherit)
         if not content and nodelist:
             return nodelist.render(context)
         return content
-    
+
     def get_name(self):
         return self.kwargs['name'].var.value.strip('"').strip("'")
 register.tag(Placeholder)
@@ -262,7 +262,7 @@ class PageAttribute(Tag):
         Argument('name', resolve=False),
         Argument('page_lookup', required=False, default=None)
     )
-    
+
     valid_attributes = [
         "title",
         "slug",
@@ -271,7 +271,7 @@ class PageAttribute(Tag):
         "page_title",
         "menu_title"
     ]
-    
+
     def render_tag(self, context, name, page_lookup):
         if not 'request' in context:
             return ''
@@ -290,12 +290,12 @@ def render_tag(self, context, name, page_lookup):
 class CleanAdminListFilter(InclusionTag):
     template = 'admin/filter.html'
     name = 'clean_admin_list_filter'
-    
+
     options = Options(
         Argument('cl'),
         Argument('spec'),
     )
-    
+
     def get_context(self, context, cl, spec):
         choices = sorted(list(spec.choices(cl)), key=lambda k: k['query_string'])
         query_string = None
@@ -319,7 +319,7 @@ def _show_placeholder_for_page(context, placeholder_name, page_lookup, lang=None
     and their interpretation for the page_lookup argument.
     """
     validate_placeholder_name(placeholder_name)
-    
+
     request = context.get('request', False)
     site_id = get_site_id(site)
 
@@ -340,7 +340,7 @@ def _show_placeholder_for_page(context, placeholder_name, page_lookup, lang=None
             return {'content': ''}
         try:
             placeholder = page.placeholders.get(slot=placeholder_name)
-        except Placeholder.DoesNotExist:
+        except PlaceholderModel.DoesNotExist:
             if settings.DEBUG:
                 raise
             return {'content': ''}
@@ -364,17 +364,17 @@ def _show_placeholder_for_page(context, placeholder_name, page_lookup, lang=None
 class ShowPlaceholderById(InclusionTag):
     template = 'cms/content.html'
     name = 'show_placeholder_by_id'
-    
+
     options = Options(
         Argument('placeholder_name'),
         Argument('reverse_id'),
         Argument('lang', required=False, default=None),
         Argument('site', required=False, default=None),
     )
-    
+
     def get_context(self, *args, **kwargs):
         return _show_placeholder_for_page(**self.get_kwargs(*args, **kwargs))
-    
+
     def get_kwargs(self, context, placeholder_name, reverse_id, lang, site):
         return {
             'context': context,
@@ -400,7 +400,7 @@ def get_kwargs(self, *args, **kwargs):
 class CMSToolbar(InclusionTag):
     template = 'cms/toolbar/toolbar.html'
     name = 'cms_toolbar'
-    
+
     def render(self, context):
         request = context.get('request', None)
         if not request:
@@ -411,7 +411,7 @@ def render(self, context):
         if not toolbar.show_toolbar:
             return ''
         return super(CMSToolbar, self).render(context)
-    
+
     def get_context(self, context):
         context['CMS_TOOLBAR_CONFIG'] = context['request'].toolbar.as_json(context)
         return context


Test patch:
diff --git a/cms/tests/templatetags.py b/cms/tests/templatetags.py
--- a/cms/tests/templatetags.py
+++ b/cms/tests/templatetags.py
@@ -1,13 +1,17 @@
 from __future__ import with_statement
-from cms.models.pagemodel import Page
-from cms.templatetags.cms_tags import get_site_id, _get_page_by_untyped_arg
+from cms.models.pagemodel import Page, Placeholder
+from cms.templatetags.cms_tags import (get_site_id, _get_page_by_untyped_arg,
+        _show_placeholder_for_page)
 from cms.test_utils.fixtures.templatetags import TwoPagesFixture
 from cms.test_utils.testcases import SettingsOverrideTestCase
 from cms.test_utils.util.context_managers import SettingsOverride
 from cms.utils.plugins import get_placeholders
+from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core import mail
 from django.core.exceptions import ImproperlyConfigured
+from django.http import HttpRequest
+from django.template import RequestContext
 from unittest import TestCase
 
 
@@ -15,60 +19,66 @@ class TemplatetagTests(TestCase):
     def test_get_site_id_from_nothing(self):
         with SettingsOverride(SITE_ID=10):
             self.assertEqual(10, get_site_id(None))
-        
+
     def test_get_site_id_from_int(self):
         self.assertEqual(10, get_site_id(10))
-        
+
     def test_get_site_id_from_site(self):
         site = Site()
         site.id = 10
         self.assertEqual(10, get_site_id(site))
-        
+
     def test_get_site_id_from_str_int(self):
         self.assertEqual(10, get_site_id('10'))
-        
+
     def test_get_site_id_from_str(self):
         with SettingsOverride(SITE_ID=10):
             self.assertEqual(10, get_site_id("something"))
-    
+
     def test_unicode_placeholder_name_fails_fast(self):
         self.assertRaises(ImproperlyConfigured, get_placeholders, 'unicode_placeholder.html')
 
 
 class TemplatetagDatabaseTests(TwoPagesFixture, SettingsOverrideTestCase):
     settings_overrides = {'CMS_MODERATOR': False}
-    
+
+    def setUp(self):
+        self._prev_DEBUG = settings.DEBUG
+
+    def tearDown(self):
+        settings.DEBUG = self._prev_DEBUG
+
     def _getfirst(self):
         return Page.objects.get(title_set__title='first')
-    
+
     def _getsecond(self):
         return Page.objects.get(title_set__title='second')
-    
+
     def test_get_page_by_untyped_arg_none(self):
         control = self._getfirst()
         request = self.get_request('/')
         request.current_page = control
         page = _get_page_by_untyped_arg(None, request, 1)
         self.assertEqual(page, control)
-        
+
     def test_get_page_by_untyped_arg_page(self):
         control = self._getfirst()
         request = self.get_request('/')
         page = _get_page_by_untyped_arg(control, request, 1)
         self.assertEqual(page, control)
-        
+
     def test_get_page_by_untyped_arg_reverse_id(self):
         second = self._getsecond()
         request = self.get_request('/')
         page = _get_page_by_untyped_arg("myreverseid", request, 1)
         self.assertEqual(page, second)
-        
+
     def test_get_page_by_untyped_arg_dict(self):
         second = self._getsecond()
         request = self.get_request('/')
         page = _get_page_by_untyped_arg({'pk': second.pk}, request, 1)
         self.assertEqual(page, second)
-        
+
     def test_get_page_by_untyped_arg_dict_fail_debug(self):
         with SettingsOverride(DEBUG=True):
             request = self.get_request('/')
@@ -76,7 +86,7 @@ def test_get_page_by_untyped_arg_dict_fail_debug(self):
                 _get_page_by_untyped_arg, {'pk': 3}, request, 1
             )
             self.assertEqual(len(mail.outbox), 0)
-            
+
     def test_get_page_by_untyped_arg_dict_fail_nodebug_do_email(self):
         with SettingsOverride(SEND_BROKEN_LINK_EMAILS=True, DEBUG=False, MANAGERS=[("Jenkins", "tests@django-cms.org")]):
             request = self.get_request('/')
@@ -90,7 +100,25 @@ def test_get_page_by_untyped_arg_dict_fail_nodebug_no_email(self):
             page = _get_page_by_untyped_arg({'pk': 3}, request, 1)
             self.assertEqual(page, None)
             self.assertEqual(len(mail.outbox), 0)
-    
+
     def test_get_page_by_untyped_arg_fail(self):
         request = self.get_request('/')
-        self.assertRaises(TypeError, _get_page_by_untyped_arg, [], request, 1)
\ No newline at end of file
+        self.assertRaises(TypeError, _get_page_by_untyped_arg, [], request, 1)
+
+    def test_show_placeholder_for_page_placeholder_does_not_exist(self):
+        """
+        Verify ``show_placeholder`` correctly handles being given an
+        invalid identifier.
+        """
+        settings.DEBUG = True # So we can see the real exception raised
+        request = HttpRequest()
+        request.REQUEST = {}
+        self.assertRaises(Placeholder.DoesNotExist,
+                          _show_placeholder_for_page,
+                          RequestContext(request),
+                          'does_not_exist',
+                          'myreverseid')
+        settings.DEBUG = False # Now test the non-debug output
+        content = _show_placeholder_for_page(RequestContext(request),
+                                            'does_not_exist', 'myreverseid')
+        self.assertEqual(content['content'], '')