1# -*- coding: utf-8 -*-
2from contextlib import contextmanager
3import datetime
4import pickle
5import warnings
6
7from cms.api import create_page
8
9from django import http
10from django.conf import settings
11from django.conf.urls import url
12from django.contrib import admin
13from django.contrib.admin.widgets import FilteredSelectMultiple, RelatedFieldWidgetWrapper
14from django.core.exceptions import ImproperlyConfigured
15from django.forms.widgets import Media
16from django.test.testcases import TestCase
17from django.urls import reverse
18from django.utils import timezone
19from django.utils.encoding import force_text
20from django.utils.translation import override as force_language
21
22from cms import api
23from cms.exceptions import PluginAlreadyRegistered, PluginNotRegistered, DontUsePageAttributeWarning
24from cms.models import Page, Placeholder
25from cms.models.pluginmodel import CMSPlugin
26from cms.plugin_base import CMSPluginBase
27from cms.plugin_pool import plugin_pool
28from cms.sitemaps.cms_sitemap import CMSSitemap
29from cms.test_utils.project.pluginapp.plugins.manytomany_rel.models import (
30    Article, Section, ArticlePluginModel,
31    FKModel,
32    M2MTargetModel)
33from cms.test_utils.project.pluginapp.plugins.meta.cms_plugins import (
34    TestPlugin, TestPlugin2, TestPlugin3, TestPlugin4, TestPlugin5)
35from cms.test_utils.project.pluginapp.plugins.validation.cms_plugins import (
36    NonExisitngRenderTemplate, NoRender, NoRenderButChildren, DynTemplate)
37from cms.test_utils.testcases import (
38    CMSTestCase, URL_CMS_PAGE, URL_CMS_PAGE_ADD,
39    URL_CMS_PLUGIN_ADD, URL_CMS_PAGE_CHANGE,
40    URL_CMS_PAGE_PUBLISH,
41)
42from cms.test_utils.util.fuzzy_int import FuzzyInt
43from cms.toolbar.toolbar import CMSToolbar
44from cms.toolbar.utils import get_toolbar_from_request
45from cms.utils.conf import get_cms_setting
46from cms.utils.copy_plugins import copy_plugins_to
47from cms.utils.plugins import get_plugins
48from django.utils.http import urlencode
49
50from djangocms_text_ckeditor.models import Text
51from djangocms_text_ckeditor.utils import plugin_to_tag
52
53
54@contextmanager
55def register_plugins(*plugins):
56    for plugin in plugins:
57        plugin_pool.register_plugin(plugin)
58
59    # clear cached properties
60    plugin_pool._clear_cached()
61
62    try:
63        yield
64    finally:
65        for plugin in plugins:
66            plugin_pool.unregister_plugin(plugin)
67
68
69def _render_placeholder(placeholder, context, **kwargs):
70    request = context['request']
71    toolbar = get_toolbar_from_request(request)
72    content_renderer = toolbar.content_renderer
73    return content_renderer.render_placeholder(placeholder, context, **kwargs)
74
75
76class DumbFixturePlugin(CMSPluginBase):
77    model = CMSPlugin
78    name = "Dumb Test Plugin. It does nothing."
79    render_template = ""
80    admin_preview = False
81    render_plugin = False
82
83    def render(self, context, instance, placeholder):
84        return context
85
86
87class DumbFixturePluginWithUrls(DumbFixturePlugin):
88    name = DumbFixturePlugin.name + " With custom URLs."
89    render_plugin = False
90
91    def _test_view(self, request):
92        return http.HttpResponse("It works")
93
94    def get_plugin_urls(self):
95        return [
96            url(r'^testview/$', admin.site.admin_view(self._test_view), name='dumbfixtureplugin'),
97        ]
98plugin_pool.register_plugin(DumbFixturePluginWithUrls)
99
100
101class PluginsTestBaseCase(CMSTestCase):
102
103    def setUp(self):
104        plugin_pool._clear_cached()
105        self.super_user = self._create_user("test", True, True)
106        self.slave = self._create_user("slave", True)
107
108        self.FIRST_LANG = settings.LANGUAGES[0][0]
109        self.SECOND_LANG = settings.LANGUAGES[1][0]
110
111        self._login_context = self.login_user_context(self.super_user)
112        self._login_context.__enter__()
113
114    def tearDown(self):
115        self._login_context.__exit__(None, None, None)
116
117    def approve_page(self, page):
118        response = self.client.get(URL_CMS_PAGE + "%d/approve/" % page.pk)
119        self.assertRedirects(response, URL_CMS_PAGE)
120        # reload page
121        return self.reload_page(page)
122
123    def get_request(self, *args, **kwargs):
124        request = super(PluginsTestBaseCase, self).get_request(*args, **kwargs)
125        request.placeholder_media = Media()
126        request.toolbar = CMSToolbar(request)
127        return request
128
129    def get_response_pk(self, response):
130        return int(response.content.decode('utf8').split("/edit-plugin/")[1].split("/")[0])
131
132    def get_placeholder(self):
133        return Placeholder.objects.create(slot='test')
134
135
136class PluginsTestCase(PluginsTestBaseCase):
137
138    def _create_link_plugin_on_page(self, page, slot='col_left'):
139        add_url = self.get_add_plugin_uri(
140            placeholder=page.placeholders.get(slot=slot),
141            plugin_type='LinkPlugin',
142            language=settings.LANGUAGES[0][0],
143        )
144        data = {'name': 'A Link', 'external_link': 'https://www.django-cms.org'}
145        response = self.client.post(add_url, data)
146        self.assertEqual(response.status_code, 200)
147        return CMSPlugin.objects.latest('pk').pk
148
149    def __edit_link_plugin(self, plugin_id, text):
150        endpoint = self.get_admin_url(Page, 'edit_plugin', plugin_id)
151        endpoint += '?cms_path=/en/'
152
153        response = self.client.get(endpoint)
154        self.assertEqual(response.status_code, 200)
155        data = {'name': text, 'external_link': 'https://www.django-cms.org'}
156        response = self.client.post(endpoint, data)
157        self.assertEqual(response.status_code, 200)
158        return CMSPlugin.objects.get(pk=plugin_id).get_bound_plugin()
159
160    def test_add_edit_plugin(self):
161        """
162        Test that you can add a text plugin
163        """
164        # add a new text plugin
165        page_data = self.get_new_page_data()
166        self.client.post(URL_CMS_PAGE_ADD, page_data)
167        page = Page.objects.drafts().first()
168        created_plugin_id = self._create_link_plugin_on_page(page)
169        # now edit the plugin
170        plugin = self.__edit_link_plugin(created_plugin_id, "Hello World")
171        self.assertEqual("Hello World", plugin.name)
172
173    def test_plugin_add_form_integrity(self):
174        admin.autodiscover()
175        admin_instance = admin.site._registry[ArticlePluginModel]
176        placeholder = self.get_placeholder()
177        url = URL_CMS_PLUGIN_ADD + '?' + urlencode({
178            'plugin_type': "ArticlePlugin",
179            'target_language': settings.LANGUAGES[0][0],
180            'placeholder_id': placeholder.pk,
181        })
182        superuser = self.get_superuser()
183        plugin = plugin_pool.get_plugin('ArticlePlugin')
184
185        with self.login_user_context(superuser):
186            request = self.get_request(url)
187            PluginFormClass = plugin(
188                model=plugin.model,
189                admin_site=admin.site,
190            ).get_form(request)
191            plugin_fields = list(PluginFormClass.base_fields.keys())
192
193            OriginalFormClass = admin_instance.get_form(request)
194            original_fields = list(OriginalFormClass.base_fields.keys())
195
196            # Assert both forms have the same fields
197            self.assertEqual(plugin_fields, original_fields)
198
199            # Now assert the plugin form has the related field wrapper
200            # widget on the sections field.
201            self.assertIsInstance(
202                PluginFormClass.base_fields['sections'].widget,
203                RelatedFieldWidgetWrapper,
204            )
205
206            # Now assert the admin form has the related field wrapper
207            # widget on the sections field.
208            self.assertIsInstance(
209                OriginalFormClass.base_fields['sections'].widget,
210                RelatedFieldWidgetWrapper,
211            )
212
213            # Now assert the plugin form has the filtered select multiple
214            # widget wrapped by the related field wrapper
215            self.assertIsInstance(
216                PluginFormClass.base_fields['sections'].widget.widget,
217                FilteredSelectMultiple,
218            )
219
220            # Now assert the admin form has the filtered select multiple
221            # widget wrapped by the related field wrapper
222            self.assertIsInstance(
223                OriginalFormClass.base_fields['sections'].widget.widget,
224                FilteredSelectMultiple,
225            )
226
227    def test_excluded_plugin(self):
228        """
229        Test that you can't add a text plugin
230        """
231
232        CMS_PLACEHOLDER_CONF = {
233            'body': {
234                'excluded_plugins': ['TextPlugin']
235            }
236        }
237
238        # try to add a new text plugin
239        with self.settings(CMS_PLACEHOLDER_CONF=CMS_PLACEHOLDER_CONF):
240            page_data = self.get_new_page_data()
241            self.client.post(URL_CMS_PAGE_ADD, page_data)
242            page = Page.objects.drafts().first()
243            installed_plugins = plugin_pool.get_all_plugins('body', page)
244            installed_plugins = [cls.__name__ for cls in installed_plugins]
245            self.assertNotIn('TextPlugin', installed_plugins)
246
247        CMS_PLACEHOLDER_CONF = {
248            'body': {
249                'plugins': ['TextPlugin'],
250                'excluded_plugins': ['TextPlugin']
251            }
252        }
253
254        # try to add a new text plugin
255        with self.settings(CMS_PLACEHOLDER_CONF=CMS_PLACEHOLDER_CONF):
256            page_data = self.get_new_page_data()
257            self.client.post(URL_CMS_PAGE_ADD, page_data)
258            page = Page.objects.drafts().first()
259            installed_plugins = plugin_pool.get_all_plugins('body', page)
260            installed_plugins = [cls.__name__ for cls in installed_plugins]
261            self.assertNotIn('TextPlugin', installed_plugins)
262
263    def test_plugin_edit_marks_page_dirty(self):
264        page_data = self.get_new_page_data()
265        response = self.client.post(URL_CMS_PAGE_ADD, page_data)
266        self.assertEqual(response.status_code, 302)
267        page = Page.objects.drafts().first()
268        response = self.client.post(URL_CMS_PAGE_PUBLISH % (page.pk, 'en'))
269        self.assertEqual(response.status_code, 302)
270        created_plugin_id = self._create_link_plugin_on_page(page)
271        page = Page.objects.drafts().first()
272        self.assertEqual(page.is_dirty('en'), True)
273        response = self.client.post(URL_CMS_PAGE_PUBLISH % (page.pk, 'en'))
274        self.assertEqual(response.status_code, 302)
275        page = Page.objects.drafts().first()
276        self.assertEqual(page.is_dirty('en'), False)
277        self.__edit_link_plugin(created_plugin_id, "Hello World")
278        page = Page.objects.drafts().first()
279        self.assertEqual(page.is_dirty('en'), True)
280
281    def test_plugin_order(self):
282        """
283        Test that plugin position is saved after creation
284        """
285        page_en = api.create_page("PluginOrderPage", "col_two.html", "en",
286                                  slug="page1", published=True, in_navigation=True)
287        ph_en = page_en.placeholders.get(slot="col_left")
288
289        # We check created objects and objects from the DB to be sure the position value
290        # has been saved correctly
291        text_plugin_1 = api.add_plugin(ph_en, "TextPlugin", "en", body="I'm the first")
292        text_plugin_2 = api.add_plugin(ph_en, "TextPlugin", "en", body="I'm the second")
293        db_plugin_1 = CMSPlugin.objects.get(pk=text_plugin_1.pk)
294        db_plugin_2 = CMSPlugin.objects.get(pk=text_plugin_2.pk)
295
296        with self.settings(CMS_PERMISSION=False):
297            self.assertEqual(text_plugin_1.position, 0)
298            self.assertEqual(db_plugin_1.position, 0)
299            self.assertEqual(text_plugin_2.position, 1)
300            self.assertEqual(db_plugin_2.position, 1)
301            ## Finally we render the placeholder to test the actual content
302            context = self.get_context(page_en.get_absolute_url(), page=page_en)
303            rendered_placeholder = _render_placeholder(ph_en, context)
304            self.assertEqual(rendered_placeholder, "I'm the firstI'm the second")
305
306    def test_plugin_order_alt(self):
307        """
308        Test that plugin position is saved after creation
309        """
310        draft_page = api.create_page("PluginOrderPage", "col_two.html", "en",
311                                     slug="page1", published=False, in_navigation=True)
312        placeholder = draft_page.placeholders.get(slot="col_left")
313
314        # We check created objects and objects from the DB to be sure the position value
315        # has been saved correctly
316        text_plugin_2 = api.add_plugin(placeholder, "TextPlugin", "en", body="I'm the second")
317        text_plugin_3 = api.add_plugin(placeholder, "TextPlugin", "en", body="I'm the third")
318        # Publish to create a 'live' version
319        draft_page.publish('en')
320        draft_page = draft_page.reload()
321        placeholder = draft_page.placeholders.get(slot="col_left")
322
323        # Add a plugin and move it to the first position
324        text_plugin_1 = api.add_plugin(placeholder, "TextPlugin", "en", body="I'm the first")
325
326        data = {
327            'placeholder_id': placeholder.id,
328            'plugin_id': text_plugin_1.id,
329            'plugin_parent': '',
330            'target_language': 'en',
331            'plugin_order[]': [text_plugin_1.id, text_plugin_2.id, text_plugin_3.id],
332        }
333
334        endpoint = self.get_move_plugin_uri(text_plugin_1)
335
336        self.client.post(endpoint, data)
337
338        draft_page.publish('en')
339        draft_page = draft_page.reload()
340        live_page = draft_page.get_public_object()
341        placeholder = draft_page.placeholders.get(slot="col_left")
342        live_placeholder = live_page.placeholders.get(slot="col_left")
343
344        with self.settings(CMS_PERMISSION=False):
345            self.assertEqual(CMSPlugin.objects.get(pk=text_plugin_1.pk).position, 0)
346            self.assertEqual(CMSPlugin.objects.get(pk=text_plugin_2.pk).position, 1)
347            self.assertEqual(CMSPlugin.objects.get(pk=text_plugin_3.pk).position, 2)
348
349            ## Finally we render the placeholder to test the actual content
350            draft_page_context = self.get_context(draft_page.get_absolute_url(), page=draft_page)
351            rendered_placeholder = _render_placeholder(placeholder, draft_page_context)
352            self.assertEqual(rendered_placeholder, "I'm the firstI'm the secondI'm the third")
353            live_page_context = self.get_context(live_page.get_absolute_url(), page=live_page)
354            rendered_live_placeholder = _render_placeholder(live_placeholder, live_page_context)
355            self.assertEqual(rendered_live_placeholder, "I'm the firstI'm the secondI'm the third")
356
357        columns = api.add_plugin(placeholder, "MultiColumnPlugin", "en")
358        column = api.add_plugin(
359            placeholder,
360            "ColumnPlugin",
361            "en",
362            target=columns,
363        )
364
365        data = {
366            'placeholder_id': placeholder.id,
367            'plugin_id': text_plugin_1.id,
368            'plugin_parent': '',
369            'target_language': 'en',
370            'plugin_order[]': [
371                text_plugin_1.id,
372                text_plugin_2.id,
373                text_plugin_3.id,
374                columns.id,
375                column.id,
376            ],
377        }
378        response = self.client.post(endpoint, data)
379        self.assertEqual(response.status_code, 400)
380        self.assertContains(
381            response,
382            'order parameter references plugins in different trees',
383            status_code=400,
384        )
385
386    def test_plugin_breadcrumbs(self):
387        """
388        Test the plugin breadcrumbs order
389        """
390        draft_page = api.create_page("home", "col_two.html", "en",
391                                     slug="page1", published=False, in_navigation=True)
392        placeholder = draft_page.placeholders.get(slot="col_left")
393
394        columns = api.add_plugin(placeholder, "MultiColumnPlugin", "en")
395        column = api.add_plugin(placeholder, "ColumnPlugin", "en", target=columns)
396        text_plugin = api.add_plugin(placeholder, "TextPlugin", "en", target=column, body="I'm the second")
397        text_breadcrumbs = text_plugin.get_breadcrumb()
398        self.assertEqual(len(columns.get_breadcrumb()), 1)
399        self.assertEqual(len(column.get_breadcrumb()), 2)
400        self.assertEqual(len(text_breadcrumbs), 3)
401        self.assertTrue(text_breadcrumbs[0]['title'], columns.get_plugin_class().name)
402        self.assertTrue(text_breadcrumbs[1]['title'], column.get_plugin_class().name)
403        self.assertTrue(text_breadcrumbs[2]['title'], text_plugin.get_plugin_class().name)
404        self.assertTrue('/edit-plugin/%s/'% columns.pk in text_breadcrumbs[0]['url'])
405        self.assertTrue('/edit-plugin/%s/'% column.pk, text_breadcrumbs[1]['url'])
406        self.assertTrue('/edit-plugin/%s/'% text_plugin.pk, text_breadcrumbs[2]['url'])
407
408    def test_add_text_plugin_empty_tag(self):
409        """
410        Test that you can add a text plugin
411        """
412        # add a new text plugin
413        page = api.create_page(
414            title='test page',
415            template='nav_playground.html',
416            language=settings.LANGUAGES[0][0],
417        )
418        plugin = api.add_plugin(
419            placeholder=page.placeholders.get(slot='body'),
420            plugin_type='TextPlugin',
421            language=settings.LANGUAGES[0][0],
422            body='<div class="someclass"></div><p>foo</p>'
423        )
424        self.assertEqual(plugin.body, '<div class="someclass"></div><p>foo</p>')
425
426    def test_add_text_plugin_html_sanitizer(self):
427        """
428        Test that you can add a text plugin
429        """
430        # add a new text plugin
431        page = api.create_page(
432            title='test page',
433            template='nav_playground.html',
434            language=settings.LANGUAGES[0][0],
435        )
436        plugin = api.add_plugin(
437            placeholder=page.placeholders.get(slot='body'),
438            plugin_type='TextPlugin',
439            language=settings.LANGUAGES[0][0],
440            body='<script>var bar="hacked"</script>'
441        )
442        self.assertEqual(
443            plugin.body,
444            '&lt;script&gt;var bar="hacked"&lt;/script&gt;'
445        )
446
447    def test_copy_plugins_method(self):
448        """
449        Test that CMSPlugin copy does not have side effects
450        """
451        # create some objects
452        page_en = api.create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en")
453        page_de = api.create_page("CopyPluginTestPage (DE)", "nav_playground.html", "de")
454        ph_en = page_en.placeholders.get(slot="body")
455        ph_de = page_de.placeholders.get(slot="body")
456
457        # add the text plugin
458        text_plugin_en = api.add_plugin(ph_en, "TextPlugin", "en", body="Hello World")
459        self.assertEqual(text_plugin_en.pk, CMSPlugin.objects.all()[0].pk)
460
461        # add a *nested* link plugin
462        link_plugin_en = api.add_plugin(ph_en, "LinkPlugin", "en", target=text_plugin_en,
463                                        name="A Link", external_link="https://www.django-cms.org")
464        #
465        text_plugin_en.body += plugin_to_tag(link_plugin_en)
466        text_plugin_en.save()
467
468        # the call above to add a child makes a plugin reload required here.
469        text_plugin_en = self.reload(text_plugin_en)
470
471        # setup the plugins to copy
472        plugins = [text_plugin_en, link_plugin_en]
473        # save the old ids for check
474        old_ids = [plugin.pk for plugin in plugins]
475        new_plugins = []
476        plugins_ziplist = []
477        old_parent_cache = {}
478
479        # This is a stripped down version of cms.copy_plugins.copy_plugins_to
480        # to low-level testing the copy process
481        for plugin in plugins:
482            new_plugins.append(plugin.copy_plugin(ph_de, 'de', old_parent_cache))
483            plugins_ziplist.append((new_plugins[-1], plugin))
484
485        for idx, plugin in enumerate(plugins):
486            inst, _ = new_plugins[idx].get_plugin_instance()
487            new_plugins[idx] = inst
488            new_plugins[idx].post_copy(plugin, plugins_ziplist)
489
490        for idx, plugin in enumerate(plugins):
491            # original plugin instance reference should stay unmodified
492            self.assertEqual(old_ids[idx], plugin.pk)
493            # new plugin instance should be different from the original
494            self.assertNotEqual(new_plugins[idx], plugin.pk)
495
496            # text plugins (both old and new) should contain a reference
497            # to the link plugins
498            if plugin.plugin_type == 'TextPlugin':
499                self.assertTrue('Link - A Link' in plugin.body)
500                self.assertTrue('id="%s"' % plugin.get_children()[0].pk in plugin.body)
501                self.assertTrue('Link - A Link' in new_plugins[idx].body)
502                self.assertTrue('id="%s"' % new_plugins[idx].get_children()[0].pk in new_plugins[idx].body)
503
504    def test_plugin_position(self):
505        page_en = api.create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en")
506        placeholder = page_en.placeholders.get(slot="body")  # ID 2
507        placeholder_right = page_en.placeholders.get(slot="right-column")
508        columns = api.add_plugin(placeholder, "MultiColumnPlugin", "en")  # ID 1
509        column_1 = api.add_plugin(placeholder, "ColumnPlugin", "en", target=columns)  # ID 2
510        column_2 = api.add_plugin(placeholder, "ColumnPlugin", "en", target=columns)  # ID 3
511        first_text_plugin = api.add_plugin(placeholder, "TextPlugin", "en", target=column_1, body="I'm the first")  # ID 4
512        text_plugin = api.add_plugin(placeholder, "TextPlugin", "en", target=column_1, body="I'm the second")  # ID 5
513
514        returned_1 = copy_plugins_to([text_plugin], placeholder, 'en', column_1.pk)  # ID 6
515        returned_2 = copy_plugins_to([text_plugin], placeholder_right, 'en')  # ID 7
516        returned_3 = copy_plugins_to([text_plugin], placeholder, 'en', column_2.pk)  # ID 8
517
518        # STATE AT THIS POINT:
519        # placeholder
520        #     - columns
521        #         - column_1
522        #             - text_plugin "I'm the first"  created here
523        #             - text_plugin "I'm the second" created here
524        #             - text_plugin "I'm the second" (returned_1) copied here
525        #         - column_2
526        #             - text_plugin "I'm the second" (returned_3) copied here
527        # placeholder_right
528        #     - text_plugin "I'm the second" (returned_2) copied here
529
530        # First plugin in the plugin branch
531        self.assertEqual(first_text_plugin.position, 0)
532        # Second plugin in the plugin branch
533        self.assertEqual(text_plugin.position, 1)
534        # Added as third plugin in the same branch as the above
535        self.assertEqual(returned_1[0][0].position, 2)
536        # First plugin in a placeholder
537        self.assertEqual(returned_2[0][0].position, 0)
538        # First plugin nested in a plugin
539        self.assertEqual(returned_3[0][0].position, 0)
540
541    def test_copy_plugins(self):
542        """
543        Test that copying plugins works as expected.
544        """
545        # create some objects
546        page_en = api.create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en")
547        page_de = api.create_page("CopyPluginTestPage (DE)", "nav_playground.html", "de")
548        ph_en = page_en.placeholders.get(slot="body")
549        ph_de = page_de.placeholders.get(slot="body")
550
551        # add the text plugin
552        text_plugin_en = api.add_plugin(ph_en, "TextPlugin", "en", body="Hello World")
553        self.assertEqual(text_plugin_en.pk, CMSPlugin.objects.all()[0].pk)
554
555        # add a *nested* link plugin
556        link_plugin_en = api.add_plugin(ph_en, "LinkPlugin", "en", target=text_plugin_en,
557                                        name="A Link", external_link="https://www.django-cms.org")
558
559        # the call above to add a child makes a plugin reload required here.
560        text_plugin_en = self.reload(text_plugin_en)
561
562        # check the relations
563        self.assertEqual(text_plugin_en.get_children().count(), 1)
564        self.assertEqual(link_plugin_en.parent.pk, text_plugin_en.pk)
565
566        # just sanity check that so far everything went well
567        self.assertEqual(CMSPlugin.objects.count(), 2)
568
569        # copy the plugins to the german placeholder
570        copy_plugins_to(ph_en.get_plugins(), ph_de, 'de')
571
572        self.assertEqual(ph_de.cmsplugin_set.filter(parent=None).count(), 1)
573        text_plugin_de = ph_de.cmsplugin_set.get(parent=None).get_plugin_instance()[0]
574        self.assertEqual(text_plugin_de.get_children().count(), 1)
575        link_plugin_de = text_plugin_de.get_children().get().get_plugin_instance()[0]
576
577        # check we have twice as many plugins as before
578        self.assertEqual(CMSPlugin.objects.count(), 4)
579
580        # check language plugins
581        self.assertEqual(CMSPlugin.objects.filter(language='de').count(), 2)
582        self.assertEqual(CMSPlugin.objects.filter(language='en').count(), 2)
583
584        text_plugin_en = self.reload(text_plugin_en)
585        link_plugin_en = self.reload(link_plugin_en)
586
587        # check the relations in english didn't change
588        self.assertEqual(text_plugin_en.get_children().count(), 1)
589        self.assertEqual(link_plugin_en.parent.pk, text_plugin_en.pk)
590
591        self.assertEqual(link_plugin_de.name, link_plugin_en.name)
592        self.assertEqual(link_plugin_de.external_link, link_plugin_en.external_link)
593
594        self.assertEqual(text_plugin_de.body, text_plugin_en.body)
595
596        # test subplugin copy
597        copy_plugins_to([link_plugin_en], ph_de, 'de')
598
599    def test_deep_copy_plugins(self):
600        page_en = api.create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en")
601        ph_en = page_en.placeholders.get(slot="body")
602
603        # Grid wrapper 1
604        mcol1_en = api.add_plugin(ph_en, "MultiColumnPlugin", "en", position="first-child")
605
606        # Grid column 1.1
607        col1_en = api.add_plugin(ph_en, "ColumnPlugin", "en", position="first-child", target=mcol1_en)
608
609        # Grid column 1.2
610        col2_en = api.add_plugin(ph_en, "ColumnPlugin", "en", position="first-child", target=mcol1_en)
611
612        # add a *nested* link plugin
613        link_plugin_en = api.add_plugin(
614            ph_en,
615            "LinkPlugin",
616            "en",
617            target=col2_en,
618            name="A Link",
619            external_link="https://www.django-cms.org"
620        )
621
622        old_plugins = [mcol1_en, col1_en, col2_en, link_plugin_en]
623
624        page_de = api.create_page("CopyPluginTestPage (DE)", "nav_playground.html", "de")
625        ph_de = page_de.placeholders.get(slot="body")
626
627        # Grid wrapper 1
628        mcol1_de = api.add_plugin(ph_de, "MultiColumnPlugin", "de", position="first-child")
629
630        # Grid column 1.1
631        col1_de = api.add_plugin(ph_de, "ColumnPlugin", "de", position="first-child", target=mcol1_de)
632
633        copy_plugins_to(
634            old_plugins=[mcol1_en, col1_en, col2_en, link_plugin_en],
635            to_placeholder=ph_de,
636            to_language='de',
637            parent_plugin_id=col1_de.pk,
638        )
639
640        col1_de = self.reload(col1_de)
641
642        new_plugins = col1_de.get_descendants().order_by('path')
643
644        self.assertEqual(new_plugins.count(), len(old_plugins))
645
646        for old_plugin, new_plugin in zip(old_plugins, new_plugins):
647            self.assertEqual(old_plugin.numchild, new_plugin.numchild)
648
649        with self.assertNumQueries(FuzzyInt(0, 207)):
650            page_en.publish('en')
651
652    def test_plugin_validation(self):
653        self.assertRaises(ImproperlyConfigured, plugin_pool.validate_templates, NonExisitngRenderTemplate)
654        self.assertRaises(ImproperlyConfigured, plugin_pool.validate_templates, NoRender)
655        self.assertRaises(ImproperlyConfigured, plugin_pool.validate_templates, NoRenderButChildren)
656        plugin_pool.validate_templates(DynTemplate)
657
658    def test_remove_plugin_before_published(self):
659        """
660        When removing a draft plugin we would expect the public copy of the plugin to also be removed
661        """
662        # add a page
663        page = api.create_page(
664             title='test page',
665            language=settings.LANGUAGES[0][0],
666            template='nav_playground.html'
667        )
668        plugin = api.add_plugin(
669            placeholder=page.placeholders.get(slot='body'),
670            language='en',
671            plugin_type='TextPlugin',
672            body=''
673        )
674        # there should be only 1 plugin
675        self.assertEqual(CMSPlugin.objects.all().count(), 1)
676
677        # delete the plugin
678        plugin_data = {
679            'plugin_id': plugin.pk
680        }
681
682        endpoint = self.get_admin_url(Page, 'delete_plugin', plugin.pk)
683        endpoint += '?cms_path=/en/'
684
685        response = self.client.post(endpoint, plugin_data)
686        self.assertEqual(response.status_code, 302)
687        # there should be no plugins
688        self.assertEqual(0, CMSPlugin.objects.all().count())
689
690    def test_remove_plugin_after_published(self):
691        # add a page
692        page = api.create_page("home", "nav_playground.html", "en")
693
694        # add a plugin
695        plugin = api.add_plugin(
696            placeholder=page.placeholders.get(slot='body'),
697            plugin_type='TextPlugin',
698            language=settings.LANGUAGES[0][0],
699            body=''
700        )
701        # there should be only 1 plugin
702        self.assertEqual(CMSPlugin.objects.all().count(), 1)
703        self.assertEqual(CMSPlugin.objects.filter(placeholder__page__publisher_is_draft=True).count(), 1)
704
705        # publish page
706        response = self.client.post(URL_CMS_PAGE + "%d/en/publish/" % page.pk, {1: 1})
707        self.assertEqual(response.status_code, 302)
708        self.assertEqual(Page.objects.count(), 2)
709
710        # there should now be two plugins - 1 draft, 1 public
711        self.assertEqual(CMSPlugin.objects.all().count(), 2)
712
713        # delete the plugin
714        plugin_data = {
715            'plugin_id': plugin.pk
716        }
717
718        endpoint = self.get_admin_url(Page, 'delete_plugin', plugin.pk)
719        endpoint += '?cms_path=/en/'
720
721        response = self.client.post(endpoint, plugin_data)
722        self.assertEqual(response.status_code, 302)
723
724        # there should be no plugins
725        self.assertEqual(CMSPlugin.objects.all().count(), 1)
726        self.assertEqual(CMSPlugin.objects.filter(placeholder__page__publisher_is_draft=False).count(), 1)
727
728    def test_remove_plugin_not_associated_to_page(self):
729        """
730        Test case for PlaceholderField
731        """
732        page = api.create_page(
733            title='test page',
734            template='nav_playground.html',
735            language='en'
736        )
737        # add a plugin
738        plugin = api.add_plugin(
739            placeholder=page.placeholders.get(slot='body'),
740            plugin_type='TextPlugin',
741            language=settings.LANGUAGES[0][0],
742            body=''
743        )
744        # there should be only 1 plugin
745        self.assertEqual(CMSPlugin.objects.all().count(), 1)
746
747        ph = Placeholder(slot="subplugin")
748        ph.save()
749        url = URL_CMS_PLUGIN_ADD + '?' + urlencode({
750            'plugin_type': "TextPlugin",
751            'target_language': settings.LANGUAGES[0][0],
752            'placeholder': ph.pk,
753            'plugin_parent': plugin.pk
754
755        })
756        response = self.client.post(url, {'body': ''})
757        # no longer allowed for security reasons
758        self.assertEqual(response.status_code, 400)
759
760    def test_register_plugin_twice_should_raise(self):
761        number_of_plugins_before = len(plugin_pool.registered_plugins)
762        # The first time we register the plugin is should work
763        with register_plugins(DumbFixturePlugin):
764            # Let's add it a second time. We should catch and exception
765            raised = False
766            try:
767                plugin_pool.register_plugin(DumbFixturePlugin)
768            except PluginAlreadyRegistered:
769                raised = True
770            self.assertTrue(raised)
771        # Let's make sure we have the same number of plugins as before:
772        number_of_plugins_after = len(plugin_pool.registered_plugins)
773        self.assertEqual(number_of_plugins_before, number_of_plugins_after)
774
775    def test_unregister_non_existing_plugin_should_raise(self):
776        number_of_plugins_before = len(plugin_pool.registered_plugins)
777        raised = False
778        try:
779            # There should not be such a plugin registered if the others tests
780            # don't leak plugins
781            plugin_pool.unregister_plugin(DumbFixturePlugin)
782        except PluginNotRegistered:
783            raised = True
784        self.assertTrue(raised)
785        # Let's count, to make sure we didn't remove a plugin accidentally.
786        number_of_plugins_after = len(plugin_pool.registered_plugins)
787        self.assertEqual(number_of_plugins_before, number_of_plugins_after)
788
789    def test_search_pages(self):
790        """
791        Test search for pages
792        To be fully useful, this testcase needs to have the following different
793        Plugin configurations within the project:
794            * unaltered cmsplugin_ptr
795            * cmsplugin_ptr with related_name='+'
796            * cmsplugin_ptr with related_query_name='+'
797            * cmsplugin_ptr with related_query_name='whatever_foo'
798            * cmsplugin_ptr with related_name='whatever_bar'
799            * cmsplugin_ptr with related_query_name='whatever_foo' and related_name='whatever_bar'
800        Those plugins are in cms/test_utils/project/pluginapp/revdesc/models.py
801        """
802        page = api.create_page("page", "nav_playground.html", "en")
803
804        placeholder = page.placeholders.get(slot='body')
805        text = Text(body="hello", language="en", placeholder=placeholder, plugin_type="TextPlugin", position=1)
806        text.save()
807        page.publish('en')
808        self.assertEqual(Page.objects.search("hi").count(), 0)
809        self.assertEqual(Page.objects.search("hello").count(), 1)
810
811    def test_empty_plugin_is_not_ignored(self):
812        page = api.create_page("page", "nav_playground.html", "en")
813
814        placeholder = page.placeholders.get(slot='body')
815
816        plugin = CMSPlugin(
817            plugin_type='TextPlugin',
818            placeholder=placeholder,
819            position=1,
820            language=self.FIRST_LANG)
821        plugin.add_root(instance=plugin)
822
823        # this should not raise any errors, but just ignore the empty plugin
824        out = _render_placeholder(placeholder, self.get_context(), width=300)
825        self.assertFalse(len(out))
826        self.assertFalse(len(placeholder._plugins_cache))
827
828    def test_repr(self):
829        non_saved_plugin = CMSPlugin()
830        self.assertIsNone(non_saved_plugin.pk)
831        self.assertIn('id=None', repr(non_saved_plugin))
832        self.assertIn("plugin_type=''", repr(non_saved_plugin))
833
834        saved_plugin = CMSPlugin.objects.create(plugin_type='TextPlugin')
835        self.assertIn('id={}'.format(saved_plugin.pk), repr(saved_plugin))
836        self.assertIn("plugin_type='{}'".format(saved_plugin.plugin_type), repr(saved_plugin))
837
838
839    def test_pickle(self):
840        page = api.create_page("page", "nav_playground.html", "en")
841        placeholder = page.placeholders.get(slot='body')
842        text_plugin = api.add_plugin(
843            placeholder,
844            "TextPlugin",
845            'en',
846            body="Hello World",
847        )
848        cms_plugin = text_plugin.cmsplugin_ptr
849
850        # assert we can pickle and unpickle a solid plugin (subclass)
851        self.assertEqual(text_plugin, pickle.loads(pickle.dumps(text_plugin)))
852
853        # assert we can pickle and unpickle a cms plugin (parent)
854        self.assertEqual(cms_plugin, pickle.loads(pickle.dumps(cms_plugin)))
855
856    def test_defer_pickle(self):
857        page = api.create_page("page", "nav_playground.html", "en")
858
859        placeholder = page.placeholders.get(slot='body')
860        api.add_plugin(placeholder, "TextPlugin", 'en', body="Hello World")
861        plugins = Text.objects.all().defer('path')
862        import io
863        a = io.BytesIO()
864        pickle.dump(plugins[0], a)
865
866    def test_empty_plugin_description(self):
867        page = api.create_page("page", "nav_playground.html", "en")
868
869        placeholder = page.placeholders.get(slot='body')
870        a = CMSPlugin(
871            plugin_type='TextPlugin',
872            placeholder=placeholder,
873            position=1,
874            language=self.FIRST_LANG
875        )
876
877        self.assertEqual(a.get_short_description(), "<Empty>")
878
879    def test_page_attribute_warns(self):
880        page = api.create_page("page", "nav_playground.html", "en")
881
882        placeholder = page.placeholders.get(slot='body')
883        a = CMSPlugin(
884            plugin_type='TextPlugin',
885            placeholder=placeholder,
886            position=1,
887            language=self.FIRST_LANG
888        )
889        a.save()
890
891        def get_page(plugin):
892            return plugin.page
893
894        self.assertWarns(
895            DontUsePageAttributeWarning,
896            "Don't use the page attribute on CMSPlugins! CMSPlugins are not guaranteed to have a page associated with them!",
897            get_page, a
898        )
899
900        with warnings.catch_warnings(record=True) as w:
901            warnings.simplefilter('always')
902            a.page
903            self.assertEqual(1, len(w))
904            self.assertIn('test_plugins.py', w[0].filename)
905
906    def test_editing_plugin_changes_page_modification_time_in_sitemap(self):
907        now = timezone.now()
908        one_day_ago = now - datetime.timedelta(days=1)
909        page = api.create_page("page", "nav_playground.html", "en", published=True)
910        title = page.get_title_obj('en')
911        page.creation_date = one_day_ago
912        page.changed_date = one_day_ago
913        plugin_id = self._create_link_plugin_on_page(page, slot='body')
914        plugin = self.__edit_link_plugin(plugin_id, "fnord")
915
916        actual_last_modification_time = CMSSitemap().lastmod(title)
917        actual_last_modification_time -= datetime.timedelta(microseconds=actual_last_modification_time.microsecond)
918        self.assertEqual(plugin.changed_date.date(), actual_last_modification_time.date())
919
920    def test_moving_plugin_to_different_placeholder(self):
921        with register_plugins(DumbFixturePlugin):
922            page = api.create_page(
923                "page",
924                "nav_playground.html",
925                "en"
926            )
927            plugin = api.add_plugin(
928                placeholder=page.placeholders.get(slot='body'),
929                plugin_type='DumbFixturePlugin',
930                language=settings.LANGUAGES[0][0]
931            )
932            child_plugin = api.add_plugin(
933                placeholder=page.placeholders.get(slot='body'),
934                plugin_type='DumbFixturePlugin',
935                language=settings.LANGUAGES[0][0],
936                parent=plugin
937            )
938            post = {
939                'plugin_id': child_plugin.pk,
940                'placeholder_id': page.placeholders.get(slot='right-column').pk,
941                'target_language': 'en',
942                'plugin_parent': '',
943            }
944
945            endpoint = self.get_move_plugin_uri(child_plugin)
946            response = self.client.post(endpoint, post)
947            self.assertEqual(response.status_code, 200)
948
949            from cms.utils.plugins import build_plugin_tree
950            build_plugin_tree(page.placeholders.get(slot='right-column').get_plugins_list())
951
952    def test_custom_plugin_urls(self):
953        plugin_url = reverse('admin:dumbfixtureplugin')
954
955        response = self.client.get(plugin_url)
956        self.assertEqual(response.status_code, 200)
957        self.assertEqual(response.content, b"It works")
958
959    def test_plugin_require_parent(self):
960        """
961        Assert that a plugin marked as 'require_parent' is not listed
962        in the plugin pool when a placeholder is specified
963        """
964        ParentRequiredPlugin = type('ParentRequiredPlugin', (CMSPluginBase,),
965                                    dict(require_parent=True, render_plugin=False))
966
967        with register_plugins(ParentRequiredPlugin):
968            page = api.create_page("page", "nav_playground.html", "en", published=True)
969            placeholder = page.placeholders.get(slot='body')
970
971            plugin_list = plugin_pool.get_all_plugins(placeholder=placeholder, page=page)
972            self.assertFalse(ParentRequiredPlugin in plugin_list)
973
974    def test_plugin_toolbar_struct(self):
975        # Tests that the output of the plugin toolbar structure.
976        page = api.create_page("page", "nav_playground.html", "en", published=True)
977        placeholder = page.placeholders.get(slot='body')
978
979        from cms.utils.placeholder import get_toolbar_plugin_struct
980
981        expected_struct_en = {
982            'module': u'Generic',
983            'name': u'Style',
984            'value': 'StylePlugin',
985        }
986
987        expected_struct_de = {
988            'module': u'Generisch',
989            'name': u'Style',
990            'value': 'StylePlugin',
991        }
992
993        toolbar_struct = get_toolbar_plugin_struct(
994            plugins=plugin_pool.registered_plugins,
995            slot=placeholder.slot,
996            page=page,
997        )
998
999        style_config = [config for config in toolbar_struct if config['value'] == 'StylePlugin']
1000
1001        self.assertEqual(len(style_config), 1)
1002
1003        style_config = style_config[0]
1004
1005        with force_language('en'):
1006            self.assertEqual(force_text(style_config['module']), expected_struct_en['module'])
1007            self.assertEqual(force_text(style_config['name']), expected_struct_en['name'])
1008
1009        with force_language('de'):
1010            self.assertEqual(force_text(style_config['module']), expected_struct_de['module'])
1011            self.assertEqual(force_text(style_config['name']), expected_struct_de['name'])
1012
1013    def test_plugin_toolbar_struct_permissions(self):
1014        page = self.get_permissions_test_page()
1015        staff_user = self.get_staff_user_with_no_permissions()
1016        placeholder = page.placeholders.get(slot='body')
1017        page_url = page.get_absolute_url() + '?' + get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
1018
1019        self.add_permission(staff_user, 'change_page')
1020        self.add_permission(staff_user, 'add_text')
1021
1022        with self.login_user_context(staff_user):
1023            request = self.get_request(page_url, page=page)
1024            request.toolbar = CMSToolbar(request)
1025            renderer = self.get_structure_renderer(request=request)
1026            output = renderer.render_placeholder(placeholder, language='en', page=page)
1027            self.assertIn('<a data-rel="add" href="TextPlugin">Text</a>', output)
1028            self.assertNotIn('<a data-rel="add" href="LinkPlugin">Link</a>', output)
1029
1030    def test_plugin_child_classes_from_settings(self):
1031        page = api.create_page("page", "nav_playground.html", "en", published=True)
1032        placeholder = page.placeholders.get(slot='body')
1033        ChildClassesPlugin = type('ChildClassesPlugin', (CMSPluginBase,),
1034                                    dict(child_classes=['TextPlugin'], render_template='allow_children_plugin.html'))
1035
1036        with register_plugins(ChildClassesPlugin):
1037            plugin = api.add_plugin(placeholder, ChildClassesPlugin, settings.LANGUAGES[0][0])
1038            plugin = plugin.get_plugin_class_instance()
1039            ## assert baseline
1040            self.assertEqual(['TextPlugin'], plugin.get_child_classes(placeholder.slot, page))
1041
1042            CMS_PLACEHOLDER_CONF = {
1043                'body': {
1044                    'child_classes': {
1045                        'ChildClassesPlugin': ['LinkPlugin', 'PicturePlugin'],
1046                    }
1047                }
1048            }
1049            with self.settings(CMS_PLACEHOLDER_CONF=CMS_PLACEHOLDER_CONF):
1050                self.assertEqual(['LinkPlugin', 'PicturePlugin'],
1051                                    plugin.get_child_classes(placeholder.slot, page))
1052
1053    def test_plugin_parent_classes_from_settings(self):
1054        page = api.create_page("page", "nav_playground.html", "en", published=True)
1055        placeholder = page.placeholders.get(slot='body')
1056        ParentClassesPlugin = type('ParentClassesPlugin', (CMSPluginBase,),
1057                                    dict(parent_classes=['TextPlugin'], render_plugin=False))
1058
1059        with register_plugins(ParentClassesPlugin):
1060            plugin = api.add_plugin(placeholder, ParentClassesPlugin, settings.LANGUAGES[0][0])
1061            plugin = plugin.get_plugin_class_instance()
1062            ## assert baseline
1063            self.assertEqual(['TextPlugin'], plugin.get_parent_classes(placeholder.slot, page))
1064
1065            CMS_PLACEHOLDER_CONF = {
1066                'body': {
1067                    'parent_classes': {
1068                        'ParentClassesPlugin': ['TestPlugin'],
1069                    }
1070                }
1071            }
1072            with self.settings(CMS_PLACEHOLDER_CONF=CMS_PLACEHOLDER_CONF):
1073                self.assertEqual(['TestPlugin'],
1074                                    plugin.get_parent_classes(placeholder.slot, page))
1075
1076    def test_plugin_parent_classes_from_object(self):
1077        page = api.create_page("page", "nav_playground.html", "en", published=True)
1078        placeholder = page.placeholders.get(slot='body')
1079        ParentPlugin = type('ParentPlugin', (CMSPluginBase,),
1080                                    dict(render_plugin=False))
1081        ChildPlugin = type('ChildPlugin', (CMSPluginBase,),
1082                                    dict(parent_classes=['ParentPlugin'], render_plugin=False))
1083
1084        with register_plugins(ParentPlugin, ChildPlugin):
1085            plugin = api.add_plugin(placeholder, ParentPlugin, settings.LANGUAGES[0][0])
1086            plugin = plugin.get_plugin_class_instance()
1087            ## assert baseline
1088            child_classes = plugin.get_child_classes(placeholder.slot, page)
1089            self.assertIn('ChildPlugin', child_classes)
1090            self.assertIn('ParentPlugin', child_classes)
1091
1092    def test_plugin_require_parent_from_object(self):
1093        page = api.create_page("page", "nav_playground.html", "en", published=True)
1094        placeholder = page.placeholders.get(slot='body')
1095        ParentPlugin = type('ParentPlugin', (CMSPluginBase,),
1096                                    dict(render_plugin=False))
1097        ChildPlugin = type('ChildPlugin', (CMSPluginBase,),
1098                                    dict(require_parent=True, render_plugin=False))
1099
1100        with register_plugins(ParentPlugin, ChildPlugin):
1101            plugin = api.add_plugin(placeholder, ParentPlugin, settings.LANGUAGES[0][0])
1102            plugin = plugin.get_plugin_class_instance()
1103            ## assert baseline
1104            child_classes = plugin.get_child_classes(placeholder.slot, page)
1105            self.assertIn('ChildPlugin', child_classes)
1106            self.assertIn('ParentPlugin', child_classes)
1107
1108    def test_plugin_pool_register_returns_plugin_class(self):
1109        @plugin_pool.register_plugin
1110        class DecoratorTestPlugin(CMSPluginBase):
1111            render_plugin = False
1112            name = "Test Plugin"
1113        self.assertIsNotNone(DecoratorTestPlugin)
1114
1115
1116class PluginManyToManyTestCase(PluginsTestBaseCase):
1117    def setUp(self):
1118        self.super_user = self._create_user("test", True, True)
1119        self.slave = self._create_user("slave", True)
1120
1121        self._login_context = self.login_user_context(self.super_user)
1122        self._login_context.__enter__()
1123
1124        # create 3 sections
1125        self.sections = []
1126        self.section_pks = []
1127        for i in range(3):
1128            section = Section.objects.create(name="section %s" % i)
1129            self.sections.append(section)
1130            self.section_pks.append(section.pk)
1131        self.section_count = len(self.sections)
1132        # create 10 articles by section
1133        for section in self.sections:
1134            for j in range(10):
1135                Article.objects.create(
1136                    title="article %s" % j,
1137                    section=section
1138                )
1139        self.FIRST_LANG = settings.LANGUAGES[0][0]
1140        self.SECOND_LANG = settings.LANGUAGES[1][0]
1141
1142    def test_dynamic_plugin_template(self):
1143        page_en = api.create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en")
1144        ph_en = page_en.placeholders.get(slot="body")
1145        api.add_plugin(ph_en, "ArticleDynamicTemplatePlugin", "en", title="a title")
1146        api.add_plugin(ph_en, "ArticleDynamicTemplatePlugin", "en", title="custom template")
1147        context = self.get_context(path=page_en.get_absolute_url())
1148        request = context['request']
1149        plugins = get_plugins(request, ph_en, page_en.template)
1150        content_renderer = self.get_content_renderer()
1151
1152        for plugin in plugins:
1153            if plugin.title == 'custom template':
1154                content = content_renderer.render_plugin(plugin, context, ph_en)
1155                self.assertEqual(plugin.get_plugin_class_instance().get_render_template({}, plugin, ph_en), 'articles_custom.html')
1156                self.assertTrue('Articles Custom template' in content)
1157            else:
1158                content = content_renderer.render_plugin(plugin, context, ph_en)
1159                self.assertEqual(plugin.get_plugin_class_instance().get_render_template({}, plugin, ph_en), 'articles.html')
1160                self.assertFalse('Articles Custom template' in content)
1161
1162    def test_add_plugin_with_m2m(self):
1163        # add a new text plugin
1164        self.assertEqual(ArticlePluginModel.objects.count(), 0)
1165        page_data = self.get_new_page_data()
1166        self.client.post(URL_CMS_PAGE_ADD, page_data)
1167        page = Page.objects.drafts().first()
1168        page.publish('en')
1169        placeholder = page.placeholders.get(slot='col_left')
1170        add_url = self.get_add_plugin_uri(
1171            placeholder=placeholder,
1172            plugin_type='ArticlePlugin',
1173            language=self.FIRST_LANG,
1174        )
1175        data = {
1176            'title': "Articles Plugin 1",
1177            "sections": self.section_pks
1178        }
1179        response = self.client.post(add_url, data)
1180        self.assertEqual(response.status_code, 200)
1181        self.assertEqual(ArticlePluginModel.objects.count(), 1)
1182        plugin = ArticlePluginModel.objects.all()[0]
1183        self.assertEqual(self.section_count, plugin.sections.count())
1184        response = self.client.get('/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON'))
1185        self.assertEqual(response.status_code, 200)
1186        self.assertEqual(plugin.sections.through._meta.db_table, 'manytomany_rel_articlepluginmodel_sections')
1187
1188    def test_add_plugin_with_m2m_and_publisher(self):
1189        self.assertEqual(ArticlePluginModel.objects.count(), 0)
1190        page_data = self.get_new_page_data()
1191        response = self.client.post(URL_CMS_PAGE_ADD, page_data)
1192        self.assertEqual(response.status_code, 302)
1193        page = Page.objects.drafts().first()
1194        placeholder = page.placeholders.get(slot='col_left')
1195
1196        # add a plugin
1197        data = {
1198            'title': "Articles Plugin 1",
1199            'sections': self.section_pks
1200        }
1201
1202        add_url = self.get_add_plugin_uri(
1203            placeholder=placeholder,
1204            plugin_type='ArticlePlugin',
1205            language=self.FIRST_LANG,
1206        )
1207
1208        response = self.client.post(add_url, data)
1209        self.assertEqual(response.status_code, 200)
1210        self.assertTemplateUsed(response, 'admin/cms/page/plugin/confirm_form.html')
1211
1212        # there should be only 1 plugin
1213        self.assertEqual(1, CMSPlugin.objects.all().count())
1214        self.assertEqual(1, ArticlePluginModel.objects.count())
1215        articles_plugin = ArticlePluginModel.objects.all()[0]
1216        self.assertEqual(u'Articles Plugin 1', articles_plugin.title)
1217        self.assertEqual(self.section_count, articles_plugin.sections.count())
1218
1219        # check publish box
1220        api.publish_page(page, self.super_user, 'en')
1221
1222        # there should now be two plugins - 1 draft, 1 public
1223        self.assertEqual(2, CMSPlugin.objects.all().count())
1224        self.assertEqual(2, ArticlePluginModel.objects.all().count())
1225
1226        db_counts = [plugin.sections.count() for plugin in ArticlePluginModel.objects.all()]
1227        expected = [self.section_count for i in range(len(db_counts))]
1228        self.assertEqual(expected, db_counts)
1229
1230    def test_copy_plugin_with_m2m(self):
1231        page = api.create_page("page", "nav_playground.html", "en")
1232
1233        placeholder = page.placeholders.get(slot='body')
1234
1235        plugin = ArticlePluginModel(
1236            plugin_type='ArticlePlugin',
1237            placeholder=placeholder,
1238            position=1,
1239            language=self.FIRST_LANG)
1240        plugin.add_root(instance=plugin)
1241
1242        endpoint = self.get_admin_url(Page, 'edit_plugin', plugin.pk)
1243        endpoint += '?cms_path=/{}/'.format(self.FIRST_LANG)
1244
1245        data = {
1246            'title': "Articles Plugin 1",
1247            "sections": self.section_pks
1248        }
1249        response = self.client.post(endpoint, data)
1250        self.assertEqual(response.status_code, 200)
1251        self.assertEqual(ArticlePluginModel.objects.count(), 1)
1252
1253        self.assertEqual(ArticlePluginModel.objects.all()[0].sections.count(), self.section_count)
1254
1255        page_data = self.get_new_page_data()
1256
1257        #create 2nd language page
1258        page_data.update({
1259            'language': self.SECOND_LANG,
1260            'title': "%s %s" % (page.get_title(), self.SECOND_LANG),
1261        })
1262
1263        response = self.client.post(URL_CMS_PAGE_CHANGE % page.pk + "?language=%s" % self.SECOND_LANG, page_data)
1264        self.assertRedirects(response, URL_CMS_PAGE + "?language=%s" % self.SECOND_LANG)
1265
1266        self.assertEqual(CMSPlugin.objects.filter(language=self.FIRST_LANG).count(), 1)
1267        self.assertEqual(CMSPlugin.objects.filter(language=self.SECOND_LANG).count(), 0)
1268        self.assertEqual(CMSPlugin.objects.count(), 1)
1269        self.assertEqual(Page.objects.all().count(), 1)
1270
1271        copy_data = {
1272            'source_placeholder_id': placeholder.pk,
1273            'target_placeholder_id': placeholder.pk,
1274            'target_language': self.SECOND_LANG,
1275            'source_language': self.FIRST_LANG,
1276        }
1277
1278        endpoint = self.get_admin_url(Page, 'copy_plugins')
1279        endpoint += '?cms_path=/{}/'.format(self.FIRST_LANG)
1280
1281        response = self.client.post(endpoint, copy_data)
1282        self.assertEqual(response.status_code, 200)
1283        self.assertEqual(response.content.decode('utf8').count('"plugin_type": "ArticlePlugin"'), 1)
1284        # assert copy success
1285        self.assertEqual(CMSPlugin.objects.filter(language=self.FIRST_LANG).count(), 1)
1286        self.assertEqual(CMSPlugin.objects.filter(language=self.SECOND_LANG).count(), 1)
1287        self.assertEqual(CMSPlugin.objects.count(), 2)
1288        db_counts = [plgn.sections.count() for plgn in ArticlePluginModel.objects.all()]
1289        expected = [self.section_count for _ in range(len(db_counts))]
1290        self.assertEqual(expected, db_counts)
1291
1292
1293class PluginCopyRelationsTestCase(PluginsTestBaseCase):
1294    """Test the suggestions in the docs for copy_relations()"""
1295
1296    def setUp(self):
1297        self.super_user = self._create_user("test", True, True)
1298        self.FIRST_LANG = settings.LANGUAGES[0][0]
1299        self._login_context = self.login_user_context(self.super_user)
1300        self._login_context.__enter__()
1301        page_data1 = self.get_new_page_data_dbfields()
1302        page_data1['published'] = False
1303        self.page1 = api.create_page(**page_data1)
1304        page_data2 = self.get_new_page_data_dbfields()
1305        page_data2['published'] = False
1306        self.page2 = api.create_page(**page_data2)
1307        self.placeholder1 = self.page1.placeholders.get(slot='body')
1308        self.placeholder2 = self.page2.placeholders.get(slot='body')
1309
1310    def test_copy_fk_from_model(self):
1311        plugin = api.add_plugin(
1312            placeholder=self.placeholder1,
1313            plugin_type="PluginWithFKFromModel",
1314            language=self.FIRST_LANG,
1315        )
1316        FKModel.objects.create(fk_field=plugin)
1317        old_public_count = FKModel.objects.filter(
1318            fk_field__placeholder__page__publisher_is_draft=False
1319        ).count()
1320        api.publish_page(
1321            self.page1,
1322            self.super_user,
1323            self.FIRST_LANG
1324        )
1325        new_public_count = FKModel.objects.filter(
1326            fk_field__placeholder__page__publisher_is_draft=False
1327        ).count()
1328        self.assertEqual(
1329            new_public_count,
1330            old_public_count + 1
1331        )
1332
1333    def test_copy_m2m_to_model(self):
1334        plugin = api.add_plugin(
1335            placeholder=self.placeholder1,
1336            plugin_type="PluginWithM2MToModel",
1337            language=self.FIRST_LANG,
1338        )
1339        m2m_target = M2MTargetModel.objects.create()
1340        plugin.m2m_field.add(m2m_target)
1341        old_public_count = M2MTargetModel.objects.filter(
1342            pluginmodelwithm2mtomodel__placeholder__page__publisher_is_draft=False
1343        ).count()
1344        api.publish_page(
1345            self.page1,
1346            self.super_user,
1347            self.FIRST_LANG
1348        )
1349        new_public_count = M2MTargetModel.objects.filter(
1350            pluginmodelwithm2mtomodel__placeholder__page__publisher_is_draft=False
1351        ).count()
1352        self.assertEqual(
1353            new_public_count,
1354            old_public_count + 1
1355        )
1356
1357
1358class PluginsMetaOptionsTests(TestCase):
1359    ''' TestCase set for ensuring that bugs like #992 are caught '''
1360
1361    # these plugins are inlined because, due to the nature of the #992
1362    # ticket, we cannot actually import a single file with all the
1363    # plugin variants in, because that calls __new__, at which point the
1364    # error with splitted occurs.
1365
1366    def test_meta_options_as_defaults(self):
1367        ''' handling when a CMSPlugin meta options are computed defaults '''
1368        # this plugin relies on the base CMSPlugin and Model classes to
1369        # decide what the app_label and db_table should be
1370
1371        plugin = TestPlugin.model
1372        self.assertEqual(plugin._meta.db_table, 'meta_testpluginmodel')
1373        self.assertEqual(plugin._meta.app_label, 'meta')
1374
1375    def test_meta_options_as_declared_defaults(self):
1376        ''' handling when a CMSPlugin meta options are declared as per defaults '''
1377        # here, we declare the db_table and app_label explicitly, but to the same
1378        # values as would be computed, thus making sure it's not a problem to
1379        # supply options.
1380
1381        plugin = TestPlugin2.model
1382        self.assertEqual(plugin._meta.db_table, 'meta_testpluginmodel2')
1383        self.assertEqual(plugin._meta.app_label, 'meta')
1384
1385    def test_meta_options_custom_app_label(self):
1386        ''' make sure customised meta options on CMSPlugins don't break things '''
1387
1388        plugin = TestPlugin3.model
1389        self.assertEqual(plugin._meta.db_table, 'one_thing_testpluginmodel3')
1390        self.assertEqual(plugin._meta.app_label, 'one_thing')
1391
1392    def test_meta_options_custom_db_table(self):
1393        ''' make sure custom database table names are OK. '''
1394
1395        plugin = TestPlugin4.model
1396        self.assertEqual(plugin._meta.db_table, 'or_another_4')
1397        self.assertEqual(plugin._meta.app_label, 'meta')
1398
1399    def test_meta_options_custom_both(self):
1400        ''' We should be able to customise app_label and db_table together '''
1401
1402        plugin = TestPlugin5.model
1403        self.assertEqual(plugin._meta.db_table, 'or_another_5')
1404        self.assertEqual(plugin._meta.app_label, 'one_thing')
1405
1406
1407class NoDatabasePluginTests(TestCase):
1408
1409    def get_plugin_model(self, plugin_type):
1410        return plugin_pool.get_plugin(plugin_type).model
1411
1412    def test_render_meta_is_unique(self):
1413        text = self.get_plugin_model('TextPlugin')
1414        link = self.get_plugin_model('LinkPlugin')
1415        self.assertNotEqual(id(text._render_meta), id(link._render_meta))
1416
1417    def test_render_meta_does_not_leak(self):
1418        text = self.get_plugin_model('TextPlugin')
1419        link = self.get_plugin_model('LinkPlugin')
1420
1421        text._render_meta.text_enabled = False
1422        link._render_meta.text_enabled = False
1423
1424        self.assertFalse(text._render_meta.text_enabled)
1425        self.assertFalse(link._render_meta.text_enabled)
1426
1427        link._render_meta.text_enabled = True
1428
1429        self.assertFalse(text._render_meta.text_enabled)
1430        self.assertTrue(link._render_meta.text_enabled)
1431
1432    def test_db_table_hack(self):
1433        # Plugin models have been moved away due to Django's AppConfig
1434        from cms.test_utils.project.bunch_of_plugins.models import TestPlugin1
1435        self.assertEqual(TestPlugin1._meta.db_table, 'bunch_of_plugins_testplugin1')
1436
1437    def test_db_table_hack_with_mixin(self):
1438        # Plugin models have been moved away due to Django's AppConfig
1439        from cms.test_utils.project.bunch_of_plugins.models import TestPlugin2
1440        self.assertEqual(TestPlugin2._meta.db_table, 'bunch_of_plugins_testplugin2')
1441
1442
1443class SimplePluginTests(TestCase):
1444
1445    def test_simple_naming(self):
1446        class MyPlugin(CMSPluginBase):
1447            render_template = 'base.html'
1448
1449        self.assertEqual(MyPlugin.name, 'My Plugin')
1450
1451    def test_simple_context(self):
1452        class MyPlugin(CMSPluginBase):
1453            render_template = 'base.html'
1454
1455        plugin = MyPlugin(ArticlePluginModel, admin.site)
1456        context = {}
1457        out_context = plugin.render(context, 1, 2)
1458        self.assertEqual(out_context['instance'], 1)
1459        self.assertEqual(out_context['placeholder'], 2)
1460        self.assertIs(out_context, context)
1461
1462
1463class BrokenPluginTests(TestCase):
1464    def test_import_broken_plugin(self):
1465        """
1466        If there is an import error in the actual cms_plugin file it should
1467        raise the ImportError rather than silently swallowing it -
1468        in opposition to the ImportError if the file 'cms_plugins.py' doesn't
1469        exist.
1470        """
1471        new_apps = ['cms.test_utils.project.brokenpluginapp']
1472        with self.settings(INSTALLED_APPS=new_apps):
1473            plugin_pool.discovered = False
1474            self.assertRaises(ImportError, plugin_pool.discover_plugins)
1475
1476
1477class MTIPluginsTestCase(PluginsTestBaseCase):
1478    def test_add_edit_plugin(self):
1479        from cms.test_utils.project.mti_pluginapp.models import TestPluginBetaModel
1480
1481        """
1482        Test that we can instantiate and use a MTI plugin
1483        """
1484
1485        # Create a page
1486        page = create_page("Test", "nav_playground.html", settings.LANGUAGES[0][0])
1487        placeholder = page.placeholders.get(slot='body')
1488
1489        # Add the MTI plugin
1490        add_url = self.get_add_plugin_uri(
1491            placeholder=placeholder,
1492            plugin_type='TestPluginBeta',
1493            language=settings.LANGUAGES[0][0],
1494        )
1495
1496        data = {
1497            'alpha': 'ALPHA',
1498            'beta': 'BETA'
1499        }
1500        response = self.client.post(add_url, data)
1501        self.assertEqual(response.status_code, 200)
1502        self.assertEqual(TestPluginBetaModel.objects.count(), 1)
1503        plugin_model = TestPluginBetaModel.objects.all()[0]
1504        self.assertEqual("ALPHA", plugin_model.alpha)
1505        self.assertEqual("BETA", plugin_model.beta)
1506
1507    def test_related_name(self):
1508        from cms.test_utils.project.mti_pluginapp.models import (
1509            TestPluginAlphaModel, TestPluginBetaModel, ProxiedAlphaPluginModel,
1510            ProxiedBetaPluginModel, AbstractPluginParent, TestPluginGammaModel, MixedPlugin,
1511            LessMixedPlugin, NonPluginModel
1512        )
1513        # the first concrete class of the following four plugins is TestPluginAlphaModel
1514        self.assertEqual(TestPluginAlphaModel.cmsplugin_ptr.field.remote_field.related_name,
1515                         'mti_pluginapp_testpluginalphamodel')
1516        self.assertEqual(TestPluginBetaModel.cmsplugin_ptr.field.remote_field.related_name,
1517                         'mti_pluginapp_testpluginalphamodel')
1518        self.assertEqual(ProxiedAlphaPluginModel.cmsplugin_ptr.field.remote_field.related_name,
1519                         'mti_pluginapp_testpluginalphamodel')
1520        self.assertEqual(ProxiedBetaPluginModel.cmsplugin_ptr.field.remote_field.related_name,
1521                         'mti_pluginapp_testpluginalphamodel')
1522        # Abstract plugins will have the dynamic format for related name
1523        self.assertEqual(
1524            AbstractPluginParent.cmsplugin_ptr.field.remote_field.related_name,
1525            '%(app_label)s_%(class)s'
1526        )
1527        # Concrete plugin of an abstract plugin gets its relatedname
1528        self.assertEqual(TestPluginGammaModel.cmsplugin_ptr.field.remote_field.related_name,
1529                         'mti_pluginapp_testplugingammamodel')
1530        # Child plugin gets it's own related name
1531        self.assertEqual(MixedPlugin.cmsplugin_ptr.field.remote_field.related_name,
1532                         'mti_pluginapp_mixedplugin')
1533        # If the child plugin inherit straight from CMSPlugin, even if composed with
1534        # other models, gets its own related_name
1535        self.assertEqual(LessMixedPlugin.cmsplugin_ptr.field.remote_field.related_name,
1536                         'mti_pluginapp_lessmixedplugin')
1537        # Non plugins are skipped
1538        self.assertFalse(hasattr(NonPluginModel, 'cmsplugin_ptr'))
1539