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 '<script>var bar="hacked"</script>' 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