wagtail 6.1.3__py3-none-any.whl → 6.2rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1336 @@
1
+ import datetime
2
+
3
+ from django.conf import settings
4
+ from django.contrib.admin.utils import quote
5
+ from django.contrib.auth.models import Group, Permission
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from django.test import TestCase, override_settings
8
+ from django.urls import reverse
9
+ from django.utils import timezone
10
+ from freezegun import freeze_time
11
+
12
+ from wagtail.admin.models import EditingSession
13
+ from wagtail.models import GroupPagePermission, Page
14
+ from wagtail.test.testapp.models import (
15
+ Advert,
16
+ AdvertWithCustomPrimaryKey,
17
+ FullFeaturedSnippet,
18
+ SimplePage,
19
+ )
20
+ from wagtail.test.utils import WagtailTestUtils
21
+
22
+ if settings.USE_TZ:
23
+ TIMESTAMP_ANCIENT = timezone.make_aware(
24
+ datetime.datetime(2019, 1, 1, 10, 30, 0), timezone=datetime.timezone.utc
25
+ )
26
+ TIMESTAMP_PAST = timezone.make_aware(
27
+ datetime.datetime(2020, 1, 1, 10, 30, 0), timezone=datetime.timezone.utc
28
+ )
29
+ TIMESTAMP_1 = timezone.make_aware(
30
+ datetime.datetime(2020, 1, 1, 11, 59, 51), timezone=datetime.timezone.utc
31
+ )
32
+ TIMESTAMP_2 = timezone.make_aware(
33
+ datetime.datetime(2020, 1, 1, 11, 59, 52), timezone=datetime.timezone.utc
34
+ )
35
+ TIMESTAMP_3 = timezone.make_aware(
36
+ datetime.datetime(2020, 1, 1, 11, 59, 53), timezone=datetime.timezone.utc
37
+ )
38
+ TIMESTAMP_4 = timezone.make_aware(
39
+ datetime.datetime(2020, 1, 1, 11, 59, 54), timezone=datetime.timezone.utc
40
+ )
41
+ TIMESTAMP_NOW = timezone.make_aware(
42
+ datetime.datetime(2020, 1, 1, 12, 0, 0), timezone=datetime.timezone.utc
43
+ )
44
+ else:
45
+ TIMESTAMP_ANCIENT = datetime.datetime(2019, 1, 1, 10, 30, 0)
46
+ TIMESTAMP_PAST = datetime.datetime(2020, 1, 1, 10, 30, 0)
47
+ TIMESTAMP_1 = datetime.datetime(2020, 1, 1, 11, 59, 51)
48
+ TIMESTAMP_2 = datetime.datetime(2020, 1, 1, 11, 59, 52)
49
+ TIMESTAMP_3 = datetime.datetime(2020, 1, 1, 11, 59, 53)
50
+ TIMESTAMP_4 = datetime.datetime(2020, 1, 1, 11, 59, 54)
51
+ TIMESTAMP_NOW = datetime.datetime(2020, 1, 1, 12, 0, 0)
52
+
53
+
54
+ class TestPingView(WagtailTestUtils, TestCase):
55
+ def setUp(self):
56
+ self.user = self.create_superuser(
57
+ "bob", password="password", first_name="Bob", last_name="Testuser"
58
+ )
59
+ self.other_user = self.create_user(
60
+ "vic", password="password", first_name="Vic", last_name="Otheruser"
61
+ )
62
+ self.third_user = self.create_user(
63
+ "gordon", password="password", first_name="Gordon", last_name="Thirduser"
64
+ )
65
+
66
+ self.login(user=self.user)
67
+ self.root_page = Page.get_first_root_node()
68
+
69
+ self.page = SimplePage(title="Test page", slug="test-page", content="test page")
70
+ self.root_page.add_child(instance=self.page)
71
+
72
+ with freeze_time(TIMESTAMP_ANCIENT):
73
+ self.original_revision = self.page.save_revision(user=self.other_user)
74
+
75
+ with freeze_time(TIMESTAMP_PAST):
76
+ self.original_revision = self.page.save_revision(user=self.user)
77
+
78
+ self.other_page = SimplePage(
79
+ title="Other page", slug="other-page", content="other page"
80
+ )
81
+ self.root_page.add_child(instance=self.other_page)
82
+
83
+ page_content_type = ContentType.objects.get_for_model(Page)
84
+
85
+ self.session = EditingSession.objects.create(
86
+ user=self.user,
87
+ content_type=page_content_type,
88
+ object_id=self.page.id,
89
+ last_seen_at=TIMESTAMP_1,
90
+ )
91
+ self.other_session = EditingSession.objects.create(
92
+ user=self.other_user,
93
+ content_type=page_content_type,
94
+ object_id=self.page.id,
95
+ last_seen_at=TIMESTAMP_2,
96
+ )
97
+ self.old_session = EditingSession.objects.create(
98
+ user=self.other_user,
99
+ content_type=page_content_type,
100
+ object_id=self.page.id,
101
+ last_seen_at=TIMESTAMP_PAST,
102
+ )
103
+
104
+ def test_ping_invalid_model(self):
105
+ response = self.client.post(
106
+ reverse(
107
+ "wagtailadmin_editing_sessions:ping",
108
+ args=("testapp", "invalidmodel", str(self.page.id), self.session.id),
109
+ )
110
+ )
111
+ self.assertEqual(response.status_code, 404)
112
+
113
+ def test_ping_non_page_non_snippet_model(self):
114
+ editors = Group.objects.get(name="Editors")
115
+ session = EditingSession.objects.create(
116
+ user=self.user,
117
+ content_type=ContentType.objects.get_for_model(Group),
118
+ object_id=editors.pk,
119
+ last_seen_at=TIMESTAMP_1,
120
+ )
121
+ response = self.client.post(
122
+ reverse(
123
+ "wagtailadmin_editing_sessions:ping",
124
+ args=("auth", "group", str(editors.pk), session.id),
125
+ )
126
+ )
127
+ self.assertEqual(response.status_code, 404)
128
+
129
+ def test_ping_non_existent_object(self):
130
+ response = self.client.post(
131
+ reverse(
132
+ "wagtailadmin_editing_sessions:ping",
133
+ args=("wagtailcore", "page", 999999, self.session.id),
134
+ )
135
+ )
136
+ self.assertEqual(response.status_code, 404)
137
+
138
+ @freeze_time(TIMESTAMP_NOW)
139
+ def test_ping_existing_session(self):
140
+ response = self.client.post(
141
+ reverse(
142
+ "wagtailadmin_editing_sessions:ping",
143
+ args=("wagtailcore", "page", self.page.id, self.session.id),
144
+ )
145
+ )
146
+ self.assertEqual(response.status_code, 200)
147
+ response_json = response.json()
148
+ self.assertEqual(response_json["session_id"], self.session.id)
149
+ self.assertEqual(
150
+ response_json["other_sessions"],
151
+ [
152
+ {
153
+ "session_id": self.other_session.id,
154
+ "user": "Vic Otheruser",
155
+ "last_seen_at": TIMESTAMP_2.isoformat(),
156
+ "is_editing": False,
157
+ "revision_id": None,
158
+ },
159
+ ],
160
+ )
161
+
162
+ soup = self.get_soup(response_json["html"])
163
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
164
+ self.assertEqual(len(rendered_sessions), 1)
165
+ session_text = rendered_sessions[0].text
166
+ self.assertIn("Vic Otheruser", session_text)
167
+ self.assertIn("Currently viewing", session_text)
168
+
169
+ self.session.refresh_from_db()
170
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
171
+ self.assertFalse(self.session.is_editing)
172
+
173
+ @freeze_time(TIMESTAMP_NOW)
174
+ def test_ping_existing_session_with_editing_flag(self):
175
+ response = self.client.post(
176
+ reverse(
177
+ "wagtailadmin_editing_sessions:ping",
178
+ args=("wagtailcore", "page", self.page.id, self.session.id),
179
+ ),
180
+ {"is_editing": "1"},
181
+ )
182
+ self.assertEqual(response.status_code, 200)
183
+ response_json = response.json()
184
+ self.assertEqual(response_json["session_id"], self.session.id)
185
+ self.assertEqual(
186
+ response_json["other_sessions"],
187
+ [
188
+ # Should not cause any changes to the other sessions list,
189
+ # as the current session is the one that is editing
190
+ {
191
+ "session_id": self.other_session.id,
192
+ "user": "Vic Otheruser",
193
+ "last_seen_at": TIMESTAMP_2.isoformat(),
194
+ "is_editing": False,
195
+ "revision_id": None,
196
+ },
197
+ ],
198
+ )
199
+
200
+ soup = self.get_soup(response_json["html"])
201
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
202
+ self.assertEqual(len(rendered_sessions), 1)
203
+ session_text = rendered_sessions[0].text
204
+ self.assertIn("Vic Otheruser", session_text)
205
+ self.assertIn("Currently viewing", session_text)
206
+
207
+ self.session.refresh_from_db()
208
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
209
+ self.assertTrue(self.session.is_editing)
210
+
211
+ @freeze_time(TIMESTAMP_NOW)
212
+ def test_ping_with_revision(self):
213
+ response = self.client.post(
214
+ reverse(
215
+ "wagtailadmin_editing_sessions:ping",
216
+ args=("wagtailcore", "page", self.page.id, self.session.id),
217
+ ),
218
+ {"revision_id": self.original_revision.id},
219
+ )
220
+ self.assertEqual(response.status_code, 200)
221
+ response_json = response.json()
222
+ self.assertEqual(response_json["session_id"], self.session.id)
223
+
224
+ # no revisions have been saved since the original revision
225
+ self.assertEqual(
226
+ response_json["other_sessions"],
227
+ [
228
+ {
229
+ "session_id": self.other_session.id,
230
+ "user": "Vic Otheruser",
231
+ "last_seen_at": TIMESTAMP_2.isoformat(),
232
+ "is_editing": False,
233
+ "revision_id": None,
234
+ },
235
+ ],
236
+ )
237
+
238
+ soup = self.get_soup(response_json["html"])
239
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
240
+ self.assertEqual(len(rendered_sessions), 1)
241
+ session_text = rendered_sessions[0].text
242
+ self.assertIn("Vic Otheruser", session_text)
243
+ self.assertIn("Currently viewing", session_text)
244
+ self.assertNotIn("saved a new version", session_text)
245
+
246
+ self.session.refresh_from_db()
247
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
248
+ self.assertFalse(self.session.is_editing)
249
+
250
+ with freeze_time(TIMESTAMP_3):
251
+ new_revision = self.page.save_revision(user=self.other_user)
252
+
253
+ response = self.client.post(
254
+ reverse(
255
+ "wagtailadmin_editing_sessions:ping",
256
+ args=("wagtailcore", "page", self.page.id, self.session.id),
257
+ ),
258
+ {"revision_id": self.original_revision.id},
259
+ )
260
+ self.assertEqual(response.status_code, 200)
261
+ response_json = response.json()
262
+ self.assertEqual(response_json["session_id"], self.session.id)
263
+
264
+ # the new revision should be indicated in the response (and last_seen_at should reflect it)
265
+ self.assertEqual(
266
+ response_json["other_sessions"],
267
+ [
268
+ {
269
+ "session_id": self.other_session.id,
270
+ "user": "Vic Otheruser",
271
+ "last_seen_at": TIMESTAMP_3.isoformat(),
272
+ "is_editing": False,
273
+ "revision_id": new_revision.id,
274
+ },
275
+ ],
276
+ )
277
+
278
+ soup = self.get_soup(response_json["html"])
279
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
280
+ self.assertEqual(len(rendered_sessions), 1)
281
+ session_text = rendered_sessions[0].text
282
+ self.assertIn("Vic Otheruser saved a new version", session_text)
283
+ self.assertNotIn("Currently viewing", session_text)
284
+ dialog_title = soup.select_one(
285
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
286
+ )
287
+ self.assertIsNotNone(dialog_title)
288
+ self.assertIn(
289
+ "Vic Otheruser has saved a newer version of this page",
290
+ dialog_title.string,
291
+ )
292
+ dialog_subtitle = soup.select_one(
293
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
294
+ )
295
+ self.assertIsNotNone(dialog_subtitle)
296
+ self.assertIn(
297
+ "Proceeding will overwrite the changes made by Vic Otheruser. "
298
+ "Refreshing the page will lose any of your unsaved changes.",
299
+ dialog_subtitle.string,
300
+ )
301
+
302
+ self.session.refresh_from_db()
303
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
304
+ self.assertFalse(self.session.is_editing)
305
+
306
+ self.other_session.delete()
307
+
308
+ response = self.client.post(
309
+ reverse(
310
+ "wagtailadmin_editing_sessions:ping",
311
+ args=("wagtailcore", "page", self.page.id, self.session.id),
312
+ ),
313
+ {"revision_id": self.original_revision.id},
314
+ )
315
+ self.assertEqual(response.status_code, 200)
316
+ response_json = response.json()
317
+ self.assertEqual(response_json["session_id"], self.session.id)
318
+
319
+ # the new revision should still appear as an "other session" in the response,
320
+ # even though the editing session record has been deleted
321
+ self.assertEqual(
322
+ response_json["other_sessions"],
323
+ [
324
+ {
325
+ "session_id": None,
326
+ "user": "Vic Otheruser",
327
+ "last_seen_at": TIMESTAMP_3.isoformat(),
328
+ "is_editing": False,
329
+ "revision_id": new_revision.id,
330
+ },
331
+ ],
332
+ )
333
+
334
+ soup = self.get_soup(response_json["html"])
335
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
336
+ self.assertEqual(len(rendered_sessions), 1)
337
+ session_text = rendered_sessions[0].text
338
+ self.assertIn("Vic Otheruser saved a new version", session_text)
339
+ self.assertNotIn("Currently viewing", session_text)
340
+ dialog_title = soup.select_one(
341
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
342
+ )
343
+ self.assertIsNotNone(dialog_title)
344
+ self.assertIn(
345
+ "Vic Otheruser has saved a newer version of this page",
346
+ dialog_title.string,
347
+ )
348
+ dialog_subtitle = soup.select_one(
349
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
350
+ )
351
+ self.assertIsNotNone(dialog_subtitle)
352
+ self.assertIn(
353
+ "Proceeding will overwrite the changes made by Vic Otheruser. "
354
+ "Refreshing the page will lose any of your unsaved changes.",
355
+ dialog_subtitle.string,
356
+ )
357
+
358
+ self.session.refresh_from_db()
359
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
360
+ self.assertFalse(self.session.is_editing)
361
+
362
+ @freeze_time(TIMESTAMP_NOW)
363
+ def test_ping_with_multiple_revisions_since_own_revision(self):
364
+ # Create a new revision with the other_user
365
+ with freeze_time(TIMESTAMP_3):
366
+ self.page.save_revision(user=self.other_user)
367
+
368
+ # Create a new session with the third_user, and save a revision too
369
+ third_session = EditingSession.objects.create(
370
+ user=self.third_user,
371
+ content_type=ContentType.objects.get_for_model(Page),
372
+ object_id=self.page.id,
373
+ last_seen_at=TIMESTAMP_3,
374
+ )
375
+ with freeze_time(TIMESTAMP_4):
376
+ latest_revision = self.page.save_revision(user=self.third_user)
377
+
378
+ response = self.client.post(
379
+ reverse(
380
+ "wagtailadmin_editing_sessions:ping",
381
+ args=("wagtailcore", "page", self.page.id, self.session.id),
382
+ ),
383
+ {"revision_id": self.original_revision.id},
384
+ )
385
+ self.assertEqual(response.status_code, 200)
386
+ response_json = response.json()
387
+ self.assertEqual(response_json["session_id"], self.session.id)
388
+
389
+ # The revision_id should only be set for the session with the latest revision
390
+ self.assertEqual(
391
+ response_json["other_sessions"],
392
+ [
393
+ {
394
+ # The third_session has a newer ID, but is shown first
395
+ # because it has a revision_id set
396
+ "session_id": third_session.id,
397
+ "user": "Gordon Thirduser",
398
+ "last_seen_at": TIMESTAMP_4.isoformat(),
399
+ "is_editing": False,
400
+ "revision_id": latest_revision.id,
401
+ },
402
+ {
403
+ "session_id": self.other_session.id,
404
+ "user": "Vic Otheruser",
405
+ # The timestamp isn't updated for the other_session and it
406
+ # doesn't have a revision_id. This is because we don't care
407
+ # about the fact that this user created a new revision if
408
+ # it's not the latest one.
409
+ "last_seen_at": TIMESTAMP_2.isoformat(),
410
+ "is_editing": False,
411
+ "revision_id": None,
412
+ },
413
+ ],
414
+ )
415
+
416
+ soup = self.get_soup(response_json["html"])
417
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
418
+ self.assertEqual(len(rendered_sessions), 2)
419
+ session_text = rendered_sessions[0].text
420
+ self.assertIn("Gordon Thirduser saved a new version", session_text)
421
+ self.assertNotIn("Currently viewing", session_text)
422
+ dialog_title = soup.select_one(
423
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
424
+ )
425
+ self.assertIsNotNone(dialog_title)
426
+ self.assertIn(
427
+ "Gordon Thirduser has saved a newer version of this page",
428
+ dialog_title.string,
429
+ )
430
+ dialog_subtitle = soup.select_one(
431
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
432
+ )
433
+ self.assertIsNotNone(dialog_subtitle)
434
+ self.assertIn(
435
+ "Proceeding will overwrite the changes made by Gordon Thirduser. "
436
+ "Refreshing the page will lose any of your unsaved changes.",
437
+ dialog_subtitle.string,
438
+ )
439
+ other_session_text = rendered_sessions[1].text
440
+ self.assertIn("Vic Otheruser", other_session_text)
441
+ self.assertIn("Currently viewing", other_session_text)
442
+ self.assertNotIn("saved a new version", other_session_text)
443
+
444
+ self.session.refresh_from_db()
445
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
446
+ self.assertFalse(self.session.is_editing)
447
+
448
+ @freeze_time(TIMESTAMP_NOW)
449
+ def test_ping_with_new_revision_that_has_no_user(self):
450
+ # Create a new revision without any user
451
+ with freeze_time(TIMESTAMP_3):
452
+ latest_revision = self.page.save_revision()
453
+
454
+ response = self.client.post(
455
+ reverse(
456
+ "wagtailadmin_editing_sessions:ping",
457
+ args=("wagtailcore", "page", self.page.id, self.session.id),
458
+ ),
459
+ {"revision_id": self.original_revision.id},
460
+ )
461
+ self.assertEqual(response.status_code, 200)
462
+ response_json = response.json()
463
+ self.assertEqual(response_json["session_id"], self.session.id)
464
+
465
+ self.assertEqual(
466
+ response_json["other_sessions"],
467
+ [
468
+ {
469
+ # Should work even if the revision has no associated user
470
+ "session_id": None,
471
+ "user": "",
472
+ "last_seen_at": TIMESTAMP_3.isoformat(),
473
+ "is_editing": False,
474
+ "revision_id": latest_revision.id,
475
+ },
476
+ {
477
+ "session_id": self.other_session.id,
478
+ "user": "Vic Otheruser",
479
+ "last_seen_at": TIMESTAMP_2.isoformat(),
480
+ "is_editing": False,
481
+ "revision_id": None,
482
+ },
483
+ ],
484
+ )
485
+
486
+ soup = self.get_soup(response_json["html"])
487
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
488
+ self.assertEqual(len(rendered_sessions), 2)
489
+ session_text = rendered_sessions[0].text
490
+ self.assertIn("System saved a new version", session_text)
491
+ self.assertNotIn("Currently viewing", session_text)
492
+ dialog_title = soup.select_one(
493
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
494
+ )
495
+ self.assertIsNotNone(dialog_title)
496
+ self.assertIn(
497
+ "System has saved a newer version of this page",
498
+ dialog_title.string,
499
+ )
500
+ dialog_subtitle = soup.select_one(
501
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
502
+ )
503
+ self.assertIsNotNone(dialog_subtitle)
504
+ self.assertIn(
505
+ "Proceeding will overwrite the changes made by System. "
506
+ "Refreshing the page will lose any of your unsaved changes.",
507
+ dialog_subtitle.string,
508
+ )
509
+ other_session_text = rendered_sessions[1].text
510
+ self.assertIn("Vic Otheruser", other_session_text)
511
+ self.assertIn("Currently viewing", other_session_text)
512
+ self.assertNotIn("saved a new version", other_session_text)
513
+
514
+ self.session.refresh_from_db()
515
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
516
+ self.assertFalse(self.session.is_editing)
517
+
518
+ @freeze_time(TIMESTAMP_NOW)
519
+ def test_ping_session_ordering(self):
520
+ fourth_user = self.create_user(
521
+ "alyx", password="password", first_name="Alyx", last_name="Fourthuser"
522
+ )
523
+ fifth_user = self.create_user(
524
+ "chell", password="password", first_name="Chell", last_name="Fifthuser"
525
+ )
526
+
527
+ third_session = EditingSession.objects.create(
528
+ user=self.third_user,
529
+ content_type=ContentType.objects.get_for_model(Page),
530
+ object_id=self.page.id,
531
+ last_seen_at=TIMESTAMP_2,
532
+ )
533
+ fourth_session = EditingSession.objects.create(
534
+ user=fourth_user,
535
+ content_type=ContentType.objects.get_for_model(Page),
536
+ object_id=self.page.id,
537
+ # newer ping but not the last one to be created
538
+ last_seen_at=TIMESTAMP_1,
539
+ )
540
+ fifth_session = EditingSession.objects.create(
541
+ user=fifth_user,
542
+ content_type=ContentType.objects.get_for_model(Page),
543
+ object_id=self.page.id,
544
+ last_seen_at=TIMESTAMP_4,
545
+ is_editing=True,
546
+ )
547
+
548
+ with freeze_time(TIMESTAMP_3):
549
+ new_revision = self.page.save_revision(user=self.third_user)
550
+
551
+ response = self.client.post(
552
+ reverse(
553
+ "wagtailadmin_editing_sessions:ping",
554
+ args=("wagtailcore", "page", self.page.id, self.session.id),
555
+ ),
556
+ {"revision_id": self.original_revision.id},
557
+ )
558
+ self.assertEqual(response.status_code, 200)
559
+ response_json = response.json()
560
+ self.assertEqual(response_json["session_id"], self.session.id)
561
+ self.assertEqual(
562
+ response_json["other_sessions"],
563
+ [
564
+ # The session with the new revision should be shown first
565
+ {
566
+ "session_id": third_session.id,
567
+ "user": "Gordon Thirduser",
568
+ "last_seen_at": TIMESTAMP_3.isoformat(),
569
+ "is_editing": False,
570
+ "revision_id": new_revision.id,
571
+ },
572
+ # Then any sessions that are currently editing
573
+ {
574
+ "session_id": fifth_session.id,
575
+ "user": "Chell Fifthuser",
576
+ "last_seen_at": TIMESTAMP_4.isoformat(),
577
+ "is_editing": True,
578
+ "revision_id": None,
579
+ },
580
+ # Then any other sessions, sorted ascending by session_id
581
+ {
582
+ "session_id": self.other_session.id,
583
+ "user": "Vic Otheruser",
584
+ "last_seen_at": TIMESTAMP_2.isoformat(),
585
+ "is_editing": False,
586
+ "revision_id": None,
587
+ },
588
+ {
589
+ "session_id": fourth_session.id,
590
+ "user": "Alyx Fourthuser",
591
+ "last_seen_at": TIMESTAMP_1.isoformat(),
592
+ "is_editing": False,
593
+ "revision_id": None,
594
+ },
595
+ ],
596
+ )
597
+ self.session.refresh_from_db()
598
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
599
+ self.assertFalse(self.session.is_editing)
600
+
601
+ @freeze_time(TIMESTAMP_NOW)
602
+ def test_ping_new_session(self):
603
+ response = self.client.post(
604
+ reverse(
605
+ "wagtailadmin_editing_sessions:ping",
606
+ args=("wagtailcore", "page", self.page.id, 999999),
607
+ )
608
+ )
609
+ self.assertEqual(response.status_code, 200)
610
+ response_json = response.json()
611
+ new_session_id = response_json["session_id"]
612
+ session = EditingSession.objects.get(id=new_session_id)
613
+ self.assertEqual(session.user, self.user)
614
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
615
+ self.assertFalse(session.is_editing)
616
+
617
+ self.assertEqual(
618
+ response_json["other_sessions"],
619
+ [
620
+ # The user's original session is not shown as it is not
621
+ # currently editing nor has it created the latest revision
622
+ {
623
+ "session_id": self.other_session.id,
624
+ "user": "Vic Otheruser",
625
+ "last_seen_at": TIMESTAMP_2.isoformat(),
626
+ "is_editing": False,
627
+ "revision_id": None,
628
+ },
629
+ ],
630
+ )
631
+
632
+ # Should include the new URLs for the new session
633
+ self.assertEqual(
634
+ response_json["ping_url"],
635
+ reverse(
636
+ "wagtailadmin_editing_sessions:ping",
637
+ args=("wagtailcore", "page", self.page.id, session.id),
638
+ ),
639
+ )
640
+
641
+ self.assertEqual(
642
+ response_json["release_url"],
643
+ reverse(
644
+ "wagtailadmin_editing_sessions:release",
645
+ args=(session.id,),
646
+ ),
647
+ )
648
+
649
+ # content_object is a non-specific Page object
650
+ self.assertEqual(type(session.content_object), Page)
651
+ self.assertEqual(session.content_object.id, self.page.id)
652
+
653
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
654
+
655
+ @freeze_time(TIMESTAMP_NOW)
656
+ def test_ping_new_session_with_editing_flag(self):
657
+ response = self.client.post(
658
+ reverse(
659
+ "wagtailadmin_editing_sessions:ping",
660
+ args=("wagtailcore", "page", self.page.id, 999999),
661
+ ),
662
+ {"is_editing": "1"},
663
+ )
664
+ self.assertEqual(response.status_code, 200)
665
+ response_json = response.json()
666
+ new_session_id = response_json["session_id"]
667
+ session = EditingSession.objects.get(id=new_session_id)
668
+ self.assertEqual(session.user, self.user)
669
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
670
+ self.assertTrue(session.is_editing)
671
+
672
+ self.assertEqual(
673
+ response_json["other_sessions"],
674
+ [
675
+ # The user's original session is not shown as it is not
676
+ # currently editing nor has it created the latest revision
677
+ {
678
+ "session_id": self.other_session.id,
679
+ "user": "Vic Otheruser",
680
+ "last_seen_at": TIMESTAMP_2.isoformat(),
681
+ "is_editing": False,
682
+ "revision_id": None,
683
+ },
684
+ ],
685
+ )
686
+
687
+ # content_object is a non-specific Page object
688
+ self.assertEqual(type(session.content_object), Page)
689
+ self.assertEqual(session.content_object.id, self.page.id)
690
+
691
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
692
+
693
+ # The original session should not be changed
694
+ self.session.refresh_from_db()
695
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_1)
696
+ self.assertFalse(self.session.is_editing)
697
+
698
+ # Ping with the original session
699
+ response = self.client.post(
700
+ reverse(
701
+ "wagtailadmin_editing_sessions:ping",
702
+ args=("wagtailcore", "page", self.page.id, self.session.id),
703
+ )
704
+ )
705
+ self.assertEqual(response.status_code, 200)
706
+ response_json = response.json()
707
+ self.assertEqual(response_json["session_id"], self.session.id)
708
+ self.assertEqual(
709
+ response_json["other_sessions"],
710
+ [
711
+ # The new session should be shown as it is currently editing
712
+ {
713
+ "session_id": session.id,
714
+ "user": "Bob Testuser",
715
+ "last_seen_at": TIMESTAMP_NOW.isoformat(),
716
+ "is_editing": True,
717
+ "revision_id": None,
718
+ },
719
+ {
720
+ "session_id": self.other_session.id,
721
+ "user": "Vic Otheruser",
722
+ "last_seen_at": TIMESTAMP_2.isoformat(),
723
+ "is_editing": False,
724
+ "revision_id": None,
725
+ },
726
+ ],
727
+ )
728
+
729
+ soup = self.get_soup(response_json["html"])
730
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
731
+ self.assertEqual(len(rendered_sessions), 2)
732
+ session_text = rendered_sessions[0].text
733
+ self.assertIn("You have unsaved changes in another session", session_text)
734
+ self.assertNotIn("Currently viewing", session_text)
735
+ dialog_title = soup.select_one(
736
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
737
+ )
738
+ self.assertIsNone(dialog_title)
739
+ dialog_subtitle = soup.select_one(
740
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
741
+ )
742
+ self.assertIsNone(dialog_subtitle)
743
+ other_session_text = rendered_sessions[1].text
744
+ self.assertIn("Vic Otheruser", other_session_text)
745
+ self.assertIn("Currently viewing", other_session_text)
746
+ self.assertNotIn("saved a new version", other_session_text)
747
+
748
+ self.session.refresh_from_db()
749
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
750
+ self.assertFalse(self.session.is_editing)
751
+
752
+ @freeze_time(TIMESTAMP_NOW)
753
+ def test_ping_new_session_with_revision(self):
754
+ response = self.client.post(
755
+ reverse(
756
+ "wagtailadmin_editing_sessions:ping",
757
+ args=("wagtailcore", "page", self.page.id, 999999),
758
+ ),
759
+ {"revision_id": self.original_revision.id},
760
+ )
761
+ self.assertEqual(response.status_code, 200)
762
+ response_json = response.json()
763
+ new_session_id = response_json["session_id"]
764
+ session = EditingSession.objects.get(id=new_session_id)
765
+ self.assertEqual(session.user, self.user)
766
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
767
+ self.assertFalse(session.is_editing)
768
+
769
+ self.assertEqual(
770
+ response_json["other_sessions"],
771
+ [
772
+ # The user's original session is not shown as it is not
773
+ # currently editing nor has it created the latest revision
774
+ {
775
+ "session_id": self.other_session.id,
776
+ "user": "Vic Otheruser",
777
+ "last_seen_at": TIMESTAMP_2.isoformat(),
778
+ "is_editing": False,
779
+ "revision_id": None,
780
+ },
781
+ ],
782
+ )
783
+
784
+ # content_object is a non-specific Page object
785
+ self.assertEqual(type(session.content_object), Page)
786
+ self.assertEqual(session.content_object.id, self.page.id)
787
+
788
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
789
+
790
+ # The original session should not be changed
791
+ self.session.refresh_from_db()
792
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_1)
793
+ self.assertFalse(self.session.is_editing)
794
+
795
+ # Ping with the original session
796
+ response = self.client.post(
797
+ reverse(
798
+ "wagtailadmin_editing_sessions:ping",
799
+ args=("wagtailcore", "page", self.page.id, self.session.id),
800
+ )
801
+ )
802
+ self.assertEqual(response.status_code, 200)
803
+ response_json = response.json()
804
+ self.assertEqual(response_json["session_id"], self.session.id)
805
+ self.assertEqual(
806
+ response_json["other_sessions"],
807
+ [
808
+ # The new session is not shown as it is not
809
+ # currently editing nor has it created the latest revision
810
+ {
811
+ "session_id": self.other_session.id,
812
+ "user": "Vic Otheruser",
813
+ "last_seen_at": TIMESTAMP_2.isoformat(),
814
+ "is_editing": False,
815
+ "revision_id": None,
816
+ },
817
+ ],
818
+ )
819
+ self.session.refresh_from_db()
820
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
821
+ self.assertFalse(self.session.is_editing)
822
+
823
+ # Save a new revision as the current user
824
+ with freeze_time(TIMESTAMP_4):
825
+ new_revision = self.page.save_revision(user=self.user)
826
+
827
+ # Ping with the previously "new" session
828
+ response = self.client.post(
829
+ reverse(
830
+ "wagtailadmin_editing_sessions:ping",
831
+ args=("wagtailcore", "page", self.page.id, new_session_id),
832
+ ),
833
+ {"revision_id": self.original_revision.id},
834
+ )
835
+ self.assertEqual(response.status_code, 200)
836
+ response_json = response.json()
837
+ self.assertEqual(response_json["session_id"], new_session_id)
838
+
839
+ self.assertEqual(
840
+ response_json["other_sessions"],
841
+ [
842
+ # The new revision should be indicated in the response.
843
+ # In this case, it's attached to the original session. This may
844
+ # not be exactly true, i.e. the new revision might not be created
845
+ # by the original session. However, we don't keep track of which
846
+ # session created which revision, and we don't really need to.
847
+ # All we need to know is that there is a new revision since the
848
+ # one the session has in hand. As the new revision happens to
849
+ # be created by self.user, and the only other session we have
850
+ # of that user is the original session (self.session), we attach
851
+ # the new revision to that session.
852
+ {
853
+ "session_id": self.session.id,
854
+ "user": "Bob Testuser",
855
+ "last_seen_at": TIMESTAMP_NOW.isoformat(),
856
+ "is_editing": False,
857
+ "revision_id": new_revision.id,
858
+ },
859
+ {
860
+ "session_id": self.other_session.id,
861
+ "user": "Vic Otheruser",
862
+ "last_seen_at": TIMESTAMP_2.isoformat(),
863
+ "is_editing": False,
864
+ "revision_id": None,
865
+ },
866
+ ],
867
+ )
868
+
869
+ # Ping with the original self.session
870
+ response = self.client.post(
871
+ reverse(
872
+ "wagtailadmin_editing_sessions:ping",
873
+ args=("wagtailcore", "page", self.page.id, self.session.id),
874
+ ),
875
+ {"revision_id": self.original_revision.id},
876
+ )
877
+ self.assertEqual(response.status_code, 200)
878
+ response_json = response.json()
879
+ self.assertEqual(response_json["session_id"], self.session.id)
880
+
881
+ self.assertEqual(
882
+ response_json["other_sessions"],
883
+ [
884
+ # In the eye of the original session, the new revision is
885
+ # attached to the new session (for the same reason as the
886
+ # previous explanation).
887
+ {
888
+ "session_id": new_session_id,
889
+ "user": "Bob Testuser",
890
+ "last_seen_at": TIMESTAMP_NOW.isoformat(),
891
+ "is_editing": False,
892
+ "revision_id": new_revision.id,
893
+ },
894
+ {
895
+ "session_id": self.other_session.id,
896
+ "user": "Vic Otheruser",
897
+ "last_seen_at": TIMESTAMP_2.isoformat(),
898
+ "is_editing": False,
899
+ "revision_id": None,
900
+ },
901
+ ],
902
+ )
903
+
904
+ # Delete the new session
905
+ session.delete()
906
+
907
+ # Ping with the original self.session
908
+ response = self.client.post(
909
+ reverse(
910
+ "wagtailadmin_editing_sessions:ping",
911
+ args=("wagtailcore", "page", self.page.id, self.session.id),
912
+ ),
913
+ {"revision_id": self.original_revision.id},
914
+ )
915
+ self.assertEqual(response.status_code, 200)
916
+ response_json = response.json()
917
+ self.assertEqual(response_json["session_id"], self.session.id)
918
+
919
+ self.assertEqual(
920
+ response_json["other_sessions"],
921
+ [
922
+ # The 'other' session of the same user is still shown
923
+ # as it has the latest revision, even though there are no
924
+ # other sessions to attach the revision to. The last_seen_at
925
+ # is set to the time of the revision's creation.
926
+ {
927
+ "session_id": None,
928
+ "user": "Bob Testuser",
929
+ "last_seen_at": TIMESTAMP_4.isoformat(),
930
+ "is_editing": False,
931
+ "revision_id": new_revision.id,
932
+ },
933
+ {
934
+ "session_id": self.other_session.id,
935
+ "user": "Vic Otheruser",
936
+ "last_seen_at": TIMESTAMP_2.isoformat(),
937
+ "is_editing": False,
938
+ "revision_id": None,
939
+ },
940
+ ],
941
+ )
942
+
943
+ @freeze_time(TIMESTAMP_NOW)
944
+ def test_user_must_have_edit_permission_on_page(self):
945
+ # make user a member of Editors
946
+ self.user.is_superuser = False
947
+ self.user.save()
948
+ editors = Group.objects.get(name="Editors")
949
+ self.user.groups.add(editors)
950
+
951
+ # give the Editors group edit permision on other_page only
952
+ GroupPagePermission.objects.filter(group=editors).delete()
953
+ GroupPagePermission.objects.create(
954
+ group=editors,
955
+ page=self.other_page,
956
+ permission=Permission.objects.get(codename="change_page"),
957
+ )
958
+
959
+ response = self.client.post(
960
+ reverse(
961
+ "wagtailadmin_editing_sessions:ping",
962
+ args=("wagtailcore", "page", self.page.id, 999999),
963
+ )
964
+ )
965
+ self.assertEqual(response.status_code, 404)
966
+
967
+ response = self.client.post(
968
+ reverse(
969
+ "wagtailadmin_editing_sessions:ping",
970
+ args=("wagtailcore", "page", self.other_page.id, 999999),
971
+ )
972
+ )
973
+ self.assertEqual(response.status_code, 200)
974
+
975
+ @freeze_time(TIMESTAMP_NOW)
976
+ def test_ping_snippet_model(self):
977
+ snippet = Advert.objects.create(text="Test snippet")
978
+
979
+ # make user a member of Editors
980
+ self.user.is_superuser = False
981
+ self.user.save()
982
+ editors = Group.objects.get(name="Editors")
983
+ self.user.groups.add(editors)
984
+
985
+ editors.permissions.add(
986
+ Permission.objects.get(codename="change_advert"),
987
+ )
988
+
989
+ session = EditingSession.objects.create(
990
+ user=self.user,
991
+ content_type=ContentType.objects.get_for_model(Advert),
992
+ object_id=snippet.pk,
993
+ last_seen_at=TIMESTAMP_1,
994
+ )
995
+ # add two sessions from other_user to test that we correctly merge them into
996
+ # one record in the response
997
+ EditingSession.objects.create(
998
+ user=self.other_user,
999
+ content_type=ContentType.objects.get_for_model(Advert),
1000
+ object_id=snippet.pk,
1001
+ last_seen_at=TIMESTAMP_2,
1002
+ is_editing=True,
1003
+ )
1004
+ other_session_2 = EditingSession.objects.create(
1005
+ user=self.other_user,
1006
+ content_type=ContentType.objects.get_for_model(Advert),
1007
+ object_id=snippet.pk,
1008
+ last_seen_at=TIMESTAMP_3,
1009
+ is_editing=False,
1010
+ )
1011
+
1012
+ # session with last_seen_at too far in the past to be included in the response
1013
+ EditingSession.objects.create(
1014
+ user=self.other_user,
1015
+ content_type=ContentType.objects.get_for_model(Advert),
1016
+ object_id=snippet.pk,
1017
+ last_seen_at=TIMESTAMP_PAST,
1018
+ )
1019
+ response = self.client.post(
1020
+ reverse(
1021
+ "wagtailadmin_editing_sessions:ping",
1022
+ args=("tests", "advert", str(snippet.pk), session.id),
1023
+ )
1024
+ )
1025
+ self.assertEqual(response.status_code, 200)
1026
+ response_json = response.json()
1027
+ self.assertEqual(response_json["session_id"], session.id)
1028
+ self.assertEqual(
1029
+ response_json["other_sessions"],
1030
+ [
1031
+ {
1032
+ "session_id": other_session_2.id,
1033
+ "user": "Vic Otheruser",
1034
+ "last_seen_at": TIMESTAMP_3.isoformat(),
1035
+ "is_editing": True,
1036
+ "revision_id": None,
1037
+ },
1038
+ ],
1039
+ )
1040
+ session.refresh_from_db()
1041
+ self.assertEqual(session.last_seen_at, TIMESTAMP_NOW)
1042
+ self.assertFalse(session.is_editing)
1043
+
1044
+ def test_ping_snippet_model_without_permission(self):
1045
+ snippet = Advert.objects.create(text="Test snippet")
1046
+
1047
+ # make user a member of Editors
1048
+ self.user.is_superuser = False
1049
+ self.user.save()
1050
+ editors = Group.objects.get(name="Editors")
1051
+ self.user.groups.add(editors)
1052
+
1053
+ session = EditingSession.objects.create(
1054
+ user=self.user,
1055
+ content_type=ContentType.objects.get_for_model(Advert),
1056
+ object_id=snippet.pk,
1057
+ last_seen_at=TIMESTAMP_1,
1058
+ )
1059
+ response = self.client.post(
1060
+ reverse(
1061
+ "wagtailadmin_editing_sessions:ping",
1062
+ args=("tests", "advert", str(snippet.pk), session.id),
1063
+ )
1064
+ )
1065
+ self.assertEqual(response.status_code, 404)
1066
+
1067
+ def test_must_post(self):
1068
+ response = self.client.get(
1069
+ reverse(
1070
+ "wagtailadmin_editing_sessions:ping",
1071
+ args=("wagtailcore", "page", self.page.id, 999999),
1072
+ )
1073
+ )
1074
+ self.assertEqual(response.status_code, 405)
1075
+ self.assertCountEqual(
1076
+ EditingSession.objects.all(),
1077
+ [self.session, self.other_session, self.old_session],
1078
+ )
1079
+
1080
+ def test_invalid_data(self):
1081
+ response = self.client.post(
1082
+ reverse(
1083
+ "wagtailadmin_editing_sessions:ping",
1084
+ args=("wagtailcore", "page", self.page.id, self.session.id),
1085
+ ),
1086
+ {"is_editing": "invalid"},
1087
+ )
1088
+ self.assertEqual(response.status_code, 400)
1089
+ self.assertEqual(response.json(), {"error": "Invalid data"})
1090
+ self.assertCountEqual(
1091
+ EditingSession.objects.all(),
1092
+ [self.session, self.other_session, self.old_session],
1093
+ )
1094
+
1095
+
1096
+ class TestCleanup(WagtailTestUtils, TestCase):
1097
+ def setUp(self):
1098
+ self.user = self.create_superuser(
1099
+ "bob", password="password", first_name="Bob", last_name="Testuser"
1100
+ )
1101
+ self.root_page = Page.get_first_root_node()
1102
+
1103
+ self.page = SimplePage(title="Test page", slug="test-page", content="test page")
1104
+ self.root_page.add_child(instance=self.page)
1105
+
1106
+ page_content_type = ContentType.objects.get_for_model(Page)
1107
+
1108
+ self.session = EditingSession.objects.create(
1109
+ user=self.user,
1110
+ content_type=page_content_type,
1111
+ object_id=self.page.id,
1112
+ last_seen_at=TIMESTAMP_1,
1113
+ )
1114
+ self.old_session = EditingSession.objects.create(
1115
+ user=self.user,
1116
+ content_type=page_content_type,
1117
+ object_id=self.page.id,
1118
+ last_seen_at=TIMESTAMP_PAST,
1119
+ )
1120
+
1121
+ @freeze_time(TIMESTAMP_NOW)
1122
+ def test_cleanup(self):
1123
+ EditingSession.cleanup()
1124
+ self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
1125
+ self.assertFalse(EditingSession.objects.filter(id=self.old_session.id).exists())
1126
+
1127
+
1128
+ class TestReleaseView(WagtailTestUtils, TestCase):
1129
+ def setUp(self):
1130
+ self.user = self.create_superuser(
1131
+ "bob", password="password", first_name="Bob", last_name="Testuser"
1132
+ )
1133
+ self.login(user=self.user)
1134
+ self.root_page = Page.get_first_root_node()
1135
+
1136
+ self.page = SimplePage(title="Test page", slug="test-page", content="test page")
1137
+ self.root_page.add_child(instance=self.page)
1138
+
1139
+ self.other_user = self.create_user(
1140
+ "vic", password="password", first_name="Vic", last_name="Otheruser"
1141
+ )
1142
+
1143
+ page_content_type = ContentType.objects.get_for_model(Page)
1144
+
1145
+ self.session = EditingSession.objects.create(
1146
+ user=self.user,
1147
+ content_type=page_content_type,
1148
+ object_id=self.page.id,
1149
+ last_seen_at=TIMESTAMP_1,
1150
+ )
1151
+ self.other_session = EditingSession.objects.create(
1152
+ user=self.other_user,
1153
+ content_type=page_content_type,
1154
+ object_id=self.page.id,
1155
+ last_seen_at=TIMESTAMP_1,
1156
+ )
1157
+
1158
+ def test_release(self):
1159
+ response = self.client.post(
1160
+ reverse("wagtailadmin_editing_sessions:release", args=(self.session.id,))
1161
+ )
1162
+ self.assertEqual(response.status_code, 200)
1163
+ self.assertFalse(EditingSession.objects.filter(id=self.session.id).exists())
1164
+ self.assertTrue(
1165
+ EditingSession.objects.filter(id=self.other_session.id).exists()
1166
+ )
1167
+
1168
+ def test_must_post(self):
1169
+ response = self.client.get(
1170
+ reverse("wagtailadmin_editing_sessions:release", args=(self.session.id,))
1171
+ )
1172
+ self.assertEqual(response.status_code, 405)
1173
+ self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
1174
+ self.assertTrue(
1175
+ EditingSession.objects.filter(id=self.other_session.id).exists()
1176
+ )
1177
+
1178
+ def test_cannot_release_other_users_session(self):
1179
+ response = self.client.post(
1180
+ reverse(
1181
+ "wagtailadmin_editing_sessions:release", args=(self.other_session.id,)
1182
+ )
1183
+ )
1184
+ self.assertEqual(response.status_code, 200)
1185
+ self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
1186
+ self.assertTrue(
1187
+ EditingSession.objects.filter(id=self.other_session.id).exists()
1188
+ )
1189
+
1190
+
1191
+ class TestModuleInEditView(WagtailTestUtils, TestCase):
1192
+ url_name = "wagtailadmin_pages:edit"
1193
+ model = Page
1194
+
1195
+ def setUp(self):
1196
+ self.user = self.create_superuser(
1197
+ "bob", password="password", first_name="Bob", last_name="Testuser"
1198
+ )
1199
+ self.login(user=self.user)
1200
+ self.content_type = ContentType.objects.get_for_model(self.model)
1201
+
1202
+ self.object = self.create_object()
1203
+
1204
+ self.session = EditingSession.objects.create(
1205
+ user=self.user,
1206
+ content_type=self.content_type,
1207
+ object_id=self.object.pk,
1208
+ last_seen_at=TIMESTAMP_1,
1209
+ )
1210
+ self.old_session = EditingSession.objects.create(
1211
+ user=self.user,
1212
+ content_type=self.content_type,
1213
+ object_id=self.object.pk,
1214
+ last_seen_at=TIMESTAMP_PAST,
1215
+ )
1216
+
1217
+ def create_object(self):
1218
+ root_page = Page.get_first_root_node()
1219
+ page = SimplePage(title="Foo", slug="foo", content="bar")
1220
+ root_page.add_child(instance=page)
1221
+ page.save_revision()
1222
+ return page
1223
+
1224
+ def get(self):
1225
+ return self.client.get(reverse(self.url_name, args=(quote(self.object.pk),)))
1226
+
1227
+ def assertRevisionInput(self, soup):
1228
+ revision_input = soup.select_one('input[name="revision_id"]')
1229
+ self.assertIsNotNone(revision_input)
1230
+ self.assertEqual(revision_input.get("type"), "hidden")
1231
+ self.assertEqual(
1232
+ revision_input.get("value"),
1233
+ str(self.object.latest_revision.id),
1234
+ )
1235
+
1236
+ @freeze_time(TIMESTAMP_NOW)
1237
+ def test_edit_view_with_default_interval(self):
1238
+ self.assertEqual(EditingSession.objects.all().count(), 2)
1239
+ response = self.get()
1240
+ self.assertEqual(response.status_code, 200)
1241
+
1242
+ # Should perform a cleanup of the EditingSessions
1243
+ self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
1244
+ self.assertFalse(EditingSession.objects.filter(id=self.old_session.id).exists())
1245
+
1246
+ # Should create a new EditingSession for the current user
1247
+ self.assertEqual(EditingSession.objects.all().count(), 2)
1248
+ new_session = EditingSession.objects.exclude(id=self.session.id).get(
1249
+ content_type=self.content_type,
1250
+ object_id=self.object.pk,
1251
+ )
1252
+ self.assertEqual(new_session.user, self.user)
1253
+
1254
+ # Should load the EditingSessionsModule with the default interval (10s)
1255
+ soup = self.get_soup(response.content)
1256
+ module = soup.select_one('form[data-controller~="w-session"]')
1257
+ self.assertIsNotNone(module)
1258
+ self.assertEqual(module.get("data-w-session-interval-value"), "10000")
1259
+
1260
+ # Should show the revision_id input
1261
+ self.assertRevisionInput(module)
1262
+
1263
+ @freeze_time(TIMESTAMP_NOW)
1264
+ @override_settings(WAGTAIL_EDITING_SESSION_PING_INTERVAL=30000)
1265
+ def test_edit_view_with_custom_interval(self):
1266
+ self.assertEqual(EditingSession.objects.all().count(), 2)
1267
+ response = self.get()
1268
+ self.assertEqual(response.status_code, 200)
1269
+
1270
+ # Should perform a cleanup of the EditingSessions
1271
+ self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
1272
+ self.assertFalse(EditingSession.objects.filter(id=self.old_session.id).exists())
1273
+
1274
+ # Should create a new EditingSession for the current user
1275
+ self.assertEqual(EditingSession.objects.all().count(), 2)
1276
+ new_session = EditingSession.objects.exclude(id=self.session.id).get(
1277
+ content_type=self.content_type,
1278
+ object_id=self.object.pk,
1279
+ )
1280
+ self.assertEqual(new_session.user, self.user)
1281
+
1282
+ # Should load the EditingSessionsModule
1283
+ soup = self.get_soup(response.content)
1284
+ module = soup.select_one('form[data-controller~="w-session"]')
1285
+ self.assertIsNotNone(module)
1286
+ self.assertEqual(
1287
+ module.get("data-w-swap-src-value"),
1288
+ reverse(
1289
+ "wagtailadmin_editing_sessions:ping",
1290
+ args=(
1291
+ self.content_type.app_label,
1292
+ self.content_type.model,
1293
+ quote(self.object.pk),
1294
+ new_session.id,
1295
+ ),
1296
+ ),
1297
+ )
1298
+ self.assertEqual(
1299
+ module.get("data-w-action-url-value"),
1300
+ reverse(
1301
+ "wagtailadmin_editing_sessions:release",
1302
+ args=(new_session.id,),
1303
+ ),
1304
+ )
1305
+
1306
+ # Should use the custom interval (30s)
1307
+ self.assertEqual(module.get("data-w-session-interval-value"), "30000")
1308
+ self.assertRevisionInput(module)
1309
+
1310
+
1311
+ class TestModuleInEditViewWithRevisableSnippet(TestModuleInEditView):
1312
+ model = FullFeaturedSnippet
1313
+
1314
+ @property
1315
+ def url_name(self):
1316
+ return self.model.snippet_viewset.get_url_name("edit")
1317
+
1318
+ def create_object(self):
1319
+ obj = self.model.objects.create(text="Shodan")
1320
+ obj.save_revision()
1321
+ return obj
1322
+
1323
+
1324
+ class TestModuleInEditViewWithNonRevisableSnippet(TestModuleInEditView):
1325
+ model = AdvertWithCustomPrimaryKey
1326
+
1327
+ @property
1328
+ def url_name(self):
1329
+ return self.model.snippet_viewset.get_url_name("edit")
1330
+
1331
+ def create_object(self):
1332
+ return self.model.objects.create(text="GLaDOS", advert_id="m0n5t3r!/#")
1333
+
1334
+ def assertRevisionInput(self, soup):
1335
+ revision_input = soup.select_one('input[name="revision_id"]')
1336
+ self.assertIsNone(revision_input)