1# -*- coding: utf-8 -*-
2import json
3import datetime
4
5from djangocms_text_ckeditor.cms_plugins import TextPlugin
6from djangocms_text_ckeditor.models import Text
7from django.contrib import admin
8from django.contrib.admin.models import LogEntry
9from django.contrib.admin.sites import site
10from django.contrib.auth import get_user_model
11from django.contrib.auth.models import Permission
12from django.contrib.sites.models import Site
13from django.urls import reverse
14from django.http import (Http404, HttpResponseBadRequest,
15                         HttpResponseNotFound)
16from django.utils.encoding import force_text, smart_str
17from django.utils import timezone
18
19from cms import api
20from cms.api import create_page, create_title, add_plugin, publish_page
21from cms.admin.pageadmin import PageAdmin
22from cms.constants import TEMPLATE_INHERITANCE_MAGIC
23from cms.models import StaticPlaceholder
24from cms.models.pagemodel import Page, PageType
25from cms.models.permissionmodels import GlobalPagePermission, PagePermission
26from cms.models.placeholdermodel import Placeholder
27from cms.models.pluginmodel import CMSPlugin
28from cms.models.titlemodels import Title
29from cms.test_utils import testcases as base
30from cms.test_utils.testcases import (
31    CMSTestCase, URL_CMS_PAGE_DELETE, URL_CMS_PAGE,URL_CMS_TRANSLATION_DELETE,
32    URL_CMS_PAGE_CHANGE_LANGUAGE, URL_CMS_PAGE_CHANGE,
33    URL_CMS_PAGE_PUBLISHED,
34)
35from cms.utils.conf import get_cms_setting
36from cms.utils.urlutils import admin_reverse
37
38
39class AdminTestsBase(CMSTestCase):
40    @property
41    def admin_class(self):
42        return site._registry[Page]
43
44    def _get_guys(self, admin_only=False, use_global_permissions=True):
45        admin_user = self.get_superuser()
46
47        if admin_only:
48            return admin_user
49        staff_user = self._get_staff_user(use_global_permissions)
50        return admin_user, staff_user
51
52    def _get_staff_user(self, use_global_permissions=True):
53        USERNAME = 'test'
54
55        if get_user_model().USERNAME_FIELD == 'email':
56            normal_guy = get_user_model().objects.create_user(USERNAME, 'test@test.com', 'test@test.com')
57        else:
58            normal_guy = get_user_model().objects.create_user(USERNAME, 'test@test.com', USERNAME)
59
60        normal_guy.is_staff = True
61        normal_guy.is_active = True
62        perms = Permission.objects.filter(
63            codename__in=['change_page', 'change_title', 'add_page', 'add_title', 'delete_page', 'delete_title']
64        )
65        normal_guy.save()
66        normal_guy.user_permissions.set(perms)
67        if use_global_permissions:
68            gpp = GlobalPagePermission.objects.create(
69                user=normal_guy,
70                can_change=True,
71                can_delete=True,
72                can_change_advanced_settings=False,
73                can_publish=True,
74                can_change_permissions=False,
75                can_move_page=True,
76            )
77            gpp.sites.set(Site.objects.all())
78        return normal_guy
79
80
81class AdminTestCase(AdminTestsBase):
82
83    def test_extension_not_in_admin(self):
84        admin_user, staff = self._get_guys()
85        with self.login_user_context(admin_user):
86            request = self.get_request(URL_CMS_PAGE_CHANGE % 1, 'en',)
87            response = site.index(request)
88            self.assertNotContains(response, '/mytitleextension/')
89            self.assertNotContains(response, '/mypageextension/')
90
91    def test_2apphooks_with_same_namespace(self):
92        PAGE1 = 'Test Page'
93        PAGE2 = 'Test page 2'
94        APPLICATION_URLS = 'project.sampleapp.urls'
95
96        admin_user, normal_guy = self._get_guys()
97
98        current_site = Site.objects.get(pk=1)
99
100        # The admin creates the page
101        page = create_page(PAGE1, "nav_playground.html", "en",
102                           site=current_site, created_by=admin_user)
103        page2 = create_page(PAGE2, "nav_playground.html", "en",
104                            site=current_site, created_by=admin_user)
105
106        page.application_urls = APPLICATION_URLS
107        page.application_namespace = "space1"
108        page.save()
109        page2.application_urls = APPLICATION_URLS
110        page2.save()
111
112        # The admin edits the page (change the page name for ex.)
113        page_data = {
114            'title': PAGE2,
115            'slug': page2.get_slug(),
116            'template': page2.template,
117            'application_urls': 'SampleApp',
118            'application_namespace': 'space1',
119        }
120
121        with self.login_user_context(admin_user):
122            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page.pk, page_data)
123            self.assertEqual(resp.status_code, 302)
124            self.assertEqual(Page.objects.filter(application_namespace="space1").count(), 1)
125            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page2.pk, page_data)
126            self.assertEqual(resp.status_code, 200)
127            page_data['application_namespace'] = 'space2'
128            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page2.pk, page_data)
129            self.assertEqual(resp.status_code, 302)
130
131    def test_delete(self):
132        admin_user = self.get_superuser()
133        create_page("home", "nav_playground.html", "en",
134                           created_by=admin_user, published=True)
135        page = create_page("delete-page", "nav_playground.html", "en",
136                           created_by=admin_user, published=True)
137        create_page('child-page', "nav_playground.html", "en",
138                    created_by=admin_user, published=True, parent=page)
139        body = page.placeholders.get(slot='body')
140        add_plugin(body, 'TextPlugin', 'en', body='text')
141        page.publish('en')
142        with self.login_user_context(admin_user):
143            data = {'post': 'yes'}
144            response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data)
145            self.assertRedirects(response, URL_CMS_PAGE)
146
147    def test_delete_diff_language(self):
148        admin_user = self.get_superuser()
149        create_page("home", "nav_playground.html", "en",
150                           created_by=admin_user, published=True)
151        page = create_page("delete-page", "nav_playground.html", "en",
152                           created_by=admin_user, published=True)
153        create_page('child-page', "nav_playground.html", "de",
154                    created_by=admin_user, published=True, parent=page)
155        body = page.placeholders.get(slot='body')
156        add_plugin(body, 'TextPlugin', 'en', body='text')
157        page.publish('en')
158        with self.login_user_context(admin_user):
159            data = {'post': 'yes'}
160            response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data)
161            self.assertRedirects(response, URL_CMS_PAGE)
162
163    def test_search_fields(self):
164        superuser = self.get_superuser()
165        from django.contrib.admin import site
166
167        with self.login_user_context(superuser):
168            for model, admin_instance in site._registry.items():
169                if model._meta.app_label != 'cms':
170                    continue
171                if not admin_instance.search_fields:
172                    continue
173                url = admin_reverse('cms_%s_changelist' % model._meta.model_name)
174                response = self.client.get('%s?q=1' % url)
175                errmsg = response.content
176                self.assertEqual(response.status_code, 200, errmsg)
177
178    def test_pagetree_filtered(self):
179        superuser = self.get_superuser()
180        create_page("root-page", "nav_playground.html", "en",
181                    created_by=superuser, published=True)
182        with self.login_user_context(superuser):
183            url = admin_reverse('cms_page_changelist')
184            response = self.client.get('%s?template__exact=nav_playground.html' % url)
185            errmsg = response.content
186            self.assertEqual(response.status_code, 200, errmsg)
187
188    def test_delete_translation(self):
189        admin_user = self.get_superuser()
190        page = create_page("delete-page-translation", "nav_playground.html", "en",
191                           created_by=admin_user, published=True)
192        create_title("de", "delete-page-translation-2", page, slug="delete-page-translation-2")
193        create_title("es-mx", "delete-page-translation-es", page, slug="delete-page-translation-es")
194        with self.login_user_context(admin_user):
195            response = self.client.get(URL_CMS_TRANSLATION_DELETE % page.pk, {'language': 'de'})
196            self.assertEqual(response.status_code, 200)
197            response = self.client.post(URL_CMS_TRANSLATION_DELETE % page.pk, {'language': 'de'})
198            self.assertRedirects(response, URL_CMS_PAGE)
199            response = self.client.get(URL_CMS_TRANSLATION_DELETE % page.pk, {'language': 'es-mx'})
200            self.assertEqual(response.status_code, 200)
201            response = self.client.post(URL_CMS_TRANSLATION_DELETE % page.pk, {'language': 'es-mx'})
202            self.assertRedirects(response, URL_CMS_PAGE)
203
204    def test_change_dates(self):
205        admin_user, staff = self._get_guys()
206
207        with self.settings(USE_TZ=False, TIME_ZONE='UTC'):
208
209            page = create_page('test-page', 'nav_playground.html', 'en')
210            page.publish('en')
211            draft = page.get_draft_object()
212
213            original_date = draft.publication_date
214            original_end_date = draft.publication_end_date
215            new_date = timezone.now() - datetime.timedelta(days=1)
216            new_end_date = timezone.now() + datetime.timedelta(days=1)
217            url = admin_reverse('cms_page_dates', args=(draft.pk,))
218            with self.login_user_context(admin_user):
219                response = self.client.post(url, {
220                    'publication_date_0': new_date.date(),
221                    'publication_date_1': new_date.strftime("%H:%M:%S"),
222                    'publication_end_date_0': new_end_date.date(),
223                    'publication_end_date_1': new_end_date.strftime("%H:%M:%S"),
224                })
225                self.assertEqual(response.status_code, 302)
226                draft = Page.objects.get(pk=draft.pk)
227                self.assertNotEqual(draft.publication_date.timetuple(), original_date.timetuple())
228                self.assertEqual(draft.publication_date.timetuple(), new_date.timetuple())
229                self.assertEqual(draft.publication_end_date.timetuple(), new_end_date.timetuple())
230                if original_end_date:
231                    self.assertNotEqual(draft.publication_end_date.timetuple(), original_end_date.timetuple())
232
233        with self.settings(USE_TZ=True, TIME_ZONE='UTC'):
234
235            page = create_page('test-page-2', 'nav_playground.html', 'en')
236            page.publish('en')
237            draft = page.get_draft_object()
238
239            original_date = draft.publication_date
240            original_end_date = draft.publication_end_date
241            new_date = timezone.localtime(timezone.now()) - datetime.timedelta(days=1)
242            new_end_date = timezone.localtime(timezone.now()) + datetime.timedelta(days=1)
243            url = admin_reverse('cms_page_dates', args=(draft.pk,))
244            with self.login_user_context(admin_user):
245                response = self.client.post(url, {
246                    'language': 'en',
247                    'publication_date_0': new_date.date(),
248                    'publication_date_1': new_date.strftime("%H:%M:%S"),
249                    'publication_end_date_0': new_end_date.date(),
250                    'publication_end_date_1': new_end_date.strftime("%H:%M:%S"),
251                })
252                self.assertEqual(response.status_code, 302)
253                draft = Page.objects.get(pk=draft.pk)
254                self.assertNotEqual(draft.publication_date.timetuple(), original_date.timetuple())
255                self.assertEqual(timezone.localtime(draft.publication_date).timetuple(), new_date.timetuple())
256                self.assertEqual(timezone.localtime(draft.publication_end_date).timetuple(), new_end_date.timetuple())
257                if original_end_date:
258                    self.assertNotEqual(draft.publication_end_date.timetuple(), original_end_date.timetuple())
259
260    def test_change_template(self):
261        template = get_cms_setting('TEMPLATES')[0][0]
262        admin_user, staff = (self.get_superuser(), self.get_staff_user_with_no_permissions())
263
264        with self.login_user_context(admin_user):
265            response = self.client.post(
266                self.get_admin_url(Page, 'change_template', 1),
267                {'template': template}
268            )
269            self.assertEqual(response.status_code, 404)
270
271        with self.login_user_context(staff):
272            response = self.client.post(
273                self.get_admin_url(Page, 'change_template', 1),
274                {'template': template}
275            )
276            self.assertEqual(response.status_code, 403)
277
278        page = create_page('test-page', template, 'en')
279
280        with self.login_user_context(staff):
281            response = self.client.post(
282                self.get_admin_url(Page, 'change_template', page.pk),
283                {'template': template}
284            )
285            self.assertEqual(response.status_code, 403)
286
287        with self.login_user_context(admin_user):
288            response = self.client.post(
289                self.get_admin_url(Page, 'change_template', page.pk),
290                {'template': 'doesntexist'}
291            )
292            self.assertEqual(response.status_code, 400)
293            response = self.client.post(
294                self.get_admin_url(Page, 'change_template', page.pk),
295                {'template': template}
296            )
297            self.assertEqual(response.status_code, 200)
298
299    def test_changelist_items(self):
300        admin_user = self.get_superuser()
301        first_level_page = create_page('level1', 'nav_playground.html', 'en')
302        second_level_page_top = create_page('level21', "nav_playground.html", "en",
303                                            created_by=admin_user, published=True, parent=first_level_page)
304        second_level_page_bottom = create_page('level22', "nav_playground.html", "en",
305                                               created_by=admin_user, published=True,
306                                               parent=self.reload(first_level_page))
307        third_level_page = create_page('level3', "nav_playground.html", "en",
308                                       created_by=admin_user, published=True, parent=second_level_page_top)
309        self.assertEqual(Page.objects.all().count(), 4)
310
311        with self.login_user_context(admin_user):
312            response = self.client.get(self.get_admin_url(Page, 'changelist'))
313            cms_page_nodes = response.context_data['tree']['items']
314            self.assertEqual(cms_page_nodes[0], first_level_page)
315            self.assertEqual(cms_page_nodes[1], second_level_page_top)
316            self.assertEqual(cms_page_nodes[2], third_level_page)
317            self.assertEqual(cms_page_nodes[3], second_level_page_bottom)
318
319    def test_changelist_get_results(self):
320        admin_user = self.get_superuser()
321        first_level_page = create_page('level1', 'nav_playground.html', 'en', published=True)
322        second_level_page_top = create_page('level21', "nav_playground.html", "en",
323                                            created_by=admin_user, published=True,
324                                            parent=first_level_page)
325        second_level_page_bottom = create_page('level22', "nav_playground.html", "en", # nopyflakes
326                                               created_by=admin_user, published=True,
327                                               parent=self.reload(first_level_page))
328        third_level_page = create_page('level3', "nav_playground.html", "en", # nopyflakes
329                                       created_by=admin_user, published=True,
330                                       parent=second_level_page_top)
331        fourth_level_page = create_page('level23', "nav_playground.html", "en", # nopyflakes
332                                        created_by=admin_user,
333                                        parent=self.reload(first_level_page))
334        self.assertEqual(Page.objects.all().count(), 9)
335        endpoint = self.get_admin_url(Page, 'changelist')
336
337        with self.login_user_context(admin_user):
338            response = self.client.get(endpoint)
339            self.assertEqual(response.context_data['tree']['items'].count(), 5)
340
341        with self.login_user_context(admin_user):
342            response = self.client.get(endpoint + '?q=level23')
343            self.assertEqual(response.context_data['tree']['items'].count(), 1)
344
345        with self.login_user_context(admin_user):
346            response = self.client.get(endpoint + '?q=level2')
347            self.assertEqual(response.context_data['tree']['items'].count(), 3)
348
349    def test_unihandecode_doesnt_break_404_in_admin(self):
350        self.get_superuser()
351
352        if get_user_model().USERNAME_FIELD == 'email':
353            self.client.login(username='admin@django-cms.org', password='admin@django-cms.org')
354        else:
355            self.client.login(username='admin', password='admin')
356
357        response = self.client.get(URL_CMS_PAGE_CHANGE_LANGUAGE % (1, 'en'))
358        self.assertEqual(response.status_code, 404)
359
360    def test_empty_placeholder_with_nested_plugins(self):
361        # It's important that this test clears a placeholder
362        # which only has nested plugins.
363        # This allows us to catch a strange bug that happened
364        # under these conditions with the new related name handling.
365        page_en = create_page("EmptyPlaceholderTestPage (EN)", "nav_playground.html", "en")
366        ph = page_en.placeholders.get(slot="body")
367
368        column_wrapper = add_plugin(ph, "MultiColumnPlugin", "en")
369
370        add_plugin(ph, "ColumnPlugin", "en", parent=column_wrapper)
371        add_plugin(ph, "ColumnPlugin", "en", parent=column_wrapper)
372
373        # before cleaning the de placeholder
374        self.assertEqual(ph.get_plugins('en').count(), 3)
375
376        admin_user, staff = self._get_guys()
377        endpoint = self.get_clear_placeholder_url(ph, language='en')
378
379        with self.login_user_context(admin_user):
380            response = self.client.post(endpoint, {'test': 0})
381
382        self.assertEqual(response.status_code, 302)
383
384        # After cleaning the de placeholder, en placeholder must still have all the plugins
385        self.assertEqual(ph.get_plugins('en').count(), 0)
386
387    def test_empty_placeholder_in_correct_language(self):
388        """
389        Test that Cleaning a placeholder only affect current language contents
390        """
391        # create some objects
392        page_en = create_page("EmptyPlaceholderTestPage (EN)", "nav_playground.html", "en")
393        ph = page_en.placeholders.get(slot="body")
394
395        # add the text plugin to the en version of the page
396        add_plugin(ph, "TextPlugin", "en", body="Hello World EN 1")
397        add_plugin(ph, "TextPlugin", "en", body="Hello World EN 2")
398
399        # creating a de title of the page and adding plugins to it
400        create_title("de", page_en.get_title(), page_en, slug=page_en.get_slug())
401        add_plugin(ph, "TextPlugin", "de", body="Hello World DE")
402        add_plugin(ph, "TextPlugin", "de", body="Hello World DE 2")
403        add_plugin(ph, "TextPlugin", "de", body="Hello World DE 3")
404
405        # before cleaning the de placeholder
406        self.assertEqual(ph.get_plugins('en').count(), 2)
407        self.assertEqual(ph.get_plugins('de').count(), 3)
408
409        admin_user, staff = self._get_guys()
410        endpoint = self.get_clear_placeholder_url(ph, language='de')
411
412        with self.login_user_context(admin_user):
413            response = self.client.post(endpoint, {'test': 0})
414
415        self.assertEqual(response.status_code, 302)
416
417        # After cleaning the de placeholder, en placeholder must still have all the plugins
418        self.assertEqual(ph.get_plugins('en').count(), 2)
419        self.assertEqual(ph.get_plugins('de').count(), 0)
420
421
422class AdminTests(AdminTestsBase):
423    # TODO: needs tests for actual permissions, not only superuser/normaluser
424
425    def setUp(self):
426        self.page = create_page("testpage", "nav_playground.html", "en")
427
428    def get_admin(self):
429        User = get_user_model()
430
431        fields = dict(email="admin@django-cms.org", is_staff=True, is_superuser=True)
432
433        if (User.USERNAME_FIELD != 'email'):
434            fields[User.USERNAME_FIELD] = "admin"
435
436        usr = User(**fields)
437        usr.set_password(getattr(usr, User.USERNAME_FIELD))
438        usr.save()
439        return usr
440
441    def get_permless(self):
442        User = get_user_model()
443
444        fields = dict(email="permless@django-cms.org", is_staff=True)
445
446        if (User.USERNAME_FIELD != 'email'):
447            fields[User.USERNAME_FIELD] = "permless"
448
449        usr = User(**fields)
450        usr.set_password(getattr(usr, User.USERNAME_FIELD))
451        usr.save()
452        return usr
453
454    def get_page(self):
455        return self.page
456
457    def test_change_publish_unpublish(self):
458        page = self.get_page()
459        permless = self.get_permless()
460        with self.login_user_context(permless):
461            request = self.get_request()
462            response = self.admin_class.publish_page(request, page.pk, "en")
463            self.assertEqual(response.status_code, 405)
464            page = self.reload(page)
465            self.assertFalse(page.is_published('en'))
466
467            request = self.get_request(post_data={'no': 'data'})
468            response = self.admin_class.publish_page(request, page.pk, "en")
469            self.assertEqual(response.status_code, 403)
470            page = self.reload(page)
471            self.assertFalse(page.is_published('en'))
472
473        admin_user = self.get_admin()
474        with self.login_user_context(admin_user):
475            request = self.get_request(post_data={'no': 'data'})
476            response = self.admin_class.publish_page(request, page.pk, "en")
477            self.assertEqual(response.status_code, 302)
478
479            page = self.reload(page)
480            self.assertTrue(page.is_published('en'))
481
482            response = self.admin_class.unpublish(request, page.pk, "en")
483            self.assertEqual(response.status_code, 302)
484
485            page = self.reload(page)
486            self.assertFalse(page.is_published('en'))
487
488    def test_change_status_adds_log_entry(self):
489        page = self.get_page()
490        admin_user = self.get_admin()
491        with self.login_user_context(admin_user):
492            request = self.get_request(post_data={'no': 'data'})
493            self.assertFalse(LogEntry.objects.count())
494            response = self.admin_class.publish_page(request, page.pk, "en")
495            self.assertEqual(response.status_code, 302)
496            self.assertEqual(1, LogEntry.objects.count())
497            self.assertEqual(page.pk, int(LogEntry.objects.all()[0].object_id))
498
499    def test_change_innavigation(self):
500        page = self.get_page()
501        permless = self.get_permless()
502        admin_user = self.get_admin()
503        with self.login_user_context(permless):
504            request = self.get_request()
505            response = self.admin_class.change_innavigation(request, page.pk)
506            self.assertEqual(response.status_code, 405)
507        with self.login_user_context(permless):
508            request = self.get_request(post_data={'no': 'data'})
509            response = self.admin_class.change_innavigation(request, page.pk)
510            self.assertEqual(response.status_code, 403)
511        with self.login_user_context(permless):
512            request = self.get_request(post_data={'no': 'data'})
513            self.assertEqual(response.status_code, 403)
514        with self.login_user_context(admin_user):
515            request = self.get_request(post_data={'no': 'data'})
516            self.assertRaises(Http404, self.admin_class.change_innavigation,
517                              request, page.pk + 100)
518        with self.login_user_context(permless):
519            request = self.get_request(post_data={'no': 'data'})
520            response = self.admin_class.change_innavigation(request, page.pk)
521            self.assertEqual(response.status_code, 403)
522        with self.login_user_context(admin_user):
523            request = self.get_request(post_data={'no': 'data'})
524            old = page.in_navigation
525            response = self.admin_class.change_innavigation(request, page.pk)
526            self.assertEqual(response.status_code, 204)
527            page = self.reload(page)
528            self.assertEqual(old, not page.in_navigation)
529
530    def test_publish_page_requires_perms(self):
531        permless = self.get_permless()
532        with self.login_user_context(permless):
533            request = self.get_request()
534            request.method = "POST"
535            response = self.admin_class.publish_page(request, Page.objects.drafts().first().pk, "en")
536            self.assertEqual(response.status_code, 403)
537
538    def test_remove_plugin_requires_post(self):
539        ph = self.page.placeholders.all()[0]
540        plugin = add_plugin(ph, 'TextPlugin', 'en', body='test')
541        admin_user = self.get_admin()
542        with self.login_user_context(admin_user):
543            endpoint = self.get_delete_plugin_uri(plugin, container=self.page)
544            response = self.client.get(endpoint)
545            self.assertEqual(response.status_code, 200)
546
547    def test_move_language(self):
548        page = self.get_page()
549        source, target = list(page.placeholders.all())[:2]
550        col = add_plugin(source, 'MultiColumnPlugin', 'en')
551        sub_col = add_plugin(source, 'ColumnPlugin', 'en', target=col)
552        col2 = add_plugin(source, 'MultiColumnPlugin', 'de')
553
554        admin_user = self.get_admin()
555        with self.login_user_context(admin_user):
556            data = {
557                'plugin_id': sub_col.pk,
558                'placeholder_id': source.id,
559                'plugin_parent': col2.pk,
560                'target_language': 'de'
561            }
562            endpoint = self.get_move_plugin_uri(sub_col)
563            response = self.client.post(endpoint, data)
564            self.assertEqual(response.status_code, 200)
565        sub_col = CMSPlugin.objects.get(pk=sub_col.pk)
566        self.assertEqual(sub_col.language, "de")
567        self.assertEqual(sub_col.parent_id, col2.pk)
568
569    def test_preview_page(self):
570        permless = self.get_permless()
571        with self.login_user_context(permless):
572            request = self.get_request()
573            self.assertRaises(Http404, self.admin_class.preview_page, request, 404, "en")
574        page = self.get_page()
575        page.publish("en")
576        page.set_as_homepage()
577
578        new_site = Site.objects.create(id=2, domain='django-cms.org', name='django-cms')
579        new_page = create_page("testpage", "nav_playground.html", "fr", site=new_site, published=True)
580
581        base_url = page.get_absolute_url()
582        with self.login_user_context(permless):
583            request = self.get_request('/?public=true')
584            response = self.admin_class.preview_page(request, page.pk, 'en')
585            self.assertEqual(response.status_code, 302)
586            self.assertEqual(response['Location'], '%s?%s&language=en' % (base_url, get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')))
587            request = self.get_request()
588            response = self.admin_class.preview_page(request, page.pk, 'en')
589            self.assertEqual(response.status_code, 302)
590            self.assertEqual(response['Location'], '%s?%s&language=en' % (base_url, get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')))
591
592            # Switch active site
593            request.session['cms_admin_site'] = new_site.pk
594
595            # Preview page attached to active site but not to current site
596            response = self.admin_class.preview_page(request, new_page.pk, 'fr')
597            self.assertEqual(response.status_code, 302)
598            self.assertEqual(response['Location'],
599                             'http://django-cms.org/fr/testpage/?%s&language=fr' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON'))
600
601    def test_too_many_plugins_global(self):
602        conf = {
603            'body': {
604                'limits': {
605                    'global': 1,
606                },
607            },
608        }
609        admin_user = self.get_admin()
610        url = admin_reverse('cms_page_add_plugin')
611        with self.settings(CMS_PERMISSION=False, CMS_PLACEHOLDER_CONF=conf):
612            page = create_page('somepage', 'nav_playground.html', 'en')
613            body = page.placeholders.get(slot='body')
614            add_plugin(body, 'TextPlugin', 'en', body='text')
615            with self.login_user_context(admin_user):
616                data = {
617                    'plugin_type': 'TextPlugin',
618                    'placeholder_id': body.pk,
619                    'target_language': 'en',
620                }
621                response = self.client.post(url, data)
622                self.assertEqual(response.status_code, HttpResponseBadRequest.status_code)
623
624    def test_too_many_plugins_type(self):
625        conf = {
626            'body': {
627                'limits': {
628                    'TextPlugin': 1,
629                },
630            },
631        }
632        admin_user = self.get_admin()
633        url = admin_reverse('cms_page_add_plugin')
634        with self.settings(CMS_PERMISSION=False, CMS_PLACEHOLDER_CONF=conf):
635            page = create_page('somepage', 'nav_playground.html', 'en')
636            body = page.placeholders.get(slot='body')
637            add_plugin(body, 'TextPlugin', 'en', body='text')
638            with self.login_user_context(admin_user):
639                data = {
640                    'plugin_type': 'TextPlugin',
641                    'placeholder_id': body.pk,
642                    'target_language': 'en',
643                    'plugin_parent': '',
644                }
645                response = self.client.post(url, data)
646                self.assertEqual(response.status_code, HttpResponseBadRequest.status_code)
647
648    def test_edit_title_dirty_bit(self):
649        language = "en"
650        admin_user = self.get_admin()
651        page = create_page('A', 'nav_playground.html', language)
652        page_admin = PageAdmin(Page, None)
653        page_admin._current_page = page
654        page.publish("en")
655        draft_page = page.get_draft_object()
656        admin_url = reverse("admin:cms_page_edit_title_fields", args=(
657            draft_page.pk, language
658        ))
659
660        post_data = {
661            'title': "A Title"
662        }
663        with self.login_user_context(admin_user):
664            self.client.post(admin_url, post_data)
665            draft_page = Page.objects.get(pk=page.pk).get_draft_object()
666            self.assertTrue(draft_page.is_dirty('en'))
667
668    def test_edit_title_languages(self):
669        language = "en"
670        admin_user = self.get_admin()
671        page = create_page('A', 'nav_playground.html', language)
672        page_admin = PageAdmin(Page, None)
673        page_admin._current_page = page
674        page.publish("en")
675        draft_page = page.get_draft_object()
676        admin_url = reverse("admin:cms_page_edit_title_fields", args=(
677            draft_page.pk, language
678        ))
679
680        post_data = {
681            'title': "A Title"
682        }
683        with self.login_user_context(admin_user):
684            self.client.post(admin_url, post_data)
685            draft_page = Page.objects.get(pk=page.pk).get_draft_object()
686            self.assertTrue(draft_page.is_dirty('en'))
687
688
689class NoDBAdminTests(CMSTestCase):
690    @property
691    def admin_class(self):
692        return site._registry[Page]
693
694    def test_lookup_allowed_site__exact(self):
695        self.assertTrue(self.admin_class.lookup_allowed('site__exact', '1'))
696
697    def test_lookup_allowed_published(self):
698        self.assertTrue(self.admin_class.lookup_allowed('published', value='1'))
699
700
701class PluginPermissionTests(AdminTestsBase):
702    def setUp(self):
703        self._page = create_page('test page', 'nav_playground.html', 'en')
704        self._placeholder = self._page.placeholders.all()[0]
705
706    def _get_admin(self):
707        User = get_user_model()
708
709        fields = dict(email="admin@django-cms.org", is_staff=True, is_active=True)
710
711        if (User.USERNAME_FIELD != 'email'):
712            fields[User.USERNAME_FIELD] = "admin"
713
714        admin_user = User(**fields)
715
716        admin_user.set_password('admin')
717        admin_user.save()
718        return admin_user
719
720    def _get_page_admin(self):
721        return admin.site._registry[Page]
722
723    def _give_permission(self, user, model, permission_type, save=True):
724        codename = '%s_%s' % (permission_type, model._meta.object_name.lower())
725        user.user_permissions.add(Permission.objects.get(codename=codename))
726
727    def _give_page_permission_rights(self, user):
728        self._give_permission(user, PagePermission, 'add')
729        self._give_permission(user, PagePermission, 'change')
730        self._give_permission(user, PagePermission, 'delete')
731
732    def _get_change_page_request(self, user, page):
733        return type('Request', (object,), {
734            'user': user,
735            'path': base.URL_CMS_PAGE_CHANGE % page.pk
736        })
737
738    def _give_cms_permissions(self, user, save=True):
739        for perm_type in ['add', 'change', 'delete']:
740            for model in [Page, Title]:
741                self._give_permission(user, model, perm_type, False)
742        gpp = GlobalPagePermission.objects.create(
743            user=user,
744            can_change=True,
745            can_delete=True,
746            can_change_advanced_settings=False,
747            can_publish=True,
748            can_change_permissions=False,
749            can_move_page=True,
750        )
751        gpp.sites = Site.objects.all()
752        if save:
753            user.save()
754
755    def _create_plugin(self):
756        plugin = add_plugin(self._placeholder, 'TextPlugin', 'en')
757        return plugin
758
759    def test_plugin_edit_wrong_url(self):
760        """User tries to edit a plugin using a random url. 404 response returned"""
761        plugin = self._create_plugin()
762        _, normal_guy = self._get_guys()
763
764        if get_user_model().USERNAME_FIELD == 'email':
765            self.client.login(username='test@test.com', password='test@test.com')
766        else:
767            self.client.login(username='test', password='test')
768
769        self._give_permission(normal_guy, Text, 'change')
770        url = '%s/edit-plugin/%s/' % (admin_reverse('cms_page_edit_plugin', args=[plugin.id]), plugin.id)
771        response = self.client.post(url, dict())
772        self.assertEqual(response.status_code, HttpResponseNotFound.status_code)
773        self.assertTrue("Plugin not found" in force_text(response.content))
774
775
776class AdminFormsTests(AdminTestsBase):
777    def test_clean_overwrite_url(self):
778        """
779        A manual path needs to be stripped from leading and trailing slashes.
780        """
781        superuser = self.get_superuser()
782        cms_page = create_page('test page', 'nav_playground.html', 'en')
783        page_data = {
784            'overwrite_url': '/overwrite/url/',
785            'template': cms_page.template,
786        }
787        endpoint = self.get_admin_url(Page, 'advanced', cms_page.pk)
788
789        with self.login_user_context(superuser):
790            response = self.client.post(endpoint, page_data)
791            self.assertRedirects(response, URL_CMS_PAGE)
792            self.assertSequenceEqual(
793                cms_page.title_set.values_list('path', 'has_url_overwrite'),
794                [('overwrite/url', True)],
795            )
796
797    def test_missmatching_site_parent_dotsite(self):
798        superuser = self.get_superuser()
799        new_site = Site.objects.create(id=2, domain='foo.com', name='foo.com')
800        parent_page = api.create_page("test", get_cms_setting('TEMPLATES')[0][0], "fr", site=new_site)
801        new_page_data = {
802            'title': 'Title',
803            'slug': 'slug',
804            'parent_node': parent_page.node.pk,
805        }
806        with self.login_user_context(superuser):
807            # Invalid parent
808            response = self.client.post(self.get_admin_url(Page, 'add'), new_page_data)
809            expected_error = (
810                '<ul class="errorlist">'
811                '<li>Site doesn&#39;t match the parent&#39;s page site</li></ul>'
812            )
813            self.assertEqual(response.status_code, 200)
814            self.assertContains(response, expected_error, html=True)
815
816    def test_form_errors(self):
817        superuser = self.get_superuser()
818        site0 = Site.objects.create(id=2, domain='foo.com', name='foo.com')
819        page1 = api.create_page("test", get_cms_setting('TEMPLATES')[0][0], "fr", site=site0)
820
821        new_page_data = {
822            'title': 'Title',
823            'slug': 'home',
824            'parent_node': page1.node.pk,
825        }
826
827        with self.login_user_context(superuser):
828            # Invalid parent
829            response = self.client.post(self.get_admin_url(Page, 'add'), new_page_data)
830            expected_error = (
831                '<ul class="errorlist">'
832                '<li>Site doesn&#39;t match the parent&#39;s page site</li></ul>'
833            )
834            self.assertEqual(response.status_code, 200)
835            self.assertContains(response, expected_error, html=True)
836
837        new_page_data = {
838            'title': 'Title',
839            'slug': '#',
840        }
841
842        with self.login_user_context(superuser):
843            # Invalid slug
844            response = self.client.post(self.get_admin_url(Page, 'add'), new_page_data)
845            expected_error = '<ul class="errorlist"><li>Slug must not be empty.</li></ul>'
846            self.assertEqual(response.status_code, 200)
847            self.assertContains(response, expected_error, html=True)
848
849        page2 = api.create_page("test", get_cms_setting('TEMPLATES')[0][0], "en")
850        new_page_data = {
851            'title': 'Title',
852            'slug': 'test',
853        }
854
855        with self.login_user_context(superuser):
856            # Duplicate slug / path
857            response = self.client.post(self.get_admin_url(Page, 'add'), new_page_data)
858            expected_error = (
859                '<ul class="errorlist"><li>Page '
860                '<a href="{}" target="_blank">test</a> '
861                'has the same url \'test\' as current page.</li></ul>'
862            ).format(self.get_admin_url(Page, 'change', page2.pk))
863            self.assertEqual(response.status_code, 200)
864            self.assertContains(response, expected_error, html=True)
865
866    def test_reverse_id_error_location(self):
867        superuser = self.get_superuser()
868        create_page('Page 1', 'nav_playground.html', 'en', reverse_id='p1')
869        page2 = create_page('Page 2', 'nav_playground.html', 'en')
870        page2_endpoint = self.get_admin_url(Page, 'advanced', page2.pk)
871
872        # Assemble a bunch of data to test the page form
873        page2_data = {
874            'reverse_id': 'p1',
875            'template': 'col_two.html',
876        }
877
878        with self.login_user_context(superuser):
879            response = self.client.post(page2_endpoint, page2_data)
880            expected_error = (
881                '<ul class="errorlist">'
882                '<li>A page with this reverse URL id exists already.</li></ul>'
883            )
884            self.assertEqual(response.status_code, 200)
885            self.assertContains(response, expected_error.format(page2.pk), html=True)
886
887    def test_advanced_settings_endpoint(self):
888        admin_user = self.get_superuser()
889        site = Site.objects.get_current()
890        page = create_page('Page 1', 'nav_playground.html', 'en')
891        page_data = {
892            'language': 'en',
893            'site': site.pk,
894            'template': 'col_two.html',
895        }
896        path = admin_reverse('cms_page_advanced', args=(page.pk,))
897
898        with self.login_user_context(admin_user):
899            en_path = path + u"?language=en"
900            redirect_path = admin_reverse('cms_page_changelist') + '?language=en'
901            response = self.client.post(en_path, page_data)
902            self.assertRedirects(response, redirect_path)
903            self.assertEqual(Page.objects.get(pk=page.pk).template, 'col_two.html')
904
905        # Now switch it up by adding german as the current language
906        # Note that german has not been created as page translation.
907        page_data['language'] = 'de'
908        page_data['template'] = 'nav_playground.html'
909
910        with self.login_user_context(admin_user):
911            de_path = path + u"?language=de"
912            redirect_path = admin_reverse('cms_page_change', args=(page.pk,)) + '?language=de'
913            response = self.client.post(de_path, page_data)
914            # Assert user is redirected to basic settings.
915            self.assertRedirects(response, redirect_path)
916            # Make sure no change was made
917            self.assertEqual(Page.objects.get(pk=page.pk).template, 'col_two.html')
918
919        de_translation = create_title('de', title='Page 1', page=page.reload())
920        de_translation.slug = ''
921        de_translation.save()
922
923        # Now try again but slug is set to empty string.
924        page_data['language'] = 'de'
925        page_data['template'] = 'nav_playground.html'
926
927        with self.login_user_context(admin_user):
928            de_path = path + u"?language=de"
929            response = self.client.post(de_path, page_data)
930            # Assert user is not redirected because there was a form error
931            self.assertEqual(response.status_code, 200)
932            # Make sure no change was made
933            self.assertEqual(Page.objects.get(pk=page.pk).template, 'col_two.html')
934
935        de_translation.slug = 'someslug'
936        de_translation.save()
937
938        # Now try again but with the title having a slug.
939        page_data['language'] = 'de'
940        page_data['template'] = 'nav_playground.html'
941
942        with self.login_user_context(admin_user):
943            en_path = path + u"?language=de"
944            redirect_path = admin_reverse('cms_page_changelist') + '?language=de'
945            response = self.client.post(en_path, page_data)
946            self.assertRedirects(response, redirect_path)
947            self.assertEqual(Page.objects.get(pk=page.pk).template, 'nav_playground.html')
948
949    def test_advanced_settings_endpoint_fails_gracefully(self):
950        admin_user = self.get_superuser()
951        page = create_page('Page 1', 'nav_playground.html', 'en')
952        page_data = {
953            'language': 'en',
954            'template': 'col_two.html',
955        }
956        path = admin_reverse('cms_page_advanced', args=(page.pk,))
957
958        # It's important to test fields that are validated
959        # automatically by Django vs fields that are validated
960        # via the clean() method by us.
961        # Fields validated by Django will not be in cleaned data
962        # if they have an error so if we rely on these in the clean()
963        # method then an error will be raised.
964
965        # So test that the form short circuits if there's errors.
966        page_data['application_urls'] = 'TestApp'
967
968        with self.login_user_context(admin_user):
969            response = self.client.post(path, page_data)
970            # Assert user is not redirected because there was a form error
971            self.assertEqual(response.status_code, 200)
972
973            page = page.reload()
974            # Make sure no change was made
975            self.assertEqual(page.application_urls, None)
976
977    def test_create_page_type(self):
978        page = create_page('Test', 'static.html', 'en', published=True, reverse_id="home")
979        for placeholder in page.placeholders.all():
980            add_plugin(placeholder, TextPlugin, 'en', body='<b>Test</b>')
981        page.publish('en')
982        self.assertEqual(Page.objects.count(), 2)
983        self.assertEqual(CMSPlugin.objects.count(), 4)
984        superuser = self.get_superuser()
985        with self.login_user_context(superuser):
986            # source is hidden because there's no page-types
987            response = self.client.get(self.get_admin_url(Page, 'add'))
988            self.assertContains(response, '<input id="id_source" name="source" type="hidden" />', html=True)
989            # create our first page type
990            page_data = {'source': page.pk, 'title': 'type1', 'slug': 'type1', '_save': 1}
991            response = self.client.post(
992                self.get_admin_url(PageType, 'add'),
993                data=page_data,
994            )
995            self.assertEqual(response.status_code, 302)
996            self.assertEqual(Page.objects.count(), 4)
997            self.assertEqual(Page.objects.filter(is_page_type=True).count(), 2)
998            self.assertEqual(CMSPlugin.objects.count(), 6)
999            new_page_id = Page.objects.filter(is_page_type=True).only('pk').latest('id').pk
1000            response = self.client.get(admin_reverse('cms_page_add'))
1001            expected_field = (
1002                '<select id="id_source" name="source">'
1003                '<option value="" selected="selected">---------</option>'
1004                '<option value="{}">type1</option></select>'
1005            ).format(new_page_id)
1006            self.assertContains(response, expected_field, html=True)
1007            # source is hidden when adding a page-type
1008            response = self.client.get(self.get_admin_url(PageType, 'add'))
1009            self.assertContains(response, '<input id="id_source" name="source" type="hidden" />', html=True)
1010
1011    def test_render_edit_mode(self):
1012        from django.core.cache import cache
1013
1014        cache.clear()
1015
1016        homepage = create_page('Test', 'static.html', 'en', published=True)
1017        homepage.set_as_homepage()
1018
1019        for placeholder in Placeholder.objects.all():
1020            add_plugin(placeholder, TextPlugin, 'en', body='<b>Test</b>')
1021
1022        user = self.get_superuser()
1023        self.assertEqual(Placeholder.objects.all().count(), 4)
1024        with self.login_user_context(user):
1025            output = force_text(
1026                self.client.get(
1027                    '/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
1028                ).content
1029            )
1030            self.assertIn('<b>Test</b>', output)
1031            self.assertEqual(Placeholder.objects.all().count(), 9)
1032            self.assertEqual(StaticPlaceholder.objects.count(), 2)
1033            for placeholder in Placeholder.objects.all():
1034                add_plugin(placeholder, TextPlugin, 'en', body='<b>Test</b>')
1035            output = force_text(
1036                self.client.get(
1037                    '/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
1038                ).content
1039            )
1040            self.assertIn('<b>Test</b>', output)
1041
1042    def test_tree_view_queries(self):
1043        from django.core.cache import cache
1044
1045        cache.clear()
1046        for i in range(10):
1047            create_page('Test%s' % i, 'col_two.html', 'en', published=True)
1048        for placeholder in Placeholder.objects.all():
1049            add_plugin(placeholder, TextPlugin, 'en', body='<b>Test</b>')
1050
1051        user = self.get_superuser()
1052        with self.login_user_context(user):
1053            with self.assertNumQueries(9):
1054                force_text(self.client.get(self.get_admin_url(Page, 'changelist')))
1055
1056    def test_smart_link_published_pages(self):
1057        admin, staff_guy = self._get_guys()
1058        page_url = URL_CMS_PAGE_PUBLISHED  # Not sure how to achieve this with reverse...
1059        create_page('home', 'col_two.html', 'en', published=True)
1060
1061        with self.login_user_context(staff_guy):
1062            multi_title_page = create_page('main_title', 'col_two.html', 'en', published=True,
1063                                           overwrite_url='overwritten_url',
1064                                           menu_title='menu_title')
1065
1066            title = multi_title_page.get_title_obj()
1067            title.page_title = 'page_title'
1068            title.save()
1069
1070            multi_title_page.save()
1071            publish_page(multi_title_page, admin, 'en')
1072
1073            # Non ajax call should return a 403 as this page shouldn't be accessed by anything else but ajax queries
1074            self.assertEqual(403, self.client.get(page_url).status_code)
1075
1076            self.assertEqual(200,
1077                             self.client.get(page_url, HTTP_X_REQUESTED_WITH='XMLHttpRequest').status_code
1078            )
1079
1080            # Test that the query param is working as expected.
1081            self.assertEqual(1, len(json.loads(self.client.get(page_url, {'q':'main_title'},
1082                                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest').content.decode("utf-8"))))
1083
1084            self.assertEqual(1, len(json.loads(self.client.get(page_url, {'q':'menu_title'},
1085                                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest').content.decode("utf-8"))))
1086
1087            self.assertEqual(1, len(json.loads(self.client.get(page_url, {'q':'overwritten_url'},
1088                                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest').content.decode("utf-8"))))
1089
1090            self.assertEqual(1, len(json.loads(self.client.get(page_url, {'q':'page_title'},
1091                                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest').content.decode("utf-8"))))
1092
1093
1094class AdminPageEditContentSizeTests(AdminTestsBase):
1095    """
1096    System user count influences the size of the page edit page,
1097    but the users are only 2 times present on the page
1098
1099    The test relates to extra=0
1100    at PagePermissionInlineAdminForm and ViewRestrictionInlineAdmin
1101    """
1102
1103    def test_editpage_contentsize(self):
1104        """
1105        Expected a username only 2 times in the content, but a relationship
1106        between usercount and pagesize
1107        """
1108        with self.settings(CMS_PERMISSION=True):
1109            admin_user = self.get_superuser()
1110            PAGE_NAME = 'TestPage'
1111            USER_NAME = 'test_size_user_0'
1112            current_site = Site.objects.get(pk=1)
1113            page = create_page(PAGE_NAME, "nav_playground.html", "en", site=current_site, created_by=admin_user)
1114            page.save()
1115            self._page = page
1116            with self.login_user_context(admin_user):
1117                url = base.URL_CMS_PAGE_PERMISSION_CHANGE % self._page.pk
1118                response = self.client.get(url)
1119                self.assertEqual(response.status_code, 200)
1120                old_response_size = len(response.content)
1121                old_user_count = get_user_model().objects.count()
1122                # create additionals user and reload the page
1123                get_user_model().objects.create_user(username=USER_NAME, email=USER_NAME + '@django-cms.org',
1124                                                     password=USER_NAME)
1125                user_count = get_user_model().objects.count()
1126                more_users_in_db = old_user_count < user_count
1127                # we have more users
1128                self.assertTrue(more_users_in_db, "New users got NOT created")
1129                response = self.client.get(url)
1130                new_response_size = len(response.content)
1131                page_size_grown = old_response_size < new_response_size
1132                # expect that the pagesize gets influenced by the useramount of the system
1133                self.assertTrue(page_size_grown, "Page size has not grown after user creation")
1134                # usernames are only 2 times in content
1135                text = smart_str(response.content, response.charset)
1136                foundcount = text.count(USER_NAME)
1137                # 2 forms contain usernames as options
1138                self.assertEqual(foundcount, 2,
1139                                 "Username %s appeared %s times in response.content, expected 2 times" % (
1140                                     USER_NAME, foundcount))
1141
1142
1143class AdminPageTreeTests(AdminTestsBase):
1144
1145    def test_move_node(self):
1146        admin_user, staff = self._get_guys()
1147        page_admin = self.admin_class
1148
1149        alpha = create_page('Alpha', 'nav_playground.html', 'en', published=True)
1150        beta = create_page('Beta', TEMPLATE_INHERITANCE_MAGIC, 'en', published=True)
1151        gamma = create_page('Gamma', TEMPLATE_INHERITANCE_MAGIC, 'en', published=True)
1152        delta = create_page('Delta', TEMPLATE_INHERITANCE_MAGIC, 'en', published=True)
1153
1154        # Current structure:
1155        #   <root>
1156        #   ⊢ Alpha
1157        #   ⊢ Beta
1158        #   ⊢ Gamma
1159        #   ⊢ Delta
1160
1161        # Move Beta to be a child of Alpha
1162        data = {
1163            'id': beta.pk,
1164            'position': 0,
1165            'target': alpha.pk,
1166        }
1167
1168        with self.login_user_context(admin_user):
1169            request = self.get_request(post_data=data)
1170            response = page_admin.move_page(request, page_id=beta.pk)
1171            data = json.loads(response.content.decode('utf8'))
1172
1173        self.assertEqual(response.status_code, 200)
1174        self.assertEqual(data['status'], 200)
1175        self.assertEqual(alpha.node._reload().get_descendants().count(), 1)
1176
1177        # Current structure:
1178        #   <root>
1179        #   ⊢ Alpha
1180        #       ⊢ Beta
1181        #   ⊢ Gamma
1182        #   ⊢ Delta
1183
1184        # Move Gamma to be a child of Beta
1185        data = {
1186            'id': gamma.pk,
1187            'position': 0,
1188            'target': beta.pk,
1189        }
1190
1191        with self.login_user_context(admin_user):
1192            request = self.get_request(post_data=data)
1193            response = page_admin.move_page(request, page_id=gamma.pk)
1194            data = json.loads(response.content.decode('utf8'))
1195
1196        self.assertEqual(response.status_code, 200)
1197        self.assertEqual(data['status'], 200)
1198        self.assertEqual(alpha.node._reload().get_descendants().count(), 2)
1199        self.assertEqual(beta.node._reload().get_descendants().count(), 1)
1200
1201        # Current structure:
1202        #   <root>
1203        #   ⊢ Alpha
1204        #       ⊢ Beta
1205        #           ⊢ Gamma
1206        #   ⊢ Delta
1207
1208        # Move Delta to be a child of Beta
1209        data = {
1210            'id': delta.pk,
1211            'position': 0,
1212            'target': gamma.pk,
1213        }
1214
1215        with self.login_user_context(admin_user):
1216            request = self.get_request(post_data=data)
1217            response = page_admin.move_page(request, page_id=delta.pk)
1218            data = json.loads(response.content.decode('utf8'))
1219
1220        self.assertEqual(response.status_code, 200)
1221        self.assertEqual(data['status'], 200)
1222        self.assertEqual(alpha.node._reload().get_descendants().count(), 3)
1223        self.assertEqual(beta.node._reload().get_descendants().count(), 2)
1224        self.assertEqual(gamma.node._reload().get_descendants().count(), 1)
1225
1226        # Current structure:
1227        #   <root>
1228        #   ⊢ Alpha
1229        #       ⊢ Beta
1230        #           ⊢ Gamma
1231        #               ⊢ Delta
1232
1233        # Move Beta to the root as node #1 (positions are 0-indexed)
1234        data = {
1235            'id': beta.pk,
1236            'position': 1,
1237        }
1238
1239        with self.login_user_context(admin_user):
1240            request = self.get_request(post_data=data)
1241            response = page_admin.move_page(request, page_id=beta.pk)
1242            data = json.loads(response.content.decode('utf8'))
1243
1244        self.assertEqual(response.status_code, 200)
1245        self.assertEqual(data['status'], 200)
1246        self.assertEqual(alpha.node._reload().get_descendants().count(), 0)
1247        self.assertEqual(beta.node._reload().get_descendants().count(), 2)
1248        self.assertEqual(gamma.node._reload().get_descendants().count(), 1)
1249
1250        # Current structure:
1251        #   <root>
1252        #   ⊢ Alpha
1253        #   ⊢ Beta
1254        #       ⊢ Gamma
1255        #           ⊢ Delta
1256
1257        # Move Beta to be a child of Alpha again
1258        data = {
1259            'id': beta.pk,
1260            'position': 0,
1261            'target': alpha.pk,
1262        }
1263
1264        with self.login_user_context(admin_user):
1265            request = self.get_request(post_data=data)
1266            response = page_admin.move_page(request, page_id=beta.pk)
1267            data = json.loads(response.content.decode('utf8'))
1268
1269        self.assertEqual(response.status_code, 200)
1270        self.assertEqual(data['status'], 200)
1271        self.assertEqual(alpha.node._reload().get_descendants().count(), 3)
1272        self.assertEqual(beta.node._reload().get_descendants().count(), 2)
1273        self.assertEqual(gamma.node._reload().get_descendants().count(), 1)
1274
1275        # Current structure:
1276        #   <root>
1277        #   ⊢ Alpha
1278        #       ⊢ Beta
1279        #           ⊢ Gamma
1280        #               ⊢ Delta
1281
1282        # Move Gamma to the root as node #1 (positions are 0-indexed)
1283        data = {
1284            'id': gamma.pk,
1285            'position': 1,
1286        }
1287
1288        with self.login_user_context(admin_user):
1289            request = self.get_request(post_data=data)
1290            response = page_admin.move_page(request, page_id=gamma.pk)
1291            data = json.loads(response.content.decode('utf8'))
1292
1293        self.assertEqual(response.status_code, 200)
1294        self.assertEqual(data['status'], 200)
1295        self.assertEqual(alpha.node._reload().get_descendants().count(), 1)
1296        self.assertEqual(beta.node._reload().get_descendants().count(), 0)
1297        self.assertEqual(gamma.node._reload().get_descendants().count(), 1)
1298
1299        # Current structure:
1300        #   <root>
1301        #   ⊢ Alpha
1302        #       ⊢ Beta
1303        #   ⊢ Gamma
1304        #       ⊢ Delta
1305
1306        # Move Delta to the root as node #1 (positions are 0-indexed)
1307        data = {
1308            'id': delta.pk,
1309            'position': 1,
1310        }
1311
1312        with self.login_user_context(admin_user):
1313            request = self.get_request(post_data=data)
1314            response = page_admin.move_page(request, page_id=delta.pk)
1315            data = json.loads(response.content.decode('utf8'))
1316
1317        self.assertEqual(response.status_code, 200)
1318        self.assertEqual(data['status'], 200)
1319        self.assertEqual(alpha.node._reload().get_descendants().count(), 1)
1320        self.assertEqual(beta.node._reload().get_descendants().count(), 0)
1321        self.assertEqual(gamma.node._reload().get_descendants().count(), 0)
1322
1323        # Current structure:
1324        #   <root>
1325        #   ⊢ Alpha
1326        #       ⊢ Beta
1327        #   ⊢ Delta
1328        #   ⊢ Gamma
1329
1330        # Move Gamma to be a child of Delta
1331        data = {
1332            'id': gamma.pk,
1333            'position': 1,
1334            'target': delta.pk,
1335        }
1336
1337        with self.login_user_context(admin_user):
1338            request = self.get_request(post_data=data)
1339            response = page_admin.move_page(request, page_id=gamma.pk)
1340            data = json.loads(response.content.decode('utf8'))
1341
1342        self.assertEqual(response.status_code, 200)
1343        self.assertEqual(data['status'], 200)
1344        self.assertEqual(alpha.node._reload().get_descendants().count(), 1)
1345        self.assertEqual(beta.node._reload().get_descendants().count(), 0)
1346        self.assertEqual(gamma.node._reload().get_descendants().count(), 0)
1347        self.assertEqual(delta.node._reload().get_descendants().count(), 1)
1348
1349        # Final structure:
1350        #   <root>
1351        #   ⊢ Alpha
1352        #       ⊢ Beta
1353        #   ⊢ Delta
1354        #       ⊢ Gamma
1355