Repository: django-cms/django-cms

Issue:
show_uncached_placeholder uses caching with CMS_PLACEHOLDER_CACHE=True
Using `show_uncached_placholder` with `CMS_PLACEHOLDER_CACHE` set to `True` (which is the default!) causes the plugin rendering system to cache the placeholder nevertheless, which can trigger issues in `restore_sekizai_context`.

Specifically what happens is the template tag hits:
- Attempts to render the placeholder at https://github.com/divio/django-cms/blob/3.0.9/cms/templatetags/cms_tags.py#L558
- Correctly bypasses caching in that function at https://github.com/divio/django-cms/blob/3.0.9/cms/templatetags/cms_tags.py#L580
- Enters actual rendering via `render_placeholder` at https://github.com/divio/django-cms/blob/3.0.9/cms/templatetags/cms_tags.py#L597
- The step above does not pass any information with regards to caching to `render_placeholder`
- `render_placeholder` checks for `CMS_PLACEHOLDER_CACHE` at https://github.com/divio/django-cms/blob/3.0.9/cms/templatetags/cms_tags.py#L597
- Triggers `restore_sekizai_context` at https://github.com/divio/django-cms/blob/3.0.9/cms/plugin_rendering.py#L135
- Because only half of the pipeline is in "caching mode", `restore_sekizai_context` may trigger a `TypeError: 'NoneType' object is unsubscriptable` at https://github.com/divio/django-cms/blob/3.0.9/cms/utils/placeholder.py#L148

Example traceback:

```
Traceback (most recent call last):
  File "/path/to/env/lib/python2.6/site-packages/django/core/handlers/base.py", line 137, in get_response
    response = response.render()
  File "/path/to/env/lib/python2.6/site-packages/django/template/response.py", line 105, in render
    self.content = self.rendered_content
  File "/path/to/env/lib/python2.6/site-packages/django/template/response.py", line 82, in rendered_content
    content = template.render(context)
  File "/path/to/env/lib/python2.6/site-packages/django/template/base.py", line 140, in render
    return self._render(context)
  File "/path/to/env/lib/python2.6/site-packages/django/template/base.py", line 134, in _render


  ... Repeated calls to render/_render removed

    return self.render_tag(context, **kwargs)
  File "/path/to/env/lib/python2.6/site-packages/classytags/helpers.py", line 78, in render_tag
    data = self.get_context(context, **kwargs)
  File "/path/to/env/lib/python2.6/site-packages/cms/templatetags/cms_tags.py", line 619, in get_context
    return _show_placeholder_for_page(**self.get_kwargs(*args, **kwargs))
  File "/path/to/env/lib/python2.6/site-packages/cms/templatetags/cms_tags.py", line 597, in _show_placeholder_for_page
    content = render_placeholder(placeholder, context, placeholder_name)
  File "/path/to/env/lib/python2.6/site-packages/cms/plugin_rendering.py", line 135, in render_placeholder
    restore_sekizai_context(context, cached_value['sekizai'])
  File "/path/to/env/lib/python2.6/site-packages/cms/utils/placeholder.py", line 148, in restore_sekizai_context
    sekizai_namespace = sekizai_container[key]
TypeError: 'NoneType' object is unsubscriptable
```



Discussion:
While I only had a cursory look into this issue, it feels like passing a `no_cache` parameter to `render_placeholder` could solve this.

@ojii The revenge of the micro-features: it's solved in #3768!
In the above PR adds a new templatetag in the process (actually that was the scope of the PR)
That should be trivial to add tests for this case also

Great job sleuthing, @ojii. Thanks for the patch, @yakky. It looks like your patch is based against 3.1.x. Does this need to be rebased to 3.0.x?

Technically speaking #3768 is a new feature, so I'd need to rework it as a patch (which means keeping 99% of the code and just removing the templatetag declaration)
I'd vote to merge it as is (but it requires docs, that I'll write shortly) given the negligible impact of the "feature" part, but I'm open to both solutions

@ojii, do you have an opinion on @yakky's comment above?

@yakky @ojii: What's the way forward on this guys?

To me we can just merge #3768 

If I read #3768 correctly, it doesn't actually fix this issue. It adds the ability to fix this issue. So after 3768 this still needs to be tested and fixed

Sorry, writing on the go and cannot clarify myself: I meant "to me we can merge #3768 as is and then write the fix on top of it instead of extracting the code needed to fix this and not merge #3768"
Anyway, I have the fix for this on top of #3768 ready


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
@@ -594,7 +594,7 @@ def _show_placeholder_for_page(context, placeholder_name, page_lookup, lang=None
             raise
         return {'content': ''}
     watcher = Watcher(context)
-    content = render_placeholder(placeholder, context, placeholder_name)
+    content = render_placeholder(placeholder, context, placeholder_name, use_cache=cache_result)
     changes = watcher.get_changes()
     if cache_result:
         cache.set(cache_key, {'content': content, 'sekizai': changes}, get_cms_setting('CACHE_DURATIONS')['content'])


Test patch:
diff --git a/cms/tests/rendering.py b/cms/tests/rendering.py
--- a/cms/tests/rendering.py
+++ b/cms/tests/rendering.py
@@ -10,6 +10,7 @@
 from cms.models.placeholdermodel import Placeholder
 from cms.models.pluginmodel import CMSPlugin
 from cms.plugin_rendering import render_plugins, PluginContext, render_placeholder_toolbar
+from cms.templatetags.cms_tags import _clean_key, _get_cache_key
 from cms.test_utils.project.placeholderapp.models import Example1
 from cms.test_utils.testcases import SettingsOverrideTestCase
 from cms.test_utils.util.context_managers import SettingsOverride, ChangeModel
@@ -288,6 +289,24 @@ def test_show_uncached_placeholder_by_page(self):
         output = self.render(template, self.test_page, {'test_page': self.test_page})
         self.assertEqual(output, self.test_data['text_sub'])
 
+    def test_show_uncached_placeholder_tag_no_use_cache(self):
+        """
+        Tests that {% show_uncached_placeholder %} does not populate cache.
+        """
+        template = '{% load cms_tags %}<h1>{% show_uncached_placeholder "sub" test_page %}</h1>'
+
+        base_key = _get_cache_key('_show_placeholder_for_page', self.test_page, "en",
+                                  self.test_page.site_id)
+        cache_key = _clean_key('%s_placeholder:%s' % (base_key, "sub"))
+
+        cache_value_before = cache.get(cache_key)
+        output = self.render(template, self.test_page, {'test_page': self.test_page})
+        cache_value_after = cache.get(cache_key)
+
+        self.assertEqual(output, '<h1>%s</h1>' % self.test_data['text_sub'])
+        self.assertEqual(cache_value_before, cache_value_after)
+        self.assertIsNone(cache_value_after)
+
     def test_page_url_by_pk(self):
         template = u'{%% load cms_tags %%}{%% page_url %s %%}' % self.test_page2.pk
         output = self.render(template, self.test_page)