1import datetime 2import os 3 4from unittest import mock 5 6from django.conf import settings 7from django.contrib.auth.models import Group, Permission 8from django.core import mail 9from django.core.files.base import ContentFile 10from django.http import HttpRequest, HttpResponse 11from django.test import TestCase, modify_settings, override_settings 12from django.urls import reverse 13from django.utils import timezone 14from django.utils.translation import gettext_lazy as _ 15 16from wagtail.admin.tests.pages.timestamps import submittable_timestamp 17from wagtail.core.exceptions import PageClassNotFoundError 18from wagtail.core.models import ( 19 Comment, CommentReply, GroupPagePermission, Locale, Page, PageLogEntry, PageRevision, 20 PageSubscription, Site) 21from wagtail.core.signals import page_published 22from wagtail.tests.testapp.models import ( 23 EVENT_AUDIENCE_CHOICES, Advert, AdvertPlacement, EventCategory, EventPage, 24 EventPageCarouselItem, FilePage, ManyToManyBlogPage, SimplePage, SingleEventPage, StandardIndex, 25 TaggedPage) 26from wagtail.tests.utils import WagtailTestUtils 27from wagtail.tests.utils.form_data import inline_formset, nested_form_data 28from wagtail.users.models import UserProfile 29 30 31class TestPageEdit(TestCase, WagtailTestUtils): 32 def setUp(self): 33 # Find root page 34 self.root_page = Page.objects.get(id=2) 35 36 # Add child page 37 child_page = SimplePage( 38 title="Hello world!", 39 slug="hello-world", 40 content="hello", 41 ) 42 self.root_page.add_child(instance=child_page) 43 child_page.save_revision().publish() 44 self.child_page = SimplePage.objects.get(id=child_page.id) 45 46 # Add file page 47 fake_file = ContentFile("File for testing multipart") 48 fake_file.name = 'test.txt' 49 file_page = FilePage( 50 title="File Page", 51 slug="file-page", 52 file_field=fake_file, 53 ) 54 self.root_page.add_child(instance=file_page) 55 file_page.save_revision().publish() 56 self.file_page = FilePage.objects.get(id=file_page.id) 57 58 # Add event page (to test edit handlers) 59 self.event_page = EventPage( 60 title="Event page", slug="event-page", 61 location='the moon', audience='public', 62 cost='free', date_from='2001-01-01', 63 ) 64 self.root_page.add_child(instance=self.event_page) 65 66 # Add single event page (to test custom URL routes) 67 self.single_event_page = SingleEventPage( 68 title="Mars landing", slug="mars-landing", 69 location='mars', audience='public', 70 cost='free', date_from='2001-01-01', 71 ) 72 self.root_page.add_child(instance=self.single_event_page) 73 74 self.unpublished_page = SimplePage( 75 title="Hello unpublished world!", 76 slug="hello-unpublished-world", 77 content="hello", 78 live=False, 79 has_unpublished_changes=True, 80 ) 81 self.root_page.add_child(instance=self.unpublished_page) 82 83 # Login 84 self.user = self.login() 85 86 def test_page_edit(self): 87 # Tests that the edit page loads 88 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 89 self.assertEqual(response.status_code, 200) 90 self.assertEqual(response['Content-Type'], "text/html; charset=utf-8") 91 self.assertContains(response, '<li class="header-meta--status">Published</li>', html=True) 92 93 # Test InlinePanel labels/headings 94 self.assertContains(response, '<legend>Speaker lineup</legend>') 95 self.assertContains(response, 'Add speakers') 96 97 # test register_page_action_menu_item hook 98 self.assertContains(response, 99 '<button type="submit" name="action-panic" value="Panic!" class="button">Panic!</button>') 100 self.assertContains(response, 'testapp/js/siren.js') 101 102 # test construct_page_action_menu hook 103 self.assertContains(response, 104 '<button type="submit" name="action-relax" value="Relax." class="button">Relax.</button>') 105 106 # test that workflow actions are shown 107 self.assertContains( 108 response, '<button type="submit" name="action-submit" value="Submit to Moderators approval" class="button">' 109 ) 110 111 @override_settings(WAGTAIL_WORKFLOW_ENABLED=False) 112 def test_workflow_buttons_not_shown_when_workflow_disabled(self): 113 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 114 self.assertEqual(response.status_code, 200) 115 self.assertNotContains( 116 response, 'value="Submit to Moderators approval"' 117 ) 118 119 def test_edit_draft_page_with_no_revisions(self): 120 # Tests that the edit page loads 121 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.unpublished_page.id, ))) 122 self.assertEqual(response.status_code, 200) 123 self.assertContains(response, '<li class="header-meta--status">Draft</li>', html=True) 124 125 def test_edit_multipart(self): 126 """ 127 Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding. 128 """ 129 # check for SimplePage where is no file field 130 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 131 self.assertEqual(response.status_code, 200) 132 self.assertNotContains(response, 'enctype="multipart/form-data"') 133 self.assertTemplateUsed(response, 'wagtailadmin/pages/edit.html') 134 135 # check for FilePage which has file field 136 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.file_page.id, ))) 137 self.assertEqual(response.status_code, 200) 138 self.assertContains(response, 'enctype="multipart/form-data"') 139 140 @mock.patch('wagtail.core.models.ContentType.model_class', return_value=None) 141 def test_edit_when_specific_class_cannot_be_found(self, mocked_method): 142 with self.assertRaises(PageClassNotFoundError): 143 self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 144 145 def test_upload_file_publish(self): 146 """ 147 Check that file uploads work when directly publishing 148 """ 149 file_upload = ContentFile(b"A new file", name='published-file.txt') 150 post_data = { 151 'title': 'New file', 152 'slug': 'new-file', 153 'file_field': file_upload, 154 'action-publish': "Publish", 155 } 156 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) 157 158 # Should be redirected to explorer 159 self.assertRedirects(response, reverse('wagtailadmin_explore', args=[self.root_page.id])) 160 161 # Check the new file exists 162 file_page = FilePage.objects.get() 163 164 self.assertEqual(file_page.file_field.name, file_upload.name) 165 self.assertTrue(os.path.exists(file_page.file_field.path)) 166 self.assertEqual(file_page.file_field.read(), b"A new file") 167 168 def test_upload_file_draft(self): 169 """ 170 Check that file uploads work when saving a draft 171 """ 172 file_upload = ContentFile(b"A new file", name='draft-file.txt') 173 post_data = { 174 'title': 'New file', 175 'slug': 'new-file', 176 'file_field': file_upload, 177 } 178 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) 179 180 # Should be redirected to edit page 181 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.file_page.id])) 182 183 # Check the file was uploaded 184 file_path = os.path.join(settings.MEDIA_ROOT, file_upload.name) 185 self.assertTrue(os.path.exists(file_path)) 186 with open(file_path, 'rb') as saved_file: 187 self.assertEqual(saved_file.read(), b"A new file") 188 189 # Publish the draft just created 190 FilePage.objects.get().get_latest_revision().publish() 191 192 # Get the file page, check the file is set 193 file_page = FilePage.objects.get() 194 self.assertEqual(file_page.file_field.name, file_upload.name) 195 self.assertTrue(os.path.exists(file_page.file_field.path)) 196 self.assertEqual(file_page.file_field.read(), b"A new file") 197 198 def test_page_edit_bad_permissions(self): 199 # Remove privileges from user 200 self.user.is_superuser = False 201 self.user.user_permissions.add( 202 Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') 203 ) 204 self.user.save() 205 206 # Get edit page 207 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 208 209 # Check that the user received a 302 redirected response 210 self.assertEqual(response.status_code, 302) 211 212 def test_page_edit_post(self): 213 # Tests simple editing 214 post_data = { 215 'title': "I've been edited!", 216 'content': "Some content", 217 'slug': 'hello-world', 218 } 219 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 220 221 # Should be redirected to edit page 222 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 223 224 # The page should have "has_unpublished_changes" flag set 225 child_page_new = SimplePage.objects.get(id=self.child_page.id) 226 self.assertTrue(child_page_new.has_unpublished_changes) 227 228 # Page fields should not be changed (because we just created a new draft) 229 self.assertEqual(child_page_new.title, self.child_page.title) 230 self.assertEqual(child_page_new.content, self.child_page.content) 231 self.assertEqual(child_page_new.slug, self.child_page.slug) 232 233 # The draft_title should have a new title 234 self.assertEqual(child_page_new.draft_title, post_data['title']) 235 236 def test_page_edit_post_when_locked(self): 237 # Tests that trying to edit a locked page results in an error 238 239 # Lock the page 240 self.child_page.locked = True 241 self.child_page.save() 242 243 # Post 244 post_data = { 245 'title': "I've been edited!", 246 'content': "Some content", 247 'slug': 'hello-world', 248 } 249 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 250 251 # Shouldn't be redirected 252 self.assertContains(response, "The page could not be saved as it is locked") 253 254 # The page shouldn't have "has_unpublished_changes" flag set 255 child_page_new = SimplePage.objects.get(id=self.child_page.id) 256 self.assertFalse(child_page_new.has_unpublished_changes) 257 258 def test_edit_post_scheduled(self): 259 # put go_live_at and expire_at several days away from the current date, to avoid 260 # false matches in content_json__contains tests 261 go_live_at = timezone.now() + datetime.timedelta(days=10) 262 expire_at = timezone.now() + datetime.timedelta(days=20) 263 post_data = { 264 'title': "I've been edited!", 265 'content': "Some content", 266 'slug': 'hello-world', 267 'go_live_at': submittable_timestamp(go_live_at), 268 'expire_at': submittable_timestamp(expire_at), 269 } 270 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 271 272 # Should be redirected to explorer page 273 self.assertEqual(response.status_code, 302) 274 275 child_page_new = SimplePage.objects.get(id=self.child_page.id) 276 277 # The page will still be live 278 self.assertTrue(child_page_new.live) 279 280 # A revision with approved_go_live_at should not exist 281 self.assertFalse(PageRevision.objects.filter( 282 page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 283 ) 284 285 # But a revision with go_live_at and expire_at in their content json *should* exist 286 self.assertTrue(PageRevision.objects.filter( 287 page=child_page_new, content_json__contains=str(go_live_at.date())).exists() 288 ) 289 self.assertTrue( 290 PageRevision.objects.filter(page=child_page_new, content_json__contains=str(expire_at.date())).exists() 291 ) 292 293 def test_edit_scheduled_go_live_before_expiry(self): 294 post_data = { 295 'title': "I've been edited!", 296 'content': "Some content", 297 'slug': 'hello-world', 298 'go_live_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=2)), 299 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=1)), 300 } 301 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 302 303 self.assertEqual(response.status_code, 200) 304 305 # Check that a form error was raised 306 self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time") 307 self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time") 308 309 # form should be marked as having unsaved changes for the purposes of the dirty-forms warning 310 self.assertContains(response, "alwaysDirty: true") 311 312 def test_edit_scheduled_expire_in_the_past(self): 313 post_data = { 314 'title': "I've been edited!", 315 'content': "Some content", 316 'slug': 'hello-world', 317 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=-1)), 318 } 319 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 320 321 self.assertEqual(response.status_code, 200) 322 323 # Check that a form error was raised 324 self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future") 325 326 # form should be marked as having unsaved changes for the purposes of the dirty-forms warning 327 self.assertContains(response, "alwaysDirty: true") 328 329 def test_page_edit_post_publish(self): 330 # Connect a mock signal handler to page_published signal 331 mock_handler = mock.MagicMock() 332 page_published.connect(mock_handler) 333 334 # Set has_unpublished_changes=True on the existing record to confirm that the publish action 335 # is resetting it (and not just leaving it alone) 336 self.child_page.has_unpublished_changes = True 337 self.child_page.save() 338 339 # Save current value of first_published_at so we can check that it doesn't change 340 first_published_at = SimplePage.objects.get(id=self.child_page.id).first_published_at 341 342 # Tests publish from edit page 343 post_data = { 344 'title': "I've been edited!", 345 'content': "Some content", 346 'slug': 'hello-world-new', 347 'action-publish': "Publish", 348 } 349 response = self.client.post( 350 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data, follow=True 351 ) 352 353 # Should be redirected to explorer 354 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) 355 356 # Check that the page was edited 357 child_page_new = SimplePage.objects.get(id=self.child_page.id) 358 self.assertEqual(child_page_new.title, post_data['title']) 359 self.assertEqual(child_page_new.draft_title, post_data['title']) 360 361 # Check that the page_published signal was fired 362 self.assertEqual(mock_handler.call_count, 1) 363 mock_call = mock_handler.mock_calls[0][2] 364 365 self.assertEqual(mock_call['sender'], child_page_new.specific_class) 366 self.assertEqual(mock_call['instance'], child_page_new) 367 self.assertIsInstance(mock_call['instance'], child_page_new.specific_class) 368 369 # The page shouldn't have "has_unpublished_changes" flag set 370 self.assertFalse(child_page_new.has_unpublished_changes) 371 372 # first_published_at should not change as it was already set 373 self.assertEqual(first_published_at, child_page_new.first_published_at) 374 375 # The "View Live" button should have the updated slug. 376 for message in response.context['messages']: 377 self.assertIn('hello-world-new', message.message) 378 break 379 380 def test_first_published_at_editable(self): 381 """Test that we can update the first_published_at via the Page edit form, 382 for page models that expose it.""" 383 384 # Add child page, of a type which has first_published_at in its form 385 child_page = ManyToManyBlogPage( 386 title="Hello world!", 387 slug="hello-again-world", 388 body="hello", 389 ) 390 self.root_page.add_child(instance=child_page) 391 child_page.save_revision().publish() 392 self.child_page = ManyToManyBlogPage.objects.get(id=child_page.id) 393 394 initial_delta = self.child_page.first_published_at - timezone.now() 395 396 first_published_at = timezone.now() - datetime.timedelta(days=2) 397 398 post_data = { 399 'title': "I've been edited!", 400 'body': "Some content", 401 'slug': 'hello-again-world', 402 'action-publish': "Publish", 403 'first_published_at': submittable_timestamp(first_published_at), 404 'comments-TOTAL_FORMS': 0, 405 'comments-INITIAL_FORMS': 0, 406 'comments-MIN_NUM_FORMS': 0, 407 'comments-MAX_NUM_FORMS': 1000, 408 } 409 self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 410 411 # Get the edited page. 412 child_page_new = ManyToManyBlogPage.objects.get(id=self.child_page.id) 413 414 # first_published_at should have changed. 415 new_delta = child_page_new.first_published_at - timezone.now() 416 self.assertNotEqual(new_delta.days, initial_delta.days) 417 # first_published_at should be 3 days ago. 418 self.assertEqual(new_delta.days, -3) 419 420 def test_edit_post_publish_scheduled_unpublished_page(self): 421 # Unpublish the page 422 self.child_page.live = False 423 self.child_page.save() 424 425 go_live_at = timezone.now() + datetime.timedelta(days=1) 426 expire_at = timezone.now() + datetime.timedelta(days=2) 427 post_data = { 428 'title': "I've been edited!", 429 'content': "Some content", 430 'slug': 'hello-world', 431 'action-publish': "Publish", 432 'go_live_at': submittable_timestamp(go_live_at), 433 'expire_at': submittable_timestamp(expire_at), 434 } 435 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 436 437 # Should be redirected to explorer page 438 self.assertEqual(response.status_code, 302) 439 440 child_page_new = SimplePage.objects.get(id=self.child_page.id) 441 442 # The page should not be live anymore 443 self.assertFalse(child_page_new.live) 444 445 # Instead a revision with approved_go_live_at should now exist 446 self.assertTrue( 447 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 448 ) 449 450 # The page SHOULD have the "has_unpublished_changes" flag set, 451 # because the changes are not visible as a live page yet 452 self.assertTrue( 453 child_page_new.has_unpublished_changes, 454 "A page scheduled for future publishing should have has_unpublished_changes=True" 455 ) 456 457 self.assertEqual(child_page_new.status_string, "scheduled") 458 459 def test_edit_post_publish_now_an_already_scheduled_unpublished_page(self): 460 # Unpublish the page 461 self.child_page.live = False 462 self.child_page.save() 463 464 # First let's publish a page with a go_live_at in the future 465 go_live_at = timezone.now() + datetime.timedelta(days=1) 466 expire_at = timezone.now() + datetime.timedelta(days=2) 467 post_data = { 468 'title': "I've been edited!", 469 'content': "Some content", 470 'slug': 'hello-world', 471 'action-publish': "Publish", 472 'go_live_at': submittable_timestamp(go_live_at), 473 'expire_at': submittable_timestamp(expire_at), 474 } 475 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 476 477 # Should be redirected to edit page 478 self.assertEqual(response.status_code, 302) 479 480 child_page_new = SimplePage.objects.get(id=self.child_page.id) 481 482 # The page should not be live 483 self.assertFalse(child_page_new.live) 484 485 self.assertEqual(child_page_new.status_string, "scheduled") 486 487 # Instead a revision with approved_go_live_at should now exist 488 self.assertTrue( 489 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 490 ) 491 492 # Now, let's edit it and publish it right now 493 go_live_at = timezone.now() 494 post_data = { 495 'title': "I've been edited!", 496 'content': "Some content", 497 'slug': 'hello-world', 498 'action-publish': "Publish", 499 'go_live_at': "", 500 } 501 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 502 503 # Should be redirected to edit page 504 self.assertEqual(response.status_code, 302) 505 506 child_page_new = SimplePage.objects.get(id=self.child_page.id) 507 508 # The page should be live now 509 self.assertTrue(child_page_new.live) 510 511 # And a revision with approved_go_live_at should not exist 512 self.assertFalse( 513 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 514 ) 515 516 def test_edit_post_publish_scheduled_published_page(self): 517 # Page is live 518 self.child_page.live = True 519 self.child_page.save() 520 521 live_revision = self.child_page.live_revision 522 original_title = self.child_page.title 523 524 go_live_at = timezone.now() + datetime.timedelta(days=1) 525 expire_at = timezone.now() + datetime.timedelta(days=2) 526 post_data = { 527 'title': "I've been edited!", 528 'content': "Some content", 529 'slug': 'hello-world', 530 'action-publish': "Publish", 531 'go_live_at': submittable_timestamp(go_live_at), 532 'expire_at': submittable_timestamp(expire_at), 533 } 534 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 535 536 # Should be redirected to explorer page 537 self.assertEqual(response.status_code, 302) 538 539 child_page_new = SimplePage.objects.get(id=self.child_page.id) 540 541 # The page should still be live 542 self.assertTrue(child_page_new.live) 543 544 self.assertEqual(child_page_new.status_string, "live + scheduled") 545 546 # Instead a revision with approved_go_live_at should now exist 547 self.assertTrue( 548 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 549 ) 550 551 # The page SHOULD have the "has_unpublished_changes" flag set, 552 # because the changes are not visible as a live page yet 553 self.assertTrue( 554 child_page_new.has_unpublished_changes, 555 "A page scheduled for future publishing should have has_unpublished_changes=True" 556 ) 557 558 self.assertNotEqual( 559 child_page_new.get_latest_revision(), live_revision, 560 "A page scheduled for future publishing should have a new revision, that is not the live revision" 561 ) 562 563 self.assertEqual( 564 child_page_new.title, original_title, 565 "A live page with scheduled revisions should still have original content" 566 ) 567 568 def test_edit_post_publish_now_an_already_scheduled_published_page(self): 569 # Unpublish the page 570 self.child_page.live = True 571 self.child_page.save() 572 573 original_title = self.child_page.title 574 # First let's publish a page with a go_live_at in the future 575 go_live_at = timezone.now() + datetime.timedelta(days=1) 576 expire_at = timezone.now() + datetime.timedelta(days=2) 577 post_data = { 578 'title': "I've been edited!", 579 'content': "Some content", 580 'slug': 'hello-world', 581 'action-publish': "Publish", 582 'go_live_at': submittable_timestamp(go_live_at), 583 'expire_at': submittable_timestamp(expire_at), 584 } 585 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 586 587 # Should be redirected to edit page 588 self.assertEqual(response.status_code, 302) 589 590 child_page_new = SimplePage.objects.get(id=self.child_page.id) 591 592 # The page should still be live 593 self.assertTrue(child_page_new.live) 594 595 # Instead a revision with approved_go_live_at should now exist 596 self.assertTrue( 597 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 598 ) 599 600 self.assertEqual( 601 child_page_new.title, original_title, 602 "A live page with scheduled revisions should still have original content" 603 ) 604 605 # Now, let's edit it and publish it right now 606 go_live_at = timezone.now() 607 post_data = { 608 'title': "I've been edited!", 609 'content': "Some content", 610 'slug': 'hello-world', 611 'action-publish': "Publish", 612 'go_live_at': "", 613 } 614 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 615 616 # Should be redirected to edit page 617 self.assertEqual(response.status_code, 302) 618 619 child_page_new = SimplePage.objects.get(id=self.child_page.id) 620 621 # The page should be live now 622 self.assertTrue(child_page_new.live) 623 624 # And a revision with approved_go_live_at should not exist 625 self.assertFalse( 626 PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() 627 ) 628 629 self.assertEqual( 630 child_page_new.title, post_data['title'], 631 "A published page should have the new title" 632 ) 633 634 def test_page_edit_post_submit(self): 635 # Create a moderator user for testing email 636 self.create_superuser('moderator', 'moderator@email.com', 'password') 637 638 # Tests submitting from edit page 639 post_data = { 640 'title': "I've been edited!", 641 'content': "Some content", 642 'slug': 'hello-world', 643 'action-submit': "Submit", 644 } 645 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 646 647 # Should be redirected to explorer 648 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) 649 650 # The page should have "has_unpublished_changes" flag set 651 child_page_new = SimplePage.objects.get(id=self.child_page.id) 652 self.assertTrue(child_page_new.has_unpublished_changes) 653 654 # The latest revision for the page should now be in moderation 655 self.assertEqual(child_page_new.current_workflow_state.status, child_page_new.current_workflow_state.STATUS_IN_PROGRESS) 656 657 def test_page_edit_post_existing_slug(self): 658 # This tests the existing slug checking on page edit 659 660 # Create a page 661 self.child_page = SimplePage(title="Hello world 2", slug="hello-world2", content="hello") 662 self.root_page.add_child(instance=self.child_page) 663 664 # Attempt to change the slug to one thats already in use 665 post_data = { 666 'title': "Hello world 2", 667 'slug': 'hello-world', 668 'action-submit': "Submit", 669 } 670 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 671 672 # Should not be redirected (as the save should fail) 673 self.assertEqual(response.status_code, 200) 674 675 # Check that a form error was raised 676 self.assertFormError(response, 'form', 'slug', "This slug is already in use") 677 678 def test_preview_on_edit(self): 679 post_data = { 680 'title': "I've been edited!", 681 'content': "Some content", 682 'slug': 'hello-world', 683 'action-submit': "Submit", 684 } 685 preview_url = reverse('wagtailadmin_pages:preview_on_edit', 686 args=(self.child_page.id,)) 687 response = self.client.post(preview_url, post_data) 688 689 # Check the JSON response 690 self.assertEqual(response.status_code, 200) 691 self.assertJSONEqual(response.content.decode(), {'is_valid': True}) 692 693 response = self.client.get(preview_url) 694 695 # Check the HTML response 696 self.assertEqual(response.status_code, 200) 697 self.assertTemplateUsed(response, 'tests/simple_page.html') 698 self.assertContains(response, "I've been edited!", html=True) 699 700 def test_preview_on_edit_no_session_key(self): 701 preview_url = reverse('wagtailadmin_pages:preview_on_edit', 702 args=(self.child_page.id,)) 703 704 # get() without corresponding post(), key not set. 705 response = self.client.get(preview_url) 706 707 # Check the HTML response 708 self.assertEqual(response.status_code, 200) 709 710 # We should have an error page because we are unable to 711 # preview; the page key was not in the session. 712 self.assertContains( 713 response, 714 "<title>Wagtail - Preview error</title>", 715 html=True 716 ) 717 self.assertContains( 718 response, 719 "<h1>Preview error</h1>", 720 html=True 721 ) 722 723 @override_settings(CACHES={ 724 'default': { 725 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 726 }}) 727 @modify_settings(MIDDLEWARE={ 728 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 729 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', 730 }) 731 def test_preview_does_not_cache(self): 732 ''' 733 Tests solution to issue #5975 734 ''' 735 post_data = { 736 'title': "I've been edited one time!", 737 'content': "Some content", 738 'slug': 'hello-world', 739 'action-submit': "Submit", 740 } 741 preview_url = reverse('wagtailadmin_pages:preview_on_edit', 742 args=(self.child_page.id,)) 743 self.client.post(preview_url, post_data) 744 response = self.client.get(preview_url) 745 self.assertContains(response, "I've been edited one time!", html=True) 746 747 post_data['title'] = "I've been edited two times!" 748 self.client.post(preview_url, post_data) 749 response = self.client.get(preview_url) 750 self.assertContains(response, "I've been edited two times!", html=True) 751 752 @modify_settings(ALLOWED_HOSTS={'append': 'childpage.example.com'}) 753 def test_preview_uses_correct_site(self): 754 # create a Site record for the child page 755 Site.objects.create(hostname='childpage.example.com', root_page=self.child_page) 756 757 post_data = { 758 'title': "I've been edited!", 759 'content': "Some content", 760 'slug': 'hello-world', 761 'action-submit': "Submit", 762 } 763 preview_url = reverse('wagtailadmin_pages:preview_on_edit', 764 args=(self.child_page.id,)) 765 response = self.client.post(preview_url, post_data) 766 767 # Check the JSON response 768 self.assertEqual(response.status_code, 200) 769 self.assertJSONEqual(response.content.decode(), {'is_valid': True}) 770 771 response = self.client.get(preview_url) 772 773 # Check that the correct site object has been selected by the site middleware 774 self.assertEqual(response.status_code, 200) 775 self.assertTemplateUsed(response, 'tests/simple_page.html') 776 self.assertEqual(Site.find_for_request(response.context['request']).hostname, 'childpage.example.com') 777 778 def test_editor_picks_up_direct_model_edits(self): 779 # If a page has no draft edits, the editor should show the version from the live database 780 # record rather than the latest revision record. This ensures that the edit interface 781 # reflects any changes made directly on the model. 782 self.child_page.title = "This title only exists on the live database record" 783 self.child_page.save() 784 785 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 786 self.assertEqual(response.status_code, 200) 787 self.assertContains(response, "This title only exists on the live database record") 788 789 def test_editor_does_not_pick_up_direct_model_edits_when_draft_edits_exist(self): 790 # If a page has draft edits, we should always show those in the editor, not the live 791 # database record 792 self.child_page.content = "Some content with a draft edit" 793 self.child_page.save_revision() 794 795 # make an independent change to the live database record 796 self.child_page = SimplePage.objects.get(id=self.child_page.id) 797 self.child_page.title = "This title only exists on the live database record" 798 self.child_page.save() 799 800 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 801 self.assertEqual(response.status_code, 200) 802 self.assertNotContains(response, "This title only exists on the live database record") 803 self.assertContains(response, "Some content with a draft edit") 804 805 def test_editor_page_shows_live_url_in_status_when_draft_edits_exist(self): 806 # If a page has draft edits (ie. page has unpublished changes) 807 # that affect the URL (eg. slug) we should still ensure the 808 # status button at the top of the page links to the live URL 809 810 self.child_page.content = "Some content with a draft edit" 811 self.child_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' 812 self.child_page.save_revision() 813 814 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 815 816 link_to_live = '<a href="/hello-world/" target="_blank" rel="noopener noreferrer" class="button button-nostroke button--live" title="Visit the live page">\n' \ 817 '<svg class="icon icon-link-external initial" aria-hidden="true" focusable="false"><use href="#icon-link-external"></use></svg>\n\n ' \ 818 'Live\n <span class="privacy-indicator-tag u-hidden" aria-hidden="true" title="This page is live but only available to certain users">(restricted)</span>' 819 input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" id="id_slug" maxlength="255" required />' 820 input_field_for_live_slug = '<input type="text" name="slug" value="hello-world" id="id_slug" maxlength="255" required />' 821 822 # Status Link should be the live page (not revision) 823 self.assertContains(response, link_to_live, html=True) 824 self.assertNotContains(response, 'href="/revised-slug-in-draft-only/"', html=True) 825 826 # Editing input for slug should be the draft revision 827 self.assertContains(response, input_field_for_draft_slug, html=True) 828 self.assertNotContains(response, input_field_for_live_slug, html=True) 829 830 def test_editor_page_shows_custom_live_url_in_status_when_draft_edits_exist(self): 831 # When showing a live URL in the status button that differs from the draft one, 832 # ensure that we pick up any custom URL logic defined on the specific page model 833 834 self.single_event_page.location = "The other side of Mars" 835 self.single_event_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' 836 self.single_event_page.save_revision() 837 838 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.single_event_page.id, ))) 839 840 link_to_live = '<a href="/mars-landing/pointless-suffix/" target="_blank" rel="noopener noreferrer" class="button button-nostroke button--live" title="Visit the live page">\n' \ 841 '<svg class="icon icon-link-external initial" aria-hidden="true" focusable="false"><use href="#icon-link-external"></use></svg>\n\n ' \ 842 'Live\n <span class="privacy-indicator-tag u-hidden" aria-hidden="true" title="This page is live but only available to certain users">(restricted)</span>' 843 input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" id="id_slug" maxlength="255" required />' 844 input_field_for_live_slug = '<input type="text" name="slug" value="mars-landing" id="id_slug" maxlength="255" required />' 845 846 # Status Link should be the live page (not revision) 847 self.assertContains(response, link_to_live, html=True) 848 self.assertNotContains(response, 'href="/revised-slug-in-draft-only/pointless-suffix/"', html=True) 849 850 # Editing input for slug should be the draft revision 851 self.assertContains(response, input_field_for_draft_slug, html=True) 852 self.assertNotContains(response, input_field_for_live_slug, html=True) 853 854 def test_before_edit_page_hook(self): 855 def hook_func(request, page): 856 self.assertIsInstance(request, HttpRequest) 857 self.assertEqual(page.id, self.child_page.id) 858 859 return HttpResponse("Overridden!") 860 861 with self.register_hook('before_edit_page', hook_func): 862 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 863 864 self.assertEqual(response.status_code, 200) 865 self.assertEqual(response.content, b"Overridden!") 866 867 def test_before_edit_page_hook_post(self): 868 def hook_func(request, page): 869 self.assertIsInstance(request, HttpRequest) 870 self.assertEqual(page.id, self.child_page.id) 871 872 return HttpResponse("Overridden!") 873 874 with self.register_hook('before_edit_page', hook_func): 875 post_data = { 876 'title': "I've been edited!", 877 'content': "Some content", 878 'slug': 'hello-world-new', 879 'action-publish': "Publish", 880 } 881 response = self.client.post( 882 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data 883 ) 884 885 self.assertEqual(response.status_code, 200) 886 self.assertEqual(response.content, b"Overridden!") 887 888 # page should not be edited 889 self.assertEqual(Page.objects.get(id=self.child_page.id).title, "Hello world!") 890 891 def test_after_edit_page_hook(self): 892 def hook_func(request, page): 893 self.assertIsInstance(request, HttpRequest) 894 self.assertEqual(page.id, self.child_page.id) 895 896 return HttpResponse("Overridden!") 897 898 with self.register_hook('after_edit_page', hook_func): 899 post_data = { 900 'title': "I've been edited!", 901 'content': "Some content", 902 'slug': 'hello-world-new', 903 'action-publish': "Publish", 904 } 905 response = self.client.post( 906 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data 907 ) 908 909 self.assertEqual(response.status_code, 200) 910 self.assertEqual(response.content, b"Overridden!") 911 912 # page should be edited 913 self.assertEqual(Page.objects.get(id=self.child_page.id).title, "I've been edited!") 914 915 def test_after_publish_page(self): 916 def hook_func(request, page): 917 self.assertIsInstance(request, HttpRequest) 918 self.assertEqual(page.id, self.child_page.id) 919 920 return HttpResponse("Overridden!") 921 922 with self.register_hook("after_publish_page", hook_func): 923 post_data = { 924 'title': "I've been edited!", 925 'content': "Some content", 926 'slug': 'hello-world-new', 927 'action-publish': "Publish", 928 } 929 response = self.client.post( 930 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data 931 ) 932 933 self.assertEqual(response.status_code, 200) 934 self.assertEqual(response.content, b"Overridden!") 935 self.child_page.refresh_from_db() 936 self.assertEqual(self.child_page.status_string, _("live")) 937 938 def test_before_publish_page(self): 939 def hook_func(request, page): 940 self.assertIsInstance(request, HttpRequest) 941 self.assertEqual(page.id, self.child_page.id) 942 943 return HttpResponse("Overridden!") 944 945 with self.register_hook("before_publish_page", hook_func): 946 post_data = { 947 'title': "I've been edited!", 948 'content': "Some content", 949 'slug': 'hello-world-new', 950 'action-publish': "Publish", 951 } 952 response = self.client.post( 953 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data 954 ) 955 956 self.assertEqual(response.status_code, 200) 957 self.assertEqual(response.content, b"Overridden!") 958 self.child_page.refresh_from_db() 959 self.assertEqual(self.child_page.status_string, _("live + draft")) 960 961 def test_override_default_action_menu_item(self): 962 def hook_func(menu_items, request, context): 963 for (index, item) in enumerate(menu_items): 964 if item.name == 'action-publish': 965 # move to top of list 966 menu_items.pop(index) 967 menu_items.insert(0, item) 968 break 969 970 with self.register_hook('construct_page_action_menu', hook_func): 971 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.single_event_page.id, ))) 972 973 publish_button = ''' 974 <button type="submit" name="action-publish" value="action-publish" class="button button-longrunning " data-clicked-text="Publishing…"> 975 <svg class="icon icon-upload button-longrunning__icon" aria-hidden="true" focusable="false"><use href="#icon-upload"></use></svg> 976 977 <svg class="icon icon-spinner icon" aria-hidden="true" focusable="false"><use href="#icon-spinner"></use></svg><em>Publish</em> 978 </button> 979 ''' 980 save_button = ''' 981 <button type="submit" class="button action-save button-longrunning " data-clicked-text="Saving…" > 982 <svg class="icon icon-draft button-longrunning__icon" aria-hidden="true" focusable="false"><use href="#icon-draft"></use></svg> 983 984 <svg class="icon icon-spinner icon" aria-hidden="true" focusable="false"><use href="#icon-spinner"></use></svg> 985 <em>Save draft</em> 986 </button> 987 ''' 988 989 # save button should be in a <li> 990 self.assertContains(response, "<li>%s</li>" % save_button, html=True) 991 992 # publish button should be present, but not in a <li> 993 self.assertContains(response, publish_button, html=True) 994 self.assertNotContains(response, "<li>%s</li>" % publish_button, html=True) 995 996 def test_edit_alias_page(self): 997 alias_page = self.event_page.create_alias(update_slug='new-event-page') 998 response = self.client.get(reverse('wagtailadmin_pages:edit', args=[alias_page.id])) 999 1000 self.assertEqual(response.status_code, 200) 1001 self.assertEqual(response['Content-Type'], "text/html; charset=utf-8") 1002 1003 # Should still have status in the header 1004 self.assertContains(response, '<li class="header-meta--status">Published</li>', html=True) 1005 1006 # Check the edit_alias.html template was used instead 1007 self.assertTemplateUsed(response, 'wagtailadmin/pages/edit_alias.html') 1008 original_page_edit_url = reverse('wagtailadmin_pages:edit', args=[self.event_page.id]) 1009 self.assertContains(response, f'<a class="button button-secondary" href="{original_page_edit_url}">Edit original page</a>', html=True) 1010 1011 def test_post_edit_alias_page(self): 1012 alias_page = self.child_page.create_alias(update_slug='new-child-page') 1013 1014 # Tests simple editing 1015 post_data = { 1016 'title': "I've been edited!", 1017 'content': "Some content", 1018 'slug': 'hello-world', 1019 } 1020 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[alias_page.id]), post_data) 1021 1022 self.assertEqual(response.status_code, 405) 1023 1024 def test_edit_after_change_language_code(self): 1025 """ 1026 Verify that changing LANGUAGE_CODE with no corresponding database change does not break editing 1027 """ 1028 # Add a draft revision 1029 self.child_page.title = "Hello world updated" 1030 self.child_page.save_revision() 1031 1032 # Hack the Locale model to simulate a page tree that was created with LANGUAGE_CODE = 'de' 1033 # (which is not a valid content language under the current configuration) 1034 Locale.objects.update(language_code='de') 1035 1036 # Tests that the edit page loads 1037 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 1038 self.assertEqual(response.status_code, 200) 1039 1040 # Tests simple editing 1041 post_data = { 1042 'title': "I've been edited!", 1043 'content': "Some content", 1044 'slug': 'hello-world', 1045 } 1046 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 1047 1048 # Should be redirected to edit page 1049 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 1050 1051 def test_edit_after_change_language_code_without_revisions(self): 1052 """ 1053 Verify that changing LANGUAGE_CODE with no corresponding database change does not break editing 1054 """ 1055 # Hack the Locale model to simulate a page tree that was created with LANGUAGE_CODE = 'de' 1056 # (which is not a valid content language under the current configuration) 1057 Locale.objects.update(language_code='de') 1058 1059 PageRevision.objects.filter(page_id=self.child_page.id).delete() 1060 1061 # Tests that the edit page loads 1062 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 1063 self.assertEqual(response.status_code, 200) 1064 1065 # Tests simple editing 1066 post_data = { 1067 'title': "I've been edited!", 1068 'content': "Some content", 1069 'slug': 'hello-world', 1070 } 1071 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) 1072 1073 # Should be redirected to edit page 1074 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) 1075 1076 1077class TestPageEditReordering(TestCase, WagtailTestUtils): 1078 def setUp(self): 1079 # Find root page 1080 self.root_page = Page.objects.get(id=2) 1081 1082 # Add event page 1083 self.event_page = EventPage( 1084 title="Event page", slug="event-page", 1085 location='the moon', audience='public', 1086 cost='free', date_from='2001-01-01', 1087 ) 1088 self.event_page.carousel_items = [ 1089 EventPageCarouselItem(caption='1234567', sort_order=1), 1090 EventPageCarouselItem(caption='7654321', sort_order=2), 1091 EventPageCarouselItem(caption='abcdefg', sort_order=3), 1092 ] 1093 self.root_page.add_child(instance=self.event_page) 1094 1095 # Login 1096 self.user = self.login() 1097 1098 def check_order(self, response, expected_order): 1099 inline_panel = response.context['edit_handler'].children[0].children[9] 1100 order = [child.form.instance.caption for child in inline_panel.children] 1101 self.assertEqual(order, expected_order) 1102 1103 def test_order(self): 1104 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 1105 1106 self.assertEqual(response.status_code, 200) 1107 self.check_order(response, ['1234567', '7654321', 'abcdefg']) 1108 1109 def test_reorder(self): 1110 post_data = { 1111 'title': "Event page", 1112 'slug': 'event-page', 1113 1114 'date_from': '01/01/2014', 1115 'cost': '$10', 1116 'audience': 'public', 1117 'location': 'somewhere', 1118 1119 'related_links-INITIAL_FORMS': 0, 1120 'related_links-MAX_NUM_FORMS': 1000, 1121 'related_links-TOTAL_FORMS': 0, 1122 1123 'speakers-INITIAL_FORMS': 0, 1124 'speakers-MAX_NUM_FORMS': 1000, 1125 'speakers-TOTAL_FORMS': 0, 1126 1127 'head_counts-INITIAL_FORMS': 0, 1128 'head_counts-MAX_NUM_FORMS': 1000, 1129 'head_counts-TOTAL_FORMS': 0, 1130 1131 'carousel_items-INITIAL_FORMS': 3, 1132 'carousel_items-MAX_NUM_FORMS': 1000, 1133 'carousel_items-TOTAL_FORMS': 3, 1134 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, 1135 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, 1136 'carousel_items-0-ORDER': 2, 1137 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, 1138 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, 1139 'carousel_items-1-ORDER': 3, 1140 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, 1141 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, 1142 'carousel_items-2-ORDER': 1, 1143 } 1144 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) 1145 1146 # Should be redirected back to same page 1147 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 1148 1149 # Check order 1150 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) 1151 1152 self.assertEqual(response.status_code, 200) 1153 self.check_order(response, ['abcdefg', '1234567', '7654321']) 1154 1155 def test_reorder_with_validation_error(self): 1156 post_data = { 1157 'title': "", # Validation error 1158 'slug': 'event-page', 1159 1160 'date_from': '01/01/2014', 1161 'cost': '$10', 1162 'audience': 'public', 1163 'location': 'somewhere', 1164 1165 'related_links-INITIAL_FORMS': 0, 1166 'related_links-MAX_NUM_FORMS': 1000, 1167 'related_links-TOTAL_FORMS': 0, 1168 1169 'speakers-INITIAL_FORMS': 0, 1170 'speakers-MAX_NUM_FORMS': 1000, 1171 'speakers-TOTAL_FORMS': 0, 1172 1173 'head_counts-INITIAL_FORMS': 0, 1174 'head_counts-MAX_NUM_FORMS': 1000, 1175 'head_counts-TOTAL_FORMS': 0, 1176 1177 'carousel_items-INITIAL_FORMS': 3, 1178 'carousel_items-MAX_NUM_FORMS': 1000, 1179 'carousel_items-TOTAL_FORMS': 3, 1180 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, 1181 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, 1182 'carousel_items-0-ORDER': 2, 1183 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, 1184 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, 1185 'carousel_items-1-ORDER': 3, 1186 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, 1187 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, 1188 'carousel_items-2-ORDER': 1, 1189 } 1190 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) 1191 1192 self.assertEqual(response.status_code, 200) 1193 self.check_order(response, ['abcdefg', '1234567', '7654321']) 1194 1195 1196class TestIssue197(TestCase, WagtailTestUtils): 1197 def test_issue_197(self): 1198 # Find root page 1199 self.root_page = Page.objects.get(id=2) 1200 1201 # Create a tagged page with no tags 1202 self.tagged_page = self.root_page.add_child(instance=TaggedPage( 1203 title="Tagged page", 1204 slug='tagged-page', 1205 live=False, 1206 )) 1207 1208 # Login 1209 self.user = self.login() 1210 1211 # Add some tags and publish using edit view 1212 post_data = { 1213 'title': "Tagged page", 1214 'slug': 'tagged-page', 1215 'tags': "hello, world", 1216 'action-publish': "Publish", 1217 } 1218 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.tagged_page.id, )), post_data) 1219 1220 # Should be redirected to explorer 1221 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) 1222 1223 # Check that both tags are in the pages tag set 1224 page = TaggedPage.objects.get(id=self.tagged_page.id) 1225 self.assertIn('hello', page.tags.slugs()) 1226 self.assertIn('world', page.tags.slugs()) 1227 1228 1229class TestChildRelationsOnSuperclass(TestCase, WagtailTestUtils): 1230 # In our test models we define AdvertPlacement as a child relation on the Page model. 1231 # Here we check that this behaves correctly when exposed on the edit form of a Page 1232 # subclass (StandardIndex here). 1233 fixtures = ['test.json'] 1234 1235 def setUp(self): 1236 # Find root page 1237 self.root_page = Page.objects.get(id=2) 1238 self.test_advert = Advert.objects.get(id=1) 1239 1240 # Add child page 1241 self.index_page = StandardIndex( 1242 title="My lovely index", 1243 slug="my-lovely-index", 1244 advert_placements=[AdvertPlacement(advert=self.test_advert)] 1245 ) 1246 self.root_page.add_child(instance=self.index_page) 1247 1248 # Login 1249 self.login() 1250 1251 def test_get_create_form(self): 1252 response = self.client.get( 1253 reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)) 1254 ) 1255 self.assertEqual(response.status_code, 200) 1256 # Response should include an advert_placements formset labelled Adverts 1257 self.assertContains(response, "Adverts") 1258 self.assertContains(response, "id_advert_placements-TOTAL_FORMS") 1259 1260 def test_post_create_form(self): 1261 post_data = { 1262 'title': "New index!", 1263 'slug': 'new-index', 1264 'advert_placements-TOTAL_FORMS': '1', 1265 'advert_placements-INITIAL_FORMS': '0', 1266 'advert_placements-MAX_NUM_FORMS': '1000', 1267 'advert_placements-0-advert': '1', 1268 'advert_placements-0-colour': 'yellow', 1269 'advert_placements-0-id': '', 1270 } 1271 response = self.client.post( 1272 reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data 1273 ) 1274 1275 # Find the page and check it 1276 page = Page.objects.get(path__startswith=self.root_page.path, slug='new-index').specific 1277 1278 # Should be redirected to edit page 1279 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(page.id, ))) 1280 1281 self.assertEqual(page.advert_placements.count(), 1) 1282 self.assertEqual(page.advert_placements.first().advert.text, 'test_advert') 1283 1284 def test_post_create_form_with_validation_error_in_formset(self): 1285 post_data = { 1286 'title': "New index!", 1287 'slug': 'new-index', 1288 'advert_placements-TOTAL_FORMS': '1', 1289 'advert_placements-INITIAL_FORMS': '0', 1290 'advert_placements-MAX_NUM_FORMS': '1000', 1291 'advert_placements-0-advert': '1', 1292 'advert_placements-0-colour': '', # should fail as colour is a required field 1293 'advert_placements-0-id': '', 1294 } 1295 response = self.client.post( 1296 reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data 1297 ) 1298 1299 # Should remain on the edit page with a validation error 1300 self.assertEqual(response.status_code, 200) 1301 self.assertContains(response, "This field is required.") 1302 # form should be marked as having unsaved changes 1303 self.assertContains(response, "alwaysDirty: true") 1304 1305 def test_get_edit_form(self): 1306 response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, ))) 1307 self.assertEqual(response.status_code, 200) 1308 1309 # Response should include an advert_placements formset labelled Adverts 1310 self.assertContains(response, "Adverts") 1311 self.assertContains(response, "id_advert_placements-TOTAL_FORMS") 1312 # the formset should be populated with an existing form 1313 self.assertContains(response, "id_advert_placements-0-advert") 1314 self.assertContains( 1315 response, '<option value="1" selected="selected">test_advert</option>', html=True 1316 ) 1317 1318 def test_post_edit_form(self): 1319 post_data = { 1320 'title': "My lovely index", 1321 'slug': 'my-lovely-index', 1322 'advert_placements-TOTAL_FORMS': '2', 1323 'advert_placements-INITIAL_FORMS': '1', 1324 'advert_placements-MAX_NUM_FORMS': '1000', 1325 'advert_placements-0-advert': '1', 1326 'advert_placements-0-colour': 'yellow', 1327 'advert_placements-0-id': self.index_page.advert_placements.first().id, 1328 'advert_placements-1-advert': '1', 1329 'advert_placements-1-colour': 'purple', 1330 'advert_placements-1-id': '', 1331 'action-publish': "Publish", 1332 } 1333 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) 1334 1335 # Should be redirected to explorer 1336 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) 1337 1338 # Find the page and check it 1339 page = Page.objects.get(id=self.index_page.id).specific 1340 self.assertEqual(page.advert_placements.count(), 2) 1341 self.assertEqual(page.advert_placements.all()[0].advert.text, 'test_advert') 1342 self.assertEqual(page.advert_placements.all()[1].advert.text, 'test_advert') 1343 1344 def test_post_edit_form_with_validation_error_in_formset(self): 1345 post_data = { 1346 'title': "My lovely index", 1347 'slug': 'my-lovely-index', 1348 'advert_placements-TOTAL_FORMS': '1', 1349 'advert_placements-INITIAL_FORMS': '1', 1350 'advert_placements-MAX_NUM_FORMS': '1000', 1351 'advert_placements-0-advert': '1', 1352 'advert_placements-0-colour': '', 1353 'advert_placements-0-id': self.index_page.advert_placements.first().id, 1354 'action-publish': "Publish", 1355 } 1356 response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) 1357 1358 # Should remain on the edit page with a validation error 1359 self.assertEqual(response.status_code, 200) 1360 self.assertContains(response, "This field is required.") 1361 # form should be marked as having unsaved changes 1362 self.assertContains(response, "alwaysDirty: true") 1363 1364 1365class TestIssue2492(TestCase, WagtailTestUtils): 1366 """ 1367 The publication submission message generation was performed using 1368 the Page class, as opposed to the specific_class for that Page. 1369 This test ensures that the specific_class url method is called 1370 when the 'view live' message button is created. 1371 """ 1372 1373 def setUp(self): 1374 self.root_page = Page.objects.get(id=2) 1375 child_page = SingleEventPage( 1376 title="Test Event", slug="test-event", location="test location", 1377 cost="10", date_from=datetime.datetime.now(), 1378 audience=EVENT_AUDIENCE_CHOICES[0][0]) 1379 self.root_page.add_child(instance=child_page) 1380 child_page.save_revision().publish() 1381 self.child_page = SingleEventPage.objects.get(id=child_page.id) 1382 self.user = self.login() 1383 1384 def test_page_edit_post_publish_url(self): 1385 post_data = { 1386 'action-publish': "Publish", 1387 'title': self.child_page.title, 1388 'date_from': self.child_page.date_from, 1389 'slug': self.child_page.slug, 1390 'audience': self.child_page.audience, 1391 'location': self.child_page.location, 1392 'cost': self.child_page.cost, 1393 'carousel_items-TOTAL_FORMS': 0, 1394 'carousel_items-INITIAL_FORMS': 0, 1395 'carousel_items-MIN_NUM_FORMS': 0, 1396 'carousel_items-MAX_NUM_FORMS': 0, 1397 'speakers-TOTAL_FORMS': 0, 1398 'speakers-INITIAL_FORMS': 0, 1399 'speakers-MIN_NUM_FORMS': 0, 1400 'speakers-MAX_NUM_FORMS': 0, 1401 'related_links-TOTAL_FORMS': 0, 1402 'related_links-INITIAL_FORMS': 0, 1403 'related_links-MIN_NUM_FORMS': 0, 1404 'related_links-MAX_NUM_FORMS': 0, 1405 'head_counts-TOTAL_FORMS': 0, 1406 'head_counts-INITIAL_FORMS': 0, 1407 'head_counts-MIN_NUM_FORMS': 0, 1408 'head_counts-MAX_NUM_FORMS': 0, 1409 } 1410 response = self.client.post( 1411 reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), 1412 post_data, follow=True) 1413 1414 # Grab a fresh copy's URL 1415 new_url = SingleEventPage.objects.get(id=self.child_page.id).url 1416 1417 # The "View Live" button should have the custom URL. 1418 for message in response.context['messages']: 1419 self.assertIn('"{}"'.format(new_url), message.message) 1420 break 1421 1422 1423class TestIssue3982(TestCase, WagtailTestUtils): 1424 """ 1425 Pages that are not associated with a site, and thus do not have a live URL, 1426 should not display a "View live" link in the flash message after being 1427 edited. 1428 """ 1429 1430 def setUp(self): 1431 super().setUp() 1432 self.login() 1433 1434 def _create_page(self, parent): 1435 response = self.client.post( 1436 reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), 1437 {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, 1438 follow=True) 1439 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) 1440 page = SimplePage.objects.get() 1441 self.assertTrue(page.live) 1442 return response, page 1443 1444 def test_create_accessible(self): 1445 """ 1446 Create a page under the site root, check the flash message has a valid 1447 "View live" button. 1448 """ 1449 response, page = self._create_page(Page.objects.get(pk=2)) 1450 self.assertIsNotNone(page.url) 1451 self.assertTrue(any( 1452 'View live' in message.message and page.url in message.message 1453 for message in response.context['messages'])) 1454 1455 def test_create_inaccessible(self): 1456 """ 1457 Create a page outside of the site root, check the flash message does 1458 not have a "View live" button. 1459 """ 1460 response, page = self._create_page(Page.objects.get(pk=1)) 1461 self.assertIsNone(page.url) 1462 self.assertFalse(any( 1463 'View live' in message.message 1464 for message in response.context['messages'])) 1465 1466 def _edit_page(self, parent): 1467 page = parent.add_child(instance=SimplePage(title='Hello, world!', content='Some content')) 1468 response = self.client.post( 1469 reverse('wagtailadmin_pages:edit', args=(page.pk,)), 1470 {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, 1471 follow=True) 1472 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) 1473 page = SimplePage.objects.get(pk=page.pk) 1474 self.assertTrue(page.live) 1475 return response, page 1476 1477 def test_edit_accessible(self): 1478 """ 1479 Edit a page under the site root, check the flash message has a valid 1480 "View live" button. 1481 """ 1482 response, page = self._edit_page(Page.objects.get(pk=2)) 1483 self.assertIsNotNone(page.url) 1484 self.assertTrue(any( 1485 'View live' in message.message and page.url in message.message 1486 for message in response.context['messages'])) 1487 1488 def test_edit_inaccessible(self): 1489 """ 1490 Edit a page outside of the site root, check the flash message does 1491 not have a "View live" button. 1492 """ 1493 response, page = self._edit_page(Page.objects.get(pk=1)) 1494 self.assertIsNone(page.url) 1495 self.assertFalse(any( 1496 'View live' in message.message 1497 for message in response.context['messages'])) 1498 1499 def _approve_page(self, parent): 1500 response = self.client.post( 1501 reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), 1502 {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world'}, 1503 follow=True) 1504 page = SimplePage.objects.get() 1505 self.assertFalse(page.live) 1506 revision = PageRevision.objects.get(page=page) 1507 revision.submitted_for_moderation = True 1508 revision.save() 1509 response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(revision.pk,)), follow=True) 1510 page = SimplePage.objects.get() 1511 self.assertTrue(page.live) 1512 self.assertRedirects(response, reverse('wagtailadmin_home')) 1513 return response, page 1514 1515 def test_approve_accessible(self): 1516 """ 1517 Edit a page under the site root, check the flash message has a valid 1518 "View live" button. 1519 """ 1520 response, page = self._approve_page(Page.objects.get(pk=2)) 1521 self.assertIsNotNone(page.url) 1522 self.assertTrue(any( 1523 'View live' in message.message and page.url in message.message 1524 for message in response.context['messages'])) 1525 1526 def test_approve_inaccessible(self): 1527 """ 1528 Edit a page outside of the site root, check the flash message does 1529 not have a "View live" button. 1530 """ 1531 response, page = self._approve_page(Page.objects.get(pk=1)) 1532 self.assertIsNone(page.url) 1533 self.assertFalse(any( 1534 'View live' in message.message 1535 for message in response.context['messages'])) 1536 1537 1538class TestParentalM2M(TestCase, WagtailTestUtils): 1539 fixtures = ['test.json'] 1540 1541 def setUp(self): 1542 self.events_index = Page.objects.get(url_path='/home/events/') 1543 self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') 1544 self.user = self.login() 1545 self.holiday_category = EventCategory.objects.create(name='Holiday') 1546 self.men_with_beards_category = EventCategory.objects.create(name='Men with beards') 1547 1548 def test_create_and_save(self): 1549 post_data = { 1550 'title': "Presidents' Day", 1551 'date_from': "2017-02-20", 1552 'slug': "presidents-day", 1553 'audience': "public", 1554 'location': "America", 1555 'cost': "$1", 1556 'carousel_items-TOTAL_FORMS': 0, 1557 'carousel_items-INITIAL_FORMS': 0, 1558 'carousel_items-MIN_NUM_FORMS': 0, 1559 'carousel_items-MAX_NUM_FORMS': 0, 1560 'speakers-TOTAL_FORMS': 0, 1561 'speakers-INITIAL_FORMS': 0, 1562 'speakers-MIN_NUM_FORMS': 0, 1563 'speakers-MAX_NUM_FORMS': 0, 1564 'related_links-TOTAL_FORMS': 0, 1565 'related_links-INITIAL_FORMS': 0, 1566 'related_links-MIN_NUM_FORMS': 0, 1567 'related_links-MAX_NUM_FORMS': 0, 1568 'head_counts-TOTAL_FORMS': 0, 1569 'head_counts-INITIAL_FORMS': 0, 1570 'head_counts-MIN_NUM_FORMS': 0, 1571 'head_counts-MAX_NUM_FORMS': 0, 1572 'categories': [self.holiday_category.id, self.men_with_beards_category.id] 1573 } 1574 response = self.client.post( 1575 reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), 1576 post_data 1577 ) 1578 created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') 1579 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(created_page.id, ))) 1580 created_revision = created_page.get_latest_revision_as_page() 1581 1582 self.assertIn(self.holiday_category, created_revision.categories.all()) 1583 self.assertIn(self.men_with_beards_category, created_revision.categories.all()) 1584 1585 def test_create_and_publish(self): 1586 post_data = { 1587 'action-publish': "Publish", 1588 'title': "Presidents' Day", 1589 'date_from': "2017-02-20", 1590 'slug': "presidents-day", 1591 'audience': "public", 1592 'location': "America", 1593 'cost': "$1", 1594 'carousel_items-TOTAL_FORMS': 0, 1595 'carousel_items-INITIAL_FORMS': 0, 1596 'carousel_items-MIN_NUM_FORMS': 0, 1597 'carousel_items-MAX_NUM_FORMS': 0, 1598 'speakers-TOTAL_FORMS': 0, 1599 'speakers-INITIAL_FORMS': 0, 1600 'speakers-MIN_NUM_FORMS': 0, 1601 'speakers-MAX_NUM_FORMS': 0, 1602 'related_links-TOTAL_FORMS': 0, 1603 'related_links-INITIAL_FORMS': 0, 1604 'related_links-MIN_NUM_FORMS': 0, 1605 'related_links-MAX_NUM_FORMS': 0, 1606 'head_counts-TOTAL_FORMS': 0, 1607 'head_counts-INITIAL_FORMS': 0, 1608 'head_counts-MIN_NUM_FORMS': 0, 1609 'head_counts-MAX_NUM_FORMS': 0, 1610 'categories': [self.holiday_category.id, self.men_with_beards_category.id] 1611 } 1612 response = self.client.post( 1613 reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), 1614 post_data 1615 ) 1616 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) 1617 1618 created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') 1619 self.assertIn(self.holiday_category, created_page.categories.all()) 1620 self.assertIn(self.men_with_beards_category, created_page.categories.all()) 1621 1622 def test_edit_and_save(self): 1623 post_data = { 1624 'title': "Christmas", 1625 'date_from': "2017-12-25", 1626 'slug': "christmas", 1627 'audience': "public", 1628 'location': "The North Pole", 1629 'cost': "Free", 1630 'carousel_items-TOTAL_FORMS': 0, 1631 'carousel_items-INITIAL_FORMS': 0, 1632 'carousel_items-MIN_NUM_FORMS': 0, 1633 'carousel_items-MAX_NUM_FORMS': 0, 1634 'speakers-TOTAL_FORMS': 0, 1635 'speakers-INITIAL_FORMS': 0, 1636 'speakers-MIN_NUM_FORMS': 0, 1637 'speakers-MAX_NUM_FORMS': 0, 1638 'related_links-TOTAL_FORMS': 0, 1639 'related_links-INITIAL_FORMS': 0, 1640 'related_links-MIN_NUM_FORMS': 0, 1641 'related_links-MAX_NUM_FORMS': 0, 1642 'head_counts-TOTAL_FORMS': 0, 1643 'head_counts-INITIAL_FORMS': 0, 1644 'head_counts-MIN_NUM_FORMS': 0, 1645 'head_counts-MAX_NUM_FORMS': 0, 1646 'categories': [self.holiday_category.id, self.men_with_beards_category.id] 1647 } 1648 response = self.client.post( 1649 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1650 post_data 1651 ) 1652 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, ))) 1653 updated_page = EventPage.objects.get(id=self.christmas_page.id) 1654 created_revision = updated_page.get_latest_revision_as_page() 1655 1656 self.assertIn(self.holiday_category, created_revision.categories.all()) 1657 self.assertIn(self.men_with_beards_category, created_revision.categories.all()) 1658 1659 # no change to live page record yet 1660 self.assertEqual(0, updated_page.categories.count()) 1661 1662 def test_edit_and_publish(self): 1663 post_data = { 1664 'action-publish': "Publish", 1665 'title': "Christmas", 1666 'date_from': "2017-12-25", 1667 'slug': "christmas", 1668 'audience': "public", 1669 'location': "The North Pole", 1670 'cost': "Free", 1671 'carousel_items-TOTAL_FORMS': 0, 1672 'carousel_items-INITIAL_FORMS': 0, 1673 'carousel_items-MIN_NUM_FORMS': 0, 1674 'carousel_items-MAX_NUM_FORMS': 0, 1675 'speakers-TOTAL_FORMS': 0, 1676 'speakers-INITIAL_FORMS': 0, 1677 'speakers-MIN_NUM_FORMS': 0, 1678 'speakers-MAX_NUM_FORMS': 0, 1679 'related_links-TOTAL_FORMS': 0, 1680 'related_links-INITIAL_FORMS': 0, 1681 'related_links-MIN_NUM_FORMS': 0, 1682 'related_links-MAX_NUM_FORMS': 0, 1683 'head_counts-TOTAL_FORMS': 0, 1684 'head_counts-INITIAL_FORMS': 0, 1685 'head_counts-MIN_NUM_FORMS': 0, 1686 'head_counts-MAX_NUM_FORMS': 0, 1687 'categories': [self.holiday_category.id, self.men_with_beards_category.id] 1688 } 1689 response = self.client.post( 1690 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1691 post_data 1692 ) 1693 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) 1694 updated_page = EventPage.objects.get(id=self.christmas_page.id) 1695 self.assertEqual(2, updated_page.categories.count()) 1696 self.assertIn(self.holiday_category, updated_page.categories.all()) 1697 self.assertIn(self.men_with_beards_category, updated_page.categories.all()) 1698 1699 1700class TestValidationErrorMessages(TestCase, WagtailTestUtils): 1701 fixtures = ['test.json'] 1702 1703 def setUp(self): 1704 self.events_index = Page.objects.get(url_path='/home/events/') 1705 self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') 1706 self.user = self.login() 1707 1708 def test_field_error(self): 1709 """Field errors should be shown against the relevant fields, not in the header message""" 1710 post_data = { 1711 'title': "", 1712 'date_from': "2017-12-25", 1713 'slug': "christmas", 1714 'audience': "public", 1715 'location': "The North Pole", 1716 'cost': "Free", 1717 'carousel_items-TOTAL_FORMS': 0, 1718 'carousel_items-INITIAL_FORMS': 0, 1719 'carousel_items-MIN_NUM_FORMS': 0, 1720 'carousel_items-MAX_NUM_FORMS': 0, 1721 'speakers-TOTAL_FORMS': 0, 1722 'speakers-INITIAL_FORMS': 0, 1723 'speakers-MIN_NUM_FORMS': 0, 1724 'speakers-MAX_NUM_FORMS': 0, 1725 'related_links-TOTAL_FORMS': 0, 1726 'related_links-INITIAL_FORMS': 0, 1727 'related_links-MIN_NUM_FORMS': 0, 1728 'related_links-MAX_NUM_FORMS': 0, 1729 'head_counts-TOTAL_FORMS': 0, 1730 'head_counts-INITIAL_FORMS': 0, 1731 'head_counts-MIN_NUM_FORMS': 0, 1732 'head_counts-MAX_NUM_FORMS': 0, 1733 } 1734 response = self.client.post( 1735 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1736 post_data 1737 ) 1738 self.assertEqual(response.status_code, 200) 1739 1740 self.assertContains(response, "The page could not be saved due to validation errors") 1741 # the error should only appear once: against the field, not in the header message 1742 self.assertContains(response, """<p class="error-message"><span>This field is required.</span></p>""", count=1, html=True) 1743 self.assertContains(response, "This field is required", count=1) 1744 1745 def test_non_field_error(self): 1746 """Non-field errors should be shown in the header message""" 1747 post_data = { 1748 'title': "Christmas", 1749 'date_from': "2017-12-25", 1750 'date_to': "2017-12-24", 1751 'slug': "christmas", 1752 'audience': "public", 1753 'location': "The North Pole", 1754 'cost': "Free", 1755 'carousel_items-TOTAL_FORMS': 0, 1756 'carousel_items-INITIAL_FORMS': 0, 1757 'carousel_items-MIN_NUM_FORMS': 0, 1758 'carousel_items-MAX_NUM_FORMS': 0, 1759 'speakers-TOTAL_FORMS': 0, 1760 'speakers-INITIAL_FORMS': 0, 1761 'speakers-MIN_NUM_FORMS': 0, 1762 'speakers-MAX_NUM_FORMS': 0, 1763 'related_links-TOTAL_FORMS': 0, 1764 'related_links-INITIAL_FORMS': 0, 1765 'related_links-MIN_NUM_FORMS': 0, 1766 'related_links-MAX_NUM_FORMS': 0, 1767 'head_counts-TOTAL_FORMS': 0, 1768 'head_counts-INITIAL_FORMS': 0, 1769 'head_counts-MIN_NUM_FORMS': 0, 1770 'head_counts-MAX_NUM_FORMS': 0, 1771 } 1772 response = self.client.post( 1773 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1774 post_data 1775 ) 1776 self.assertEqual(response.status_code, 200) 1777 1778 self.assertContains(response, "The page could not be saved due to validation errors") 1779 self.assertContains(response, "<li>The end date must be after the start date</li>", count=1) 1780 1781 def test_field_and_non_field_error(self): 1782 """ 1783 If both field and non-field errors exist, all errors should be shown in the header message 1784 with appropriate context to identify the field; and field errors should also be shown 1785 against the relevant fields. 1786 """ 1787 post_data = { 1788 'title': "", 1789 'date_from': "2017-12-25", 1790 'date_to': "2017-12-24", 1791 'slug': "christmas", 1792 'audience': "public", 1793 'location': "The North Pole", 1794 'cost': "Free", 1795 'carousel_items-TOTAL_FORMS': 0, 1796 'carousel_items-INITIAL_FORMS': 0, 1797 'carousel_items-MIN_NUM_FORMS': 0, 1798 'carousel_items-MAX_NUM_FORMS': 0, 1799 'speakers-TOTAL_FORMS': 0, 1800 'speakers-INITIAL_FORMS': 0, 1801 'speakers-MIN_NUM_FORMS': 0, 1802 'speakers-MAX_NUM_FORMS': 0, 1803 'related_links-TOTAL_FORMS': 0, 1804 'related_links-INITIAL_FORMS': 0, 1805 'related_links-MIN_NUM_FORMS': 0, 1806 'related_links-MAX_NUM_FORMS': 0, 1807 'head_counts-TOTAL_FORMS': 0, 1808 'head_counts-INITIAL_FORMS': 0, 1809 'head_counts-MIN_NUM_FORMS': 0, 1810 'head_counts-MAX_NUM_FORMS': 0, 1811 } 1812 response = self.client.post( 1813 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1814 post_data 1815 ) 1816 self.assertEqual(response.status_code, 200) 1817 1818 self.assertContains(response, "The page could not be saved due to validation errors") 1819 self.assertContains(response, "<li>The end date must be after the start date</li>", count=1) 1820 1821 # Error on title shown against the title field 1822 self.assertContains(response, """<p class="error-message"><span>This field is required.</span></p>""", count=1, html=True) 1823 # Error on title shown in the header message 1824 self.assertContains(response, "<li>Title: This field is required.</li>", count=1) 1825 1826 1827class TestNestedInlinePanel(TestCase, WagtailTestUtils): 1828 fixtures = ['test.json'] 1829 1830 def setUp(self): 1831 self.events_index = Page.objects.get(url_path='/home/events/') 1832 self.christmas_page = EventPage.objects.get(url_path='/home/events/christmas/') 1833 self.speaker = self.christmas_page.speakers.first() 1834 self.speaker.awards.create( 1835 name="Beard Of The Year", date_awarded=datetime.date(1997, 12, 25) 1836 ) 1837 self.speaker.save() 1838 self.user = self.login() 1839 1840 def test_get_edit_form(self): 1841 response = self.client.get( 1842 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )) 1843 ) 1844 self.assertEqual(response.status_code, 200) 1845 self.assertContains( 1846 response, 1847 """<input type="text" name="speakers-0-awards-0-name" value="Beard Of The Year" maxlength="255" id="id_speakers-0-awards-0-name">""", 1848 count=1, html=True 1849 ) 1850 1851 # there should be no "extra" forms, as the nested formset should respect the extra_form_count=0 set on WagtailAdminModelForm 1852 self.assertContains( 1853 response, 1854 """<input type="hidden" name="speakers-0-awards-TOTAL_FORMS" value="1" id="id_speakers-0-awards-TOTAL_FORMS">""", 1855 count=1, html=True 1856 ) 1857 self.assertContains( 1858 response, 1859 """<input type="text" name="speakers-0-awards-1-name" value="" maxlength="255" id="id_speakers-0-awards-1-name">""", 1860 count=0, html=True 1861 ) 1862 1863 # date field should use AdminDatePicker 1864 self.assertContains( 1865 response, 1866 """<input type="text" name="speakers-0-awards-0-date_awarded" value="1997-12-25" autocomplete="off" id="id_speakers-0-awards-0-date_awarded">""", 1867 count=1, html=True 1868 ) 1869 1870 def test_post_edit(self): 1871 post_data = nested_form_data({ 1872 'title': "Christmas", 1873 'date_from': "2017-12-25", 1874 'date_to': "2017-12-25", 1875 'slug': "christmas", 1876 'audience': "public", 1877 'location': "The North Pole", 1878 'cost': "Free", 1879 'carousel_items': inline_formset([]), 1880 'speakers': inline_formset([ 1881 { 1882 'id': self.speaker.id, 1883 'first_name': "Jeff", 1884 'last_name': "Christmas", 1885 'awards': inline_formset([ 1886 { 1887 'id': self.speaker.awards.first().id, 1888 'name': "Beard Of The Century", 1889 'date_awarded': "1997-12-25", 1890 }, 1891 { 1892 'name': "Bobsleigh Olympic gold medallist", 1893 'date_awarded': "2018-02-01", 1894 }, 1895 ], initial=1) 1896 }, 1897 ], initial=1), 1898 'related_links': inline_formset([]), 1899 'head_counts': inline_formset([]), 1900 'action-publish': "Publish", 1901 }) 1902 response = self.client.post( 1903 reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), 1904 post_data 1905 ) 1906 self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) 1907 1908 new_christmas_page = EventPage.objects.get(url_path='/home/events/christmas/') 1909 self.assertEqual(new_christmas_page.speakers.first().first_name, "Jeff") 1910 awards = new_christmas_page.speakers.first().awards.all() 1911 self.assertEqual(len(awards), 2) 1912 self.assertEqual(awards[0].name, "Beard Of The Century") 1913 self.assertEqual(awards[1].name, "Bobsleigh Olympic gold medallist") 1914 1915 1916@override_settings(WAGTAIL_I18N_ENABLED=True) 1917class TestLocaleSelector(TestCase, WagtailTestUtils): 1918 fixtures = ['test.json'] 1919 1920 def setUp(self): 1921 self.christmas_page = EventPage.objects.get(url_path='/home/events/christmas/') 1922 self.fr_locale = Locale.objects.create(language_code='fr') 1923 self.translated_christmas_page = self.christmas_page.copy_for_translation(self.fr_locale, copy_parents=True) 1924 self.user = self.login() 1925 1926 def test_locale_selector(self): 1927 response = self.client.get( 1928 reverse('wagtailadmin_pages:edit', args=[self.christmas_page.id]) 1929 ) 1930 1931 self.assertContains(response, '<li class="header-meta--locale">') 1932 1933 edit_translation_url = reverse('wagtailadmin_pages:edit', args=[self.translated_christmas_page.id]) 1934 self.assertContains(response, f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">') 1935 1936 @override_settings(WAGTAIL_I18N_ENABLED=False) 1937 def test_locale_selector_not_present_when_i18n_disabled(self): 1938 response = self.client.get( 1939 reverse('wagtailadmin_pages:edit', args=[self.christmas_page.id]) 1940 ) 1941 1942 self.assertNotContains(response, '<li class="header-meta--locale">') 1943 1944 edit_translation_url = reverse('wagtailadmin_pages:edit', args=[self.translated_christmas_page.id]) 1945 self.assertNotContains(response, f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">') 1946 1947 def test_locale_dropdown_not_present_without_permission_to_edit(self): 1948 # Remove user's permissions to edit French tree 1949 en_events_index = Page.objects.get(url_path='/home/events/') 1950 group = Group.objects.get(name='Moderators') 1951 GroupPagePermission.objects.create( 1952 group=group, 1953 page=en_events_index, 1954 permission_type='edit', 1955 ) 1956 self.user.is_superuser = False 1957 self.user.user_permissions.add( 1958 Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') 1959 ) 1960 self.user.groups.add(group) 1961 self.user.save() 1962 1963 # Locale indicator should exist, but the "French" option should be hidden 1964 response = self.client.get( 1965 reverse('wagtailadmin_pages:edit', args=[self.christmas_page.id]) 1966 ) 1967 1968 self.assertContains(response, '<li class="header-meta--locale">') 1969 1970 edit_translation_url = reverse('wagtailadmin_pages:edit', args=[self.translated_christmas_page.id]) 1971 self.assertNotContains(response, f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">') 1972 1973 1974class TestPageSubscriptionSettings(TestCase, WagtailTestUtils): 1975 def setUp(self): 1976 # Find root page 1977 self.root_page = Page.objects.get(id=2) 1978 1979 # Add child page 1980 child_page = SimplePage( 1981 title="Hello world!", 1982 slug="hello-world", 1983 content="hello", 1984 ) 1985 self.root_page.add_child(instance=child_page) 1986 child_page.save_revision().publish() 1987 self.child_page = SimplePage.objects.get(id=child_page.id) 1988 1989 # Login 1990 self.user = self.login() 1991 1992 def test_commment_notifications_switched_off(self): 1993 response = self.client.get(reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 1994 1995 self.assertEqual(response.status_code, 200) 1996 self.assertContains(response, '<input type="checkbox" name="comment_notifications" id="id_comment_notifications">') 1997 1998 def test_commment_notifications_switched_on(self): 1999 PageSubscription.objects.create( 2000 page=self.child_page, 2001 user=self.user, 2002 comment_notifications=True 2003 ) 2004 2005 response = self.client.get(reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2006 2007 self.assertEqual(response.status_code, 200) 2008 self.assertContains(response, '<input type="checkbox" name="comment_notifications" id="id_comment_notifications" checked>') 2009 2010 def test_post_with_comment_notifications_switched_on(self): 2011 post_data = { 2012 'title': "I've been edited!", 2013 'content': "Some content", 2014 'slug': 'hello-world', 2015 'comment_notifications': 'on' 2016 } 2017 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2018 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2019 2020 # Check the subscription 2021 page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific 2022 subscription = page.subscribers.get() 2023 2024 self.assertEqual(subscription.user, self.user) 2025 self.assertTrue(subscription.comment_notifications) 2026 2027 def test_post_with_comment_notifications_switched_off(self): 2028 # Switch on comment notifications so we can test switching them off 2029 subscription = PageSubscription.objects.create( 2030 page=self.child_page, 2031 user=self.user, 2032 comment_notifications=True 2033 ) 2034 2035 post_data = { 2036 'title': "I've been edited!", 2037 'content': "Some content", 2038 'slug': 'hello-world', 2039 } 2040 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2041 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2042 2043 # Check the subscription 2044 subscription.refresh_from_db() 2045 self.assertFalse(subscription.comment_notifications) 2046 2047 @override_settings(WAGTAILADMIN_COMMENTS_ENABLED=False) 2048 def test_comments_disabled(self): 2049 response = self.client.get(reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2050 2051 self.assertEqual(response.status_code, 200) 2052 self.assertNotContains(response, '<input type="checkbox" name="comment_notifications" id="id_comment_notifications">') 2053 2054 @override_settings(WAGTAILADMIN_COMMENTS_ENABLED=False) 2055 def test_post_comments_disabled(self): 2056 post_data = { 2057 'title': "I've been edited!", 2058 'content': "Some content", 2059 'slug': 'hello-world', 2060 'comment_notifications': 'on' # Testing that this gets ignored 2061 } 2062 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2063 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2064 2065 # Check the subscription 2066 self.assertFalse(PageSubscription.objects.get().comment_notifications) 2067 2068 2069class TestCommenting(TestCase, WagtailTestUtils): 2070 """ 2071 Tests both the comment notification and audit logging logic of the edit page view. 2072 """ 2073 def setUp(self): 2074 # Find root page 2075 self.root_page = Page.objects.get(id=2) 2076 2077 # Add child page 2078 child_page = SimplePage( 2079 title="Hello world!", 2080 slug="hello-world", 2081 content="hello", 2082 ) 2083 self.root_page.add_child(instance=child_page) 2084 child_page.save_revision().publish() 2085 self.child_page = SimplePage.objects.get(id=child_page.id) 2086 2087 # Login 2088 self.user = self.login() 2089 2090 # Add a couple more users 2091 self.subscriber = self.create_user('subscriber') 2092 self.non_subscriber = self.create_user('non-subscriber') 2093 self.non_subscriber_2 = self.create_user('non-subscriber-2') 2094 2095 PageSubscription.objects.create( 2096 page=self.child_page, 2097 user=self.user, 2098 comment_notifications=True 2099 ) 2100 2101 PageSubscription.objects.create( 2102 page=self.child_page, 2103 user=self.subscriber, 2104 comment_notifications=True 2105 ) 2106 2107 def test_new_comment(self): 2108 post_data = { 2109 'title': "I've been edited!", 2110 'content': "Some content", 2111 'slug': 'hello-world', 2112 'comments-TOTAL_FORMS': '1', 2113 'comments-INITIAL_FORMS': '0', 2114 'comments-MIN_NUM_FORMS': '0', 2115 'comments-MAX_NUM_FORMS': '', 2116 'comments-0-DELETE': '', 2117 'comments-0-resolved': '', 2118 'comments-0-id': '', 2119 'comments-0-contentpath': 'title', 2120 'comments-0-text': 'A test comment', 2121 'comments-0-position': '', 2122 'comments-0-replies-TOTAL_FORMS': '0', 2123 'comments-0-replies-INITIAL_FORMS': '0', 2124 'comments-0-replies-MIN_NUM_FORMS': '0', 2125 'comments-0-replies-MAX_NUM_FORMS': '0' 2126 } 2127 2128 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2129 2130 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2131 2132 # Check the comment was added 2133 comment = self.child_page.comments.get() 2134 self.assertEqual(comment.text, 'A test comment') 2135 2136 # Check notification email 2137 self.assertEqual(len(mail.outbox), 1) 2138 self.assertEqual(mail.outbox[0].to, [self.subscriber.email]) 2139 self.assertEqual(mail.outbox[0].subject, 'test@email.com has updated comments on "I\'ve been edited! (simple page)"') 2140 self.assertIn('New comments:\n - "A test comment"\n\n', mail.outbox[0].body) 2141 2142 # Check audit log 2143 log_entry = PageLogEntry.objects.get(action='wagtail.comments.create') 2144 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2145 self.assertEqual(log_entry.user, self.user) 2146 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2147 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2148 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2149 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2150 2151 def test_edit_comment(self): 2152 comment = Comment.objects.create( 2153 page=self.child_page, 2154 user=self.user, 2155 text="A test comment", 2156 contentpath="title", 2157 ) 2158 2159 post_data = { 2160 'title': "I've been edited!", 2161 'content': "Some content", 2162 'slug': 'hello-world', 2163 'comments-TOTAL_FORMS': '1', 2164 'comments-INITIAL_FORMS': '1', 2165 'comments-MIN_NUM_FORMS': '0', 2166 'comments-MAX_NUM_FORMS': '', 2167 'comments-0-DELETE': '', 2168 'comments-0-resolved': '', 2169 'comments-0-id': str(comment.id), 2170 'comments-0-contentpath': 'title', 2171 'comments-0-text': 'Edited', 2172 'comments-0-position': '', 2173 'comments-0-replies-TOTAL_FORMS': '0', 2174 'comments-0-replies-INITIAL_FORMS': '0', 2175 'comments-0-replies-MIN_NUM_FORMS': '0', 2176 'comments-0-replies-MAX_NUM_FORMS': '0' 2177 } 2178 2179 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2180 2181 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2182 2183 # Check the comment was edited 2184 comment.refresh_from_db() 2185 self.assertEqual(comment.text, 'Edited') 2186 2187 # No emails should be sent for edited comments 2188 self.assertEqual(len(mail.outbox), 0) 2189 2190 # Check audit log 2191 log_entry = PageLogEntry.objects.get(action='wagtail.comments.edit') 2192 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2193 self.assertEqual(log_entry.user, self.user) 2194 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2195 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2196 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2197 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2198 2199 def test_edit_another_users_comment(self): 2200 comment = Comment.objects.create( 2201 page=self.child_page, 2202 user=self.subscriber, 2203 text="A test comment", 2204 contentpath="title", 2205 ) 2206 2207 post_data = { 2208 'title': "I've been edited!", 2209 'content': "Some content", 2210 'slug': 'hello-world', 2211 'comments-TOTAL_FORMS': '1', 2212 'comments-INITIAL_FORMS': '1', 2213 'comments-MIN_NUM_FORMS': '0', 2214 'comments-MAX_NUM_FORMS': '', 2215 'comments-0-DELETE': '', 2216 'comments-0-resolved': '', 2217 'comments-0-id': str(comment.id), 2218 'comments-0-contentpath': 'title', 2219 'comments-0-text': 'Edited', 2220 'comments-0-position': '', 2221 'comments-0-replies-TOTAL_FORMS': '0', 2222 'comments-0-replies-INITIAL_FORMS': '0', 2223 'comments-0-replies-MIN_NUM_FORMS': '0', 2224 'comments-0-replies-MAX_NUM_FORMS': '0' 2225 } 2226 2227 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2228 2229 self.assertEqual(response.context['form'].formsets['comments'].errors, [{'__all__': ["You cannot edit another user's comment."]}]) 2230 2231 # Check the comment was not edited 2232 comment.refresh_from_db() 2233 self.assertNotEqual(comment.text, 'Edited') 2234 2235 # Check no log entry was created 2236 self.assertFalse(PageLogEntry.objects.filter(action='wagtail.comments.edit').exists()) 2237 2238 def test_resolve_comment(self): 2239 comment = Comment.objects.create( 2240 page=self.child_page, 2241 user=self.non_subscriber, 2242 text="A test comment", 2243 contentpath="title", 2244 ) 2245 2246 post_data = { 2247 'title': "I've been edited!", 2248 'content': "Some content", 2249 'slug': 'hello-world', 2250 'comments-TOTAL_FORMS': '1', 2251 'comments-INITIAL_FORMS': '1', 2252 'comments-MIN_NUM_FORMS': '0', 2253 'comments-MAX_NUM_FORMS': '', 2254 'comments-0-DELETE': '', 2255 'comments-0-resolved': 'on', 2256 'comments-0-id': str(comment.id), 2257 'comments-0-contentpath': 'title', 2258 'comments-0-text': 'A test comment', 2259 'comments-0-position': '', 2260 'comments-0-replies-TOTAL_FORMS': '0', 2261 'comments-0-replies-INITIAL_FORMS': '0', 2262 'comments-0-replies-MIN_NUM_FORMS': '0', 2263 'comments-0-replies-MAX_NUM_FORMS': '0' 2264 } 2265 2266 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2267 2268 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2269 2270 # Check the comment was resolved 2271 comment.refresh_from_db() 2272 self.assertTrue(comment.resolved_at) 2273 self.assertEqual(comment.resolved_by, self.user) 2274 2275 # Check notification email 2276 self.assertEqual(len(mail.outbox), 2) 2277 # The non subscriber created the comment, so should also get an email 2278 self.assertEqual(mail.outbox[0].to, [self.non_subscriber.email]) 2279 self.assertEqual(mail.outbox[0].subject, 'test@email.com has updated comments on "I\'ve been edited! (simple page)"') 2280 self.assertIn('Resolved comments:\n - "A test comment"\n\n', mail.outbox[0].body) 2281 self.assertEqual(mail.outbox[1].to, [self.subscriber.email]) 2282 self.assertEqual(mail.outbox[1].subject, 'test@email.com has updated comments on "I\'ve been edited! (simple page)"') 2283 self.assertIn('Resolved comments:\n - "A test comment"\n\n', mail.outbox[1].body) 2284 2285 # Check audit log 2286 log_entry = PageLogEntry.objects.get(action='wagtail.comments.resolve') 2287 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2288 self.assertEqual(log_entry.user, self.user) 2289 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2290 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2291 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2292 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2293 2294 def test_delete_comment(self): 2295 comment = Comment.objects.create( 2296 page=self.child_page, 2297 user=self.user, 2298 text="A test comment", 2299 contentpath="title", 2300 ) 2301 2302 post_data = { 2303 'title': "I've been edited!", 2304 'content': "Some content", 2305 'slug': 'hello-world', 2306 'comments-TOTAL_FORMS': '1', 2307 'comments-INITIAL_FORMS': '1', 2308 'comments-MIN_NUM_FORMS': '0', 2309 'comments-MAX_NUM_FORMS': '', 2310 'comments-0-DELETE': 'on', 2311 'comments-0-resolved': '', 2312 'comments-0-id': str(comment.id), 2313 'comments-0-contentpath': 'title', 2314 'comments-0-text': 'A test comment', 2315 'comments-0-position': '', 2316 'comments-0-replies-TOTAL_FORMS': '0', 2317 'comments-0-replies-INITIAL_FORMS': '0', 2318 'comments-0-replies-MIN_NUM_FORMS': '0', 2319 'comments-0-replies-MAX_NUM_FORMS': '0' 2320 } 2321 2322 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2323 2324 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2325 2326 # Check the comment was deleted 2327 self.assertFalse(self.child_page.comments.exists()) 2328 2329 # Check notification email 2330 self.assertEqual(len(mail.outbox), 1) 2331 self.assertEqual(mail.outbox[0].to, [self.subscriber.email]) 2332 self.assertEqual(mail.outbox[0].subject, 'test@email.com has updated comments on "I\'ve been edited! (simple page)"') 2333 self.assertIn('Deleted comments:\n - "A test comment"\n\n', mail.outbox[0].body) 2334 2335 # Check audit log 2336 log_entry = PageLogEntry.objects.get(action='wagtail.comments.delete') 2337 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2338 self.assertEqual(log_entry.user, self.user) 2339 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2340 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2341 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2342 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2343 2344 def test_new_reply(self): 2345 comment = Comment.objects.create( 2346 page=self.child_page, 2347 user=self.non_subscriber, 2348 text="A test comment", 2349 contentpath="title", 2350 ) 2351 2352 reply = CommentReply.objects.create( 2353 comment=comment, 2354 user=self.non_subscriber_2, 2355 text='an old reply' 2356 ) 2357 2358 post_data = { 2359 'title': "I've been edited!", 2360 'content': "Some content", 2361 'slug': 'hello-world', 2362 'comments-TOTAL_FORMS': '1', 2363 'comments-INITIAL_FORMS': '1', 2364 'comments-MIN_NUM_FORMS': '0', 2365 'comments-MAX_NUM_FORMS': '', 2366 'comments-0-DELETE': '', 2367 'comments-0-resolved': '', 2368 'comments-0-id': str(comment.id), 2369 'comments-0-contentpath': 'title', 2370 'comments-0-text': 'A test comment', 2371 'comments-0-position': '', 2372 'comments-0-replies-TOTAL_FORMS': '2', 2373 'comments-0-replies-INITIAL_FORMS': '1', 2374 'comments-0-replies-MIN_NUM_FORMS': '0', 2375 'comments-0-replies-MAX_NUM_FORMS': '', 2376 'comments-0-replies-0-id': str(reply.id), 2377 'comments-0-replies-0-text': 'an old reply', 2378 'comments-0-replies-1-id': '', 2379 'comments-0-replies-1-text': 'a new reply' 2380 } 2381 2382 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2383 2384 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2385 2386 # Check the comment reply was added 2387 comment.refresh_from_db() 2388 self.assertEqual(comment.replies.last().text, 'a new reply') 2389 2390 # Check notification email 2391 self.assertEqual(len(mail.outbox), 3) 2392 2393 recipients = [mail.to for mail in mail.outbox] 2394 # The other non subscriber replied in the thread, so should get an email 2395 self.assertIn([self.non_subscriber_2.email], recipients) 2396 2397 # The non subscriber created the comment, so should get an email 2398 self.assertIn([self.non_subscriber.email], recipients) 2399 2400 self.assertIn([self.subscriber.email], recipients) 2401 self.assertEqual(mail.outbox[2].subject, 'test@email.com has updated comments on "I\'ve been edited! (simple page)"') 2402 self.assertIn(' New replies to: "A test comment"\n - "a new reply"', mail.outbox[2].body) 2403 2404 # Check audit log 2405 log_entry = PageLogEntry.objects.get(action='wagtail.comments.create_reply') 2406 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2407 self.assertEqual(log_entry.user, self.user) 2408 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2409 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2410 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2411 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2412 self.assertNotEqual(log_entry.data['reply']['id'], reply.id) 2413 self.assertEqual(log_entry.data['reply']['text'], 'a new reply') 2414 2415 def test_edit_reply(self): 2416 comment = Comment.objects.create( 2417 page=self.child_page, 2418 user=self.non_subscriber, 2419 text="A test comment", 2420 contentpath="title", 2421 ) 2422 2423 reply = CommentReply.objects.create( 2424 comment=comment, 2425 user=self.user, 2426 text='an old reply' 2427 ) 2428 2429 post_data = { 2430 'title': "I've been edited!", 2431 'content': "Some content", 2432 'slug': 'hello-world', 2433 'comments-TOTAL_FORMS': '1', 2434 'comments-INITIAL_FORMS': '1', 2435 'comments-MIN_NUM_FORMS': '0', 2436 'comments-MAX_NUM_FORMS': '', 2437 'comments-0-DELETE': '', 2438 'comments-0-resolved': '', 2439 'comments-0-id': str(comment.id), 2440 'comments-0-contentpath': 'title', 2441 'comments-0-text': 'A test comment', 2442 'comments-0-position': '', 2443 'comments-0-replies-TOTAL_FORMS': '1', 2444 'comments-0-replies-INITIAL_FORMS': '1', 2445 'comments-0-replies-MIN_NUM_FORMS': '0', 2446 'comments-0-replies-MAX_NUM_FORMS': '', 2447 'comments-0-replies-0-id': str(reply.id), 2448 'comments-0-replies-0-text': 'an edited reply', 2449 } 2450 2451 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2452 2453 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2454 2455 # Check the comment reply was edited 2456 reply.refresh_from_db() 2457 self.assertEqual(reply.text, 'an edited reply') 2458 2459 # Check no notification was sent 2460 self.assertEqual(len(mail.outbox), 0) 2461 2462 # Check audit log 2463 log_entry = PageLogEntry.objects.get(action='wagtail.comments.edit_reply') 2464 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2465 self.assertEqual(log_entry.user, self.user) 2466 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2467 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2468 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2469 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2470 self.assertEqual(log_entry.data['reply']['id'], reply.id) 2471 self.assertEqual(log_entry.data['reply']['text'], 'an edited reply') 2472 2473 def test_delete_reply(self): 2474 comment = Comment.objects.create( 2475 page=self.child_page, 2476 user=self.non_subscriber, 2477 text="A test comment", 2478 contentpath="title", 2479 ) 2480 2481 reply = CommentReply.objects.create( 2482 comment=comment, 2483 user=self.user, 2484 text='an old reply' 2485 ) 2486 2487 post_data = { 2488 'title': "I've been edited!", 2489 'content': "Some content", 2490 'slug': 'hello-world', 2491 'comments-TOTAL_FORMS': '1', 2492 'comments-INITIAL_FORMS': '1', 2493 'comments-MIN_NUM_FORMS': '0', 2494 'comments-MAX_NUM_FORMS': '', 2495 'comments-0-DELETE': '', 2496 'comments-0-resolved': '', 2497 'comments-0-id': str(comment.id), 2498 'comments-0-contentpath': 'title', 2499 'comments-0-text': 'A test comment', 2500 'comments-0-position': '', 2501 'comments-0-replies-TOTAL_FORMS': '1', 2502 'comments-0-replies-INITIAL_FORMS': '1', 2503 'comments-0-replies-MIN_NUM_FORMS': '0', 2504 'comments-0-replies-MAX_NUM_FORMS': '', 2505 'comments-0-replies-0-id': str(reply.id), 2506 'comments-0-replies-0-text': 'an old reply', 2507 'comments-0-replies-0-DELETE': 'on', 2508 } 2509 2510 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2511 2512 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2513 2514 # Check the comment reply was deleted 2515 self.assertFalse(comment.replies.exists()) 2516 2517 # Check no notification was sent 2518 self.assertEqual(len(mail.outbox), 0) 2519 2520 # Check audit log 2521 log_entry = PageLogEntry.objects.get(action='wagtail.comments.delete_reply') 2522 self.assertEqual(log_entry.page, self.child_page.page_ptr) 2523 self.assertEqual(log_entry.user, self.user) 2524 self.assertEqual(log_entry.revision, self.child_page.get_latest_revision()) 2525 self.assertEqual(log_entry.data['comment']['id'], comment.id) 2526 self.assertEqual(log_entry.data['comment']['contentpath'], comment.contentpath) 2527 self.assertEqual(log_entry.data['comment']['text'], comment.text) 2528 self.assertEqual(log_entry.data['reply']['id'], reply.id) 2529 self.assertEqual(log_entry.data['reply']['text'], reply.text) 2530 2531 def test_updated_comments_notifications_profile_setting(self): 2532 # Users can disable commenting notifications globally from account settings 2533 profile = UserProfile.get_for_user(self.subscriber) 2534 profile.updated_comments_notifications = False 2535 profile.save() 2536 2537 post_data = { 2538 'title': "I've been edited!", 2539 'content': "Some content", 2540 'slug': 'hello-world', 2541 'comments-TOTAL_FORMS': '1', 2542 'comments-INITIAL_FORMS': '0', 2543 'comments-MIN_NUM_FORMS': '0', 2544 'comments-MAX_NUM_FORMS': '', 2545 'comments-0-DELETE': '', 2546 'comments-0-resolved': '', 2547 'comments-0-id': '', 2548 'comments-0-contentpath': 'title', 2549 'comments-0-text': 'A test comment', 2550 'comments-0-position': '', 2551 'comments-0-replies-TOTAL_FORMS': '0', 2552 'comments-0-replies-INITIAL_FORMS': '0', 2553 'comments-0-replies-MIN_NUM_FORMS': '0', 2554 'comments-0-replies-MAX_NUM_FORMS': '0' 2555 } 2556 2557 response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.child_page.id]), post_data) 2558 2559 self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.child_page.id])) 2560 2561 # Check the comment was added 2562 comment = self.child_page.comments.get() 2563 self.assertEqual(comment.text, 'A test comment') 2564 2565 # This time, no emails should be submitted because the only subscriber has disabled these emails globally 2566 self.assertEqual(len(mail.outbox), 0) 2567