Repository: django-cms/django-cms

Issue:
 Templatetag "render_model_add" requires a model instance 
Allow to use `render_model_add` by passing a class as argument instead of  an instance



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
@@ -4,6 +4,7 @@
 from itertools import chain
 import re
 from classytags.values import StringValue
+from django.db.models import Model
 from cms.utils.urlutils import admin_reverse
 
 from django import template
@@ -535,26 +536,6 @@ def get_value(self, context, name, page_lookup):
 register.tag(PageAttribute)
 
 
-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
-        unique_choices = []
-        for choice in choices:
-            if choice['query_string'] != query_string:
-                unique_choices.append(choice)
-                query_string = choice['query_string']
-        return {'title': spec.title(), 'choices': unique_choices}
-
-
 def _show_placeholder_for_page(context, placeholder_name, page_lookup, lang=None,
                                site=None, cache_result=True):
     """
@@ -985,6 +966,8 @@ def get_context(self, context, instance, language,
         """
         Uses _get_empty_context and adds the `render_model_icon` variable.
         """
+        if isinstance(instance, Model) and not instance.pk:
+            instance.pk = 0
         extra_context = self._get_empty_context(context, instance, None,
                                                 language, view_url, view_method,
                                                 editmode=False)
@@ -993,6 +976,55 @@ def get_context(self, context, instance, language,
 register.tag(CMSEditableObjectAdd)
 
 
+class CMSEditableObjectAddBlock(CMSEditableObject):
+    """
+    Templatetag that links arbitrary content to the addform for the specified
+    model (based on the provided model instance).
+    """
+    name = 'render_model_add_block'
+    options = Options(
+        Argument('instance'),
+        Argument('language', default=None, required=False),
+        Argument('view_url', default=None, required=False),
+        Argument('view_method', default=None, required=False),
+        'as',
+        Argument('varname', required=False, resolve=False),
+        blocks=[('endrender_model_add_block', 'nodelist')],
+    )
+
+    def render_tag(self, context, **kwargs):
+        """
+        Renders the block and then inject the resulting HTML in the template
+        context
+        """
+        context.push()
+        template = self.get_template(context, **kwargs)
+        data = self.get_context(context, **kwargs)
+        data['content'] = mark_safe(kwargs['nodelist'].render(data))
+        data['rendered_content'] = data['content']
+        output = render_to_string(template, data)
+        context.pop()
+        if kwargs.get('varname'):
+            context[kwargs['varname']] = output
+            return ''
+        else:
+            return output
+
+    def get_context(self, context, instance, language,
+                    view_url, view_method, varname, nodelist):
+        """
+        Uses _get_empty_context and adds the `render_model_icon` variable.
+        """
+        if isinstance(instance, Model) and not instance.pk:
+            instance.pk = 0
+        extra_context = self._get_empty_context(context, instance, None,
+                                                language, view_url, view_method,
+                                                editmode=False)
+        extra_context['render_model_add'] = True
+        return extra_context
+register.tag(CMSEditableObjectAddBlock)
+
+
 class CMSEditableObjectBlock(CMSEditableObject):
     """
     Templatetag that links a content extracted from a generic django model


Test patch:
diff --git a/cms/test_utils/project/placeholderapp/cms_app.py b/cms/test_utils/project/placeholderapp/cms_app.py
--- a/cms/test_utils/project/placeholderapp/cms_app.py
+++ b/cms/test_utils/project/placeholderapp/cms_app.py
@@ -9,6 +9,7 @@ class Example1App(CMSApp):
 
 apphook_pool.register(Example1App)
 
+
 class MultilingualExample1App(CMSApp):
     name = _("MultilingualExample1 App")
     urls = ["cms.test_utils.project.placeholderapp.urls_multi"]
diff --git a/cms/test_utils/project/placeholderapp/models.py b/cms/test_utils/project/placeholderapp/models.py
--- a/cms/test_utils/project/placeholderapp/models.py
+++ b/cms/test_utils/project/placeholderapp/models.py
@@ -65,11 +65,15 @@ class DynamicPlaceholderSlotExample(models.Model):
     placeholder_2 = PlaceholderField(dynamic_placeholder_2, related_name='dynamic_pl_2')
 
 
+@python_2_unicode_compatible
 class CharPksExample(models.Model):
     char_1 = models.CharField(u'char_1', max_length=255)
     slug = models.SlugField(u'char_1', max_length=255, primary_key=True)
     placeholder_1 = PlaceholderField('placeholder_1', related_name='charpk_p1')
 
+    def __str__(self):
+        return "%s - %s" % (self.char_1, self.pk)
+
 
 @python_2_unicode_compatible
 class MultilingualExample1(TranslatableModel):
diff --git a/cms/test_utils/project/placeholderapp/templates/detail.html b/cms/test_utils/project/placeholderapp/templates/detail.html
--- a/cms/test_utils/project/placeholderapp/templates/detail.html
+++ b/cms/test_utils/project/placeholderapp/templates/detail.html
@@ -3,7 +3,7 @@
 
 {% block content %}
 <h2 class="page_title">{% render_model request.current_page "page_title" %}</h2>
-<h1>{% render_model instance "char_1"  %}</h1>
+<h1>{% render_model instance "char_1"  %}</h1> {% render_model_add instance_class %}
 {% render_model instance "char_2" "char_1,char_2" %}
 {% render_placeholder instance.placeholder %}
 {% endblock content %}
diff --git a/cms/test_utils/project/placeholderapp/templates/list.html b/cms/test_utils/project/placeholderapp/templates/list.html
--- a/cms/test_utils/project/placeholderapp/templates/list.html
+++ b/cms/test_utils/project/placeholderapp/templates/list.html
@@ -2,7 +2,7 @@
 {% load cms_tags %}
 
 {% block content %}
-<h2 class="page_title">{% render_model request.current_page "page_title" %}</h2>
+<h2 class="page_title">{% render_model request.current_page "page_title" %} {% render_model_add instance_class %}</h2>
 {% for example in examples %}
 <h2><a href="{{example.get_absolute_url}}">{{example}}</a></h2>
 {% endfor %}
diff --git a/cms/test_utils/project/placeholderapp/views.py b/cms/test_utils/project/placeholderapp/views.py
--- a/cms/test_utils/project/placeholderapp/views.py
+++ b/cms/test_utils/project/placeholderapp/views.py
@@ -18,6 +18,7 @@ def _base_detail(request, instance, template_name='detail.html',
                  item_name="char_1", template_string='',):
     context = RequestContext(request)
     context['instance'] = instance
+    context['instance_class'] = instance.__class__
     context['item_name'] = item_name
     if template_string:
         template = Template(template_string)
@@ -30,6 +31,7 @@ def list_view_multi(request):
     context = RequestContext(request)
     context['examples'] = MultilingualExample1.objects.language(
         get_language_from_request(request)).all()
+    context['instance_class'] = MultilingualExample1
     return render_to_response('list.html', context)
 
 
@@ -51,6 +53,7 @@ def detail_view_multi_unfiltered(request, pk, template_name='detail_multi.html',
 def list_view(request):
     context = RequestContext(request)
     context['examples'] = Example1.objects.all()
+    context['instance_class'] = Example1
     return render_to_response('list.html', context)
 
 
@@ -70,4 +73,18 @@ def detail_view_char(request, pk, template_name='detail.html', item_name="char_1
 
 class ClassDetail(DetailView):
     model = Example1
-    template_name = "detail.html"
\ No newline at end of file
+    template_name = "detail.html"
+    template_string = ''
+
+    def render_to_response(self, context, **response_kwargs):
+        if self.template_string:
+            context = RequestContext(self.request, context)
+            template = Template(self.template_string)
+            return HttpResponse(template.render(context))
+        else:
+            return super(ClassDetail, self).render_to_response(context, **response_kwargs)
+
+    def get_context_data(self, **kwargs):
+        context = super(ClassDetail, self).get_context_data(**kwargs)
+        context['instance_class'] = self.model
+        return context
diff --git a/cms/test_utils/project/sampleapp/views.py b/cms/test_utils/project/sampleapp/views.py
--- a/cms/test_utils/project/sampleapp/views.py
+++ b/cms/test_utils/project/sampleapp/views.py
@@ -48,6 +48,7 @@ def current_app(request):
     context = RequestContext(request, {'app': app}, current_app=app)
     return render_to_response("sampleapp/app.html", context)
 
+
 def plain_view(request):
     context = RequestContext(request, {'content': 'plain text'})
     return render_to_response("sampleapp/plain.html", context)
diff --git a/cms/tests/templatetags.py b/cms/tests/templatetags.py
--- a/cms/tests/templatetags.py
+++ b/cms/tests/templatetags.py
@@ -382,3 +382,72 @@ def test_render_placeholder_as_var(self):
         with self.assertNumQueries(4):
             template.render(context)
 
+    def test_render_model_add(self):
+        from django.core.cache import cache
+        from cms.test_utils.project.sampleapp.models import Category
+
+        cache.clear()
+        page = create_page('Test', 'col_two.html', 'en', published=True)
+        template = Template(
+            "{% load cms_tags %}{% render_model_add category %}")
+        user = self._create_user("admin", True, True)
+        request = RequestFactory().get('/')
+        request.user = user
+        request.current_page = page
+        request.session = {}
+        request.toolbar = CMSToolbar(request)
+        request.toolbar.edit_mode = True
+        request.toolbar.is_staff = True
+        context = RequestContext(request, {'category': Category()})
+        with self.assertNumQueries(0):
+            output = template.render(context)
+        expected = 'cms_plugin cms_plugin-sampleapp-category-add-0 '
+        'cms_render_model_add'
+        self.assertIn(expected, output)
+
+        # Now test that it does NOT render when not in edit mode
+        request = RequestFactory().get('/')
+        request.user = user
+        request.current_page = page
+        request.session = {}
+        request.toolbar = CMSToolbar(request)
+        context = RequestContext(request, {'category': Category()})
+        with self.assertNumQueries(0):
+            output = template.render(context)
+        expected = ''
+        self.assertEqual(expected, output)
+
+    def test_render_model_add_block(self):
+        from django.core.cache import cache
+        from cms.test_utils.project.sampleapp.models import Category
+
+        cache.clear()
+        page = create_page('Test', 'col_two.html', 'en', published=True)
+        template = Template(
+            "{% load cms_tags %}{% render_model_add_block category %}wrapped{% endrender_model_add_block %}")
+        user = self._create_user("admin", True, True)
+        request = RequestFactory().get('/')
+        request.user = user
+        request.current_page = page
+        request.session = {}
+        request.toolbar = CMSToolbar(request)
+        request.toolbar.edit_mode = True
+        request.toolbar.is_staff = True
+        context = RequestContext(request, {'category': Category()})
+        with self.assertNumQueries(0):
+            output = template.render(context)
+        expected = 'cms_plugin cms_plugin-sampleapp-category-add-0 '
+        'cms_render_model_add'
+        self.assertIn(expected, output)
+
+        # Now test that it does NOT render when not in edit mode
+        request = RequestFactory().get('/')
+        request.user = user
+        request.current_page = page
+        request.session = {}
+        request.toolbar = CMSToolbar(request)
+        context = RequestContext(request, {'category': Category()})
+        with self.assertNumQueries(0):
+            output = template.render(context)
+        expected = 'wrapped'
+        self.assertEqual(expected, output)
diff --git a/cms/tests/toolbar.py b/cms/tests/toolbar.py
--- a/cms/tests/toolbar.py
+++ b/cms/tests/toolbar.py
@@ -23,7 +23,7 @@
                                                           MultilingualExample1)
 from cms.test_utils.project.placeholderapp.views import (detail_view, detail_view_char,
                                                          detail_view_multi,
-                                                         detail_view_multi_unfiltered)
+                                                         detail_view_multi_unfiltered, ClassDetail)
 from cms.test_utils.testcases import (SettingsOverrideTestCase,
                                       URL_CMS_PAGE_ADD, URL_CMS_PAGE_CHANGE)
 from cms.test_utils.util.context_managers import SettingsOverride, UserLoginContext
@@ -787,6 +787,47 @@ def test_add_tag(self):
             '<div class="cms_plugin cms_plugin-%s-%s-add-%s cms_render_model_add"><img src="/static/cms/img/toolbar/render_model_placeholder.png"></div>' % (
                 'placeholderapp', 'example1', ex1.pk))
 
+    def test_add_tag_class(self):
+        user = self.get_staff()
+        page = create_page('Test', 'col_two.html', 'en', published=True)
+        ex1 = Example1(char_1="char_1", char_2="char_2", char_3="char_3",
+                       char_4="char_4")
+        ex1.save()
+        template_text = '''{% extends "base.html" %}
+{% load cms_tags %}
+
+{% block content %}
+{% render_model_add instance_class %}
+{% endblock content %}
+'''
+        request = self.get_page_request(page, user, edit=True)
+        response = detail_view(request, ex1.pk, template_string=template_text)
+        self.assertContains(
+            response,
+            '<div class="cms_plugin cms_plugin-%s-%s-add-%s cms_render_model_add"><img src="/static/cms/img/toolbar/render_model_placeholder.png"></div>' % (
+                'placeholderapp', 'example1', '0'))
+
+    def test_add_tag_classview(self):
+        user = self.get_staff()
+        page = create_page('Test', 'col_two.html', 'en', published=True)
+        ex1 = Example1(char_1="char_1", char_2="char_2", char_3="char_3",
+                       char_4="char_4")
+        ex1.save()
+        template_text = '''{% extends "base.html" %}
+{% load cms_tags %}
+
+{% block content %}
+{% render_model_add instance_class %}
+{% endblock content %}
+'''
+        request = self.get_page_request(page, user, edit=True)
+        view_func = ClassDetail.as_view(template_string=template_text)
+        response = view_func(request, pk=ex1.pk, template_string=template_text)
+        self.assertContains(
+            response,
+            '<div class="cms_plugin cms_plugin-%s-%s-add-%s cms_render_model_add"><img src="/static/cms/img/toolbar/render_model_placeholder.png"></div>' % (
+                'placeholderapp', 'example1', '0'))
+
     def test_block_tag(self):
         user = self.get_staff()
         page = create_page('Test', 'col_two.html', 'en', published=True)