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