1# -*- coding: utf-8 -*-
2
3import json
4import unittest
5
6from django import VERSION as DJANGO_VERSION
7from django.conf import settings
8from django.contrib.auth.models import Group, Permission
9from django.core import mail
10from django.core.management import call_command
11from django.test import TestCase, override_settings
12from django.urls import reverse, reverse_lazy
13from django.utils.translation import gettext_lazy as _
14from taggit.models import Tag
15
16from wagtail.admin.auth import user_has_any_page_permission
17from wagtail.admin.mail import send_mail
18from wagtail.admin.menu import MenuItem
19from wagtail.core.models import Page
20from wagtail.tests.testapp.models import RestaurantTag
21from wagtail.tests.utils import WagtailTestUtils
22
23
24class TestHome(TestCase, WagtailTestUtils):
25    def setUp(self):
26        # Login
27        self.login()
28
29    def test_simple(self):
30        response = self.client.get(reverse('wagtailadmin_home'))
31        self.assertEqual(response.status_code, 200)
32        self.assertContains(response, "Welcome to the Test Site Wagtail CMS")
33
34    def test_admin_menu(self):
35        response = self.client.get(reverse('wagtailadmin_home'))
36        self.assertEqual(response.status_code, 200)
37        # check that media attached to menu items is correctly pulled in
38        if DJANGO_VERSION >= (3, 1):
39            self.assertContains(
40                response,
41                '<script src="/static/testapp/js/kittens.js"></script>',
42                html=True
43            )
44        else:
45            self.assertContains(
46                response,
47                '<script type="text/javascript" src="/static/testapp/js/kittens.js"></script>',
48                html=True
49            )
50
51        # check that custom menu items (including classname / attrs parameters) are pulled in
52        self.assertContains(
53            response,
54            '<a href="http://www.tomroyal.com/teaandkittens/" class="icon icon-kitten" data-fluffy="yes">Kittens!</a>',
55            html=True
56        )
57
58        # Check that the explorer menu item is here, with the right start page.
59        self.assertContains(
60            response,
61            'data-explorer-start-page="1"'
62        )
63
64        # check that is_shown is respected on menu items
65        response = self.client.get(reverse('wagtailadmin_home') + '?hide-kittens=true')
66        self.assertNotContains(
67            response,
68            '<a href="http://www.tomroyal.com/teaandkittens/" class="icon icon-kitten" data-fluffy="yes">Kittens!</a>'
69        )
70
71    def test_never_cache_header(self):
72        # This tests that wagtailadmins global cache settings have been applied correctly
73        response = self.client.get(reverse('wagtailadmin_home'))
74
75        self.assertIn('no-cache', response['Cache-Control'])
76        self.assertIn('no-store', response['Cache-Control'])
77        self.assertIn('max-age=0', response['Cache-Control'])
78        self.assertIn('must-revalidate', response['Cache-Control'])
79
80    @unittest.skipIf(settings.AUTH_USER_MODEL == 'emailuser.EmailUser', "Only applicable to CustomUser")
81    def test_nonascii_email(self):
82        # Test that non-ASCII email addresses don't break the admin; previously these would
83        # cause a failure when generating Gravatar URLs
84        self.create_superuser(username='snowman', email='☃@thenorthpole.com', password='password')
85        # Login
86        self.assertTrue(self.client.login(username='snowman', password='password'))
87        response = self.client.get(reverse('wagtailadmin_home'))
88        self.assertEqual(response.status_code, 200)
89
90
91class TestEditorHooks(TestCase, WagtailTestUtils):
92    def setUp(self):
93        self.homepage = Page.objects.get(id=2)
94        self.login()
95
96    def test_editor_css_hooks_on_add(self):
97        response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.homepage.id)))
98        self.assertEqual(response.status_code, 200)
99        self.assertContains(response, '<link rel="stylesheet" href="/path/to/my/custom.css">')
100
101    def test_editor_js_hooks_on_add(self):
102        response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.homepage.id)))
103        self.assertEqual(response.status_code, 200)
104        self.assertContains(response, '<script src="/path/to/my/custom.js"></script>')
105
106    def test_editor_css_hooks_on_edit(self):
107        response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.homepage.id, )))
108        self.assertEqual(response.status_code, 200)
109        self.assertContains(response, '<link rel="stylesheet" href="/path/to/my/custom.css">')
110
111    def test_editor_js_hooks_on_edit(self):
112        response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.homepage.id, )))
113        self.assertEqual(response.status_code, 200)
114        self.assertContains(response, '<script src="/path/to/my/custom.js"></script>')
115
116
117class TestSendMail(TestCase):
118    def test_send_email(self):
119        send_mail("Test subject", "Test content", ["nobody@email.com"], "test@email.com")
120
121        # Check that the email was sent
122        self.assertEqual(len(mail.outbox), 1)
123        self.assertEqual(mail.outbox[0].subject, "Test subject")
124        self.assertEqual(mail.outbox[0].body, "Test content")
125        self.assertEqual(mail.outbox[0].to, ["nobody@email.com"])
126        self.assertEqual(mail.outbox[0].from_email, "test@email.com")
127
128    @override_settings(WAGTAILADMIN_NOTIFICATION_FROM_EMAIL='anothertest@email.com')
129    def test_send_fallback_to_wagtailadmin_notification_from_email_setting(self):
130        send_mail("Test subject", "Test content", ["nobody@email.com"])
131
132        # Check that the email was sent
133        self.assertEqual(len(mail.outbox), 1)
134        self.assertEqual(mail.outbox[0].subject, "Test subject")
135        self.assertEqual(mail.outbox[0].body, "Test content")
136        self.assertEqual(mail.outbox[0].to, ["nobody@email.com"])
137        self.assertEqual(mail.outbox[0].from_email, "anothertest@email.com")
138
139    @override_settings(DEFAULT_FROM_EMAIL='yetanothertest@email.com')
140    def test_send_fallback_to_default_from_email_setting(self):
141        send_mail("Test subject", "Test content", ["nobody@email.com"])
142
143        # Check that the email was sent
144        self.assertEqual(len(mail.outbox), 1)
145        self.assertEqual(mail.outbox[0].subject, "Test subject")
146        self.assertEqual(mail.outbox[0].body, "Test content")
147        self.assertEqual(mail.outbox[0].to, ["nobody@email.com"])
148        self.assertEqual(mail.outbox[0].from_email, "yetanothertest@email.com")
149
150    def test_send_default_from_email(self):
151        send_mail("Test subject", "Test content", ["nobody@email.com"])
152
153        # Check that the email was sent
154        self.assertEqual(len(mail.outbox), 1)
155        self.assertEqual(mail.outbox[0].subject, "Test subject")
156        self.assertEqual(mail.outbox[0].body, "Test content")
157        self.assertEqual(mail.outbox[0].to, ["nobody@email.com"])
158        self.assertEqual(mail.outbox[0].from_email, "webmaster@localhost")
159
160    def test_send_html_email(self):
161        """Test that the kwarg 'html_message' works as expected on send_mail by creating 'alternatives' on the EmailMessage object"""
162
163        send_mail("Test HTML subject", "TEXT content", ["has.html@email.com"], html_message="<h2>Test HTML content</h2>")
164        send_mail("Test TEXT subject", "TEXT content", ["mr.plain.text@email.com"])
165
166        # Check that the emails were sent
167        self.assertEqual(len(mail.outbox), 2)
168
169        # check that the first email is the HTML email
170        email_message = mail.outbox[0]
171        self.assertEqual(email_message.subject, "Test HTML subject")
172        self.assertEqual(email_message.alternatives, [('<h2>Test HTML content</h2>', 'text/html')])
173        self.assertEqual(email_message.body, "TEXT content")  # note: plain text will alwasy be added to body, even with alternatives
174        self.assertEqual(email_message.to, ["has.html@email.com"])
175
176        # confirm that without html_message kwarg we do not get 'alternatives'
177        email_message = mail.outbox[1]
178        self.assertEqual(email_message.subject, "Test TEXT subject")
179        self.assertEqual(email_message.alternatives, [])
180        self.assertEqual(email_message.body, "TEXT content")
181        self.assertEqual(email_message.to, ["mr.plain.text@email.com"])
182
183
184class TestTagsAutocomplete(TestCase, WagtailTestUtils):
185    def setUp(self):
186        self.login()
187        Tag.objects.create(name="Test", slug="test")
188        RestaurantTag.objects.create(name="Italian", slug="italian")
189        RestaurantTag.objects.create(name="Indian", slug="indian")
190
191    def test_tags_autocomplete(self):
192        response = self.client.get(reverse('wagtailadmin_tag_autocomplete'), {
193            'term': 'test'
194        })
195
196        self.assertEqual(response.status_code, 200)
197        self.assertEqual(response['Content-Type'], 'application/json')
198        data = json.loads(response.content.decode('utf-8'))
199
200        self.assertEqual(data, ['Test'])
201
202    def test_tags_autocomplete_partial_match(self):
203        response = self.client.get(reverse('wagtailadmin_tag_autocomplete'), {
204            'term': 'te'
205        })
206
207        self.assertEqual(response.status_code, 200)
208        self.assertEqual(response['Content-Type'], 'application/json')
209        data = json.loads(response.content.decode('utf-8'))
210
211        self.assertEqual(data, ['Test'])
212
213    def test_tags_autocomplete_different_term(self):
214        response = self.client.get(reverse('wagtailadmin_tag_autocomplete'), {
215            'term': 'hello'
216        })
217
218        self.assertEqual(response.status_code, 200)
219        self.assertEqual(response['Content-Type'], 'application/json')
220        data = json.loads(response.content.decode('utf-8'))
221
222        self.assertEqual(data, [])
223
224    def test_tags_autocomplete_no_term(self):
225        response = self.client.get(reverse('wagtailadmin_tag_autocomplete'))
226
227        self.assertEqual(response.status_code, 200)
228        self.assertEqual(response['Content-Type'], 'application/json')
229        data = json.loads(response.content.decode('utf-8'))
230        self.assertEqual(data, [])
231
232    def test_tags_autocomplete_custom_model(self):
233        response = self.client.get(
234            reverse('wagtailadmin_tag_model_autocomplete', args=('tests', 'restauranttag')),
235            {'term': 'ital'}
236        )
237
238        self.assertEqual(response.status_code, 200)
239        self.assertEqual(response['Content-Type'], 'application/json')
240        data = json.loads(response.content.decode('utf-8'))
241
242        self.assertEqual(data, ['Italian'])
243
244        # should not return results from the standard Tag model
245        response = self.client.get(
246            reverse('wagtailadmin_tag_model_autocomplete', args=('tests', 'restauranttag')),
247            {'term': 'test'}
248        )
249
250        self.assertEqual(response.status_code, 200)
251        self.assertEqual(response['Content-Type'], 'application/json')
252        data = json.loads(response.content.decode('utf-8'))
253
254        self.assertEqual(data, [])
255
256
257class TestMenuItem(TestCase, WagtailTestUtils):
258    def setUp(self):
259        self.login()
260        response = self.client.get(reverse('wagtailadmin_home'))
261        self.request = response.wsgi_request
262
263    def test_menuitem_reverse_lazy_url_pass(self):
264        menuitem = MenuItem(_('Test'), reverse_lazy('wagtailadmin_home'))
265        self.assertEqual(menuitem.is_active(self.request), True)
266
267
268class TestUserPassesTestPermissionDecorator(TestCase, WagtailTestUtils):
269    """
270    Test for custom user_passes_test permission decorators.
271    testapp_bob_only_zone is a view configured to only grant access to users with a first_name of Bob
272    """
273    def test_user_passes_test(self):
274        # create and log in as a user called Bob
275        self.create_superuser(first_name='Bob', last_name='Mortimer', username='test', password='password')
276        self.login(username='test', password='password')
277
278        response = self.client.get(reverse('testapp_bob_only_zone'))
279        self.assertEqual(response.status_code, 200)
280
281    def test_user_fails_test(self):
282        # create and log in as a user not called Bob
283        self.create_superuser(first_name='Vic', last_name='Reeves', username='test', password='password')
284        self.login(username='test', password='password')
285
286        response = self.client.get(reverse('testapp_bob_only_zone'))
287        self.assertRedirects(response, reverse('wagtailadmin_home'))
288
289    def test_user_fails_test_ajax(self):
290        # create and log in as a user not called Bob
291        self.create_superuser(first_name='Vic', last_name='Reeves', username='test', password='password')
292        self.login(username='test', password='password')
293
294        response = self.client.get(reverse('testapp_bob_only_zone'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
295        self.assertEqual(response.status_code, 403)
296
297
298class TestUserHasAnyPagePermission(TestCase, WagtailTestUtils):
299    def test_superuser(self):
300        user = self.create_superuser(
301            username='superuser', email='admin@example.com', password='p')
302        self.assertTrue(user_has_any_page_permission(user))
303
304    def test_inactive_superuser(self):
305        user = self.create_superuser(
306            username='superuser', email='admin@example.com', password='p')
307        user.is_active = False
308        self.assertFalse(user_has_any_page_permission(user))
309
310    def test_editor(self):
311        user = self.create_user(
312            username='editor', email='ed@example.com', password='p')
313        editors = Group.objects.get(name='Editors')
314        user.groups.add(editors)
315        self.assertTrue(user_has_any_page_permission(user))
316
317    def test_moderator(self):
318        user = self.create_user(
319            username='moderator', email='mod@example.com', password='p')
320        editors = Group.objects.get(name='Moderators')
321        user.groups.add(editors)
322        self.assertTrue(user_has_any_page_permission(user))
323
324    def test_no_permissions(self):
325        user = self.create_user(
326            username='pleb', email='pleb@example.com', password='p')
327        user.user_permissions.add(
328            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
329        )
330        self.assertFalse(user_has_any_page_permission(user))
331
332
333class Test404(TestCase, WagtailTestUtils):
334    def test_admin_404_template_used_append_slash_true(self):
335        self.login()
336        with self.settings(APPEND_SLASH=True):
337            response = self.client.get('/admin/sdfgdsfgdsfgsdf', follow=True)
338
339            # Check 404 error after CommonMiddleware redirect
340            self.assertEqual(response.status_code, 404)
341            self.assertTemplateUsed(response, 'wagtailadmin/404.html')
342
343    def test_not_logged_in_redirect(self):
344        response = self.client.get('/admin/sdfgdsfgdsfgsdf/')
345
346        # Check that the user was redirected to the login page and that next was set correctly
347        self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=/admin/sdfgdsfgdsfgsdf/')
348
349
350class TestAdminURLAppendSlash(TestCase, WagtailTestUtils):
351    def setUp(self):
352        # Find root page
353        self.root_page = Page.objects.get(id=2)
354
355    def test_return_correct_view_for_correct_url_without_ending_slash(self):
356        self.login()
357        with self.settings(APPEND_SLASH=True):
358            # Remove trailing slash from URL
359            response = self.client.get(reverse('wagtailadmin_explore_root')[:-1], follow=True)
360
361            # Check that correct page is returned after CommonMiddleware redirect
362            self.assertEqual(response.status_code, 200)
363            self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html')
364            self.assertEqual(Page.objects.get(id=1), response.context['parent_page'])
365            self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.root_page.id).exists())
366
367
368class TestRemoveStaleContentTypes(TestCase):
369    def test_remove_stale_content_types_preserves_access_admin_permission(self):
370        call_command('remove_stale_contenttypes', interactive=False)
371        self.assertTrue(
372            Permission.objects.filter(content_type__app_label='wagtailadmin', codename='access_admin').exists()
373        )
374