wagtail 7.2.1__py3-none-any.whl → 7.3rc1__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 (312) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +4 -2
  3. wagtail/admin/action_menu.py +4 -1
  4. wagtail/admin/api/actions/convert_alias.py +2 -2
  5. wagtail/admin/api/actions/copy.py +2 -2
  6. wagtail/admin/api/actions/copy_for_translation.py +2 -2
  7. wagtail/admin/api/actions/create_alias.py +2 -2
  8. wagtail/admin/api/actions/delete.py +1 -1
  9. wagtail/admin/api/actions/move.py +1 -1
  10. wagtail/admin/api/actions/publish.py +2 -2
  11. wagtail/admin/api/actions/revert_to_page_revision.py +2 -2
  12. wagtail/admin/api/actions/unpublish.py +1 -1
  13. wagtail/admin/api/filters.py +2 -2
  14. wagtail/admin/compare.py +22 -0
  15. wagtail/admin/forms/account.py +52 -1
  16. wagtail/admin/forms/comments.py +53 -0
  17. wagtail/admin/forms/models.py +36 -0
  18. wagtail/admin/forms/pages.py +7 -0
  19. wagtail/admin/forms/workflows.py +5 -2
  20. wagtail/admin/icons.py +4 -3
  21. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  22. wagtail/admin/locale/ar/LC_MESSAGES/django.po +35 -0
  23. wagtail/admin/locale/en/LC_MESSAGES/django.po +262 -234
  24. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +72 -43
  25. wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
  26. wagtail/admin/locale/it/LC_MESSAGES/django.po +566 -6
  27. wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
  28. wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +40 -2
  29. wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
  30. wagtail/admin/locale/nl/LC_MESSAGES/django.po +2 -2
  31. wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
  32. wagtail/admin/locale/sv/LC_MESSAGES/django.po +330 -15
  33. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.mo +0 -0
  34. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.po +3 -2
  35. wagtail/admin/panels/comment_panel.py +1 -51
  36. wagtail/admin/panels/title_field_panel.py +3 -1
  37. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  38. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  42. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  48. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  49. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  50. wagtail/admin/templates/wagtailadmin/base.html +1 -1
  51. wagtail/admin/templates/wagtailadmin/generic/edit_partials.html +100 -0
  52. wagtail/admin/templates/wagtailadmin/generic/form.html +26 -5
  53. wagtail/admin/templates/wagtailadmin/generic/includes/_loaded_revision_inputs.html +3 -0
  54. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +1 -17
  55. wagtail/admin/templates/wagtailadmin/pages/create.html +14 -4
  56. wagtail/admin/templates/wagtailadmin/pages/edit.html +16 -3
  57. wagtail/admin/templates/wagtailadmin/pages/edit_partials.html +31 -0
  58. wagtail/admin/templates/wagtailadmin/pages/index_results.html +1 -9
  59. wagtail/admin/templates/wagtailadmin/shared/autosave/indicator.html +36 -0
  60. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +1 -1
  61. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +2 -2
  62. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +19 -3
  63. wagtail/admin/templates/wagtailadmin/shared/headers/_actions.html +5 -0
  64. wagtail/admin/templates/wagtailadmin/shared/headers/_history_icon_link.html +2 -2
  65. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +8 -9
  66. wagtail/admin/templates/wagtailadmin/shared/listing/filter_partials.html +19 -0
  67. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +3 -3
  68. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/side_panel.html +3 -0
  69. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +18 -6
  70. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +11 -4
  71. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/workflow.html +22 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -0
  73. wagtail/admin/templates/wagtailadmin/shared/side_panels.html +1 -2
  74. wagtail/admin/templates/wagtailadmin/shared/unsaved_changes_warning.html +20 -20
  75. wagtail/admin/templates/wagtailadmin/userbar/base.html +2 -0
  76. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +1 -1
  77. wagtail/admin/templatetags/wagtailadmin_tags.py +6 -14
  78. wagtail/admin/tests/pages/test_create_page.py +298 -1
  79. wagtail/admin/tests/pages/test_custom_listing.py +48 -2
  80. wagtail/admin/tests/pages/test_edit_page.py +721 -7
  81. wagtail/admin/tests/pages/test_explorer_view.py +9 -5
  82. wagtail/admin/tests/pages/test_page_search.py +15 -0
  83. wagtail/admin/tests/pages/test_revisions.py +4 -0
  84. wagtail/admin/tests/test_account_management.py +88 -2
  85. wagtail/admin/tests/test_collections_views.py +15 -15
  86. wagtail/admin/tests/test_compare.py +34 -0
  87. wagtail/admin/tests/test_editing_sessions.py +230 -8
  88. wagtail/admin/tests/test_forms.py +192 -1
  89. wagtail/admin/tests/test_icon_sprite.py +22 -2
  90. wagtail/admin/tests/test_page_chooser.py +13 -13
  91. wagtail/admin/tests/test_reports_views.py +4 -1
  92. wagtail/admin/tests/test_userbar.py +69 -5
  93. wagtail/admin/tests/test_views_generic.py +5 -5
  94. wagtail/admin/tests/test_workflows.py +14 -12
  95. wagtail/admin/tests/viewsets/test_model_viewset.py +13 -0
  96. wagtail/admin/ui/autosave.py +5 -0
  97. wagtail/admin/ui/editing_sessions.py +3 -0
  98. wagtail/admin/ui/side_panels.py +19 -20
  99. wagtail/admin/userbar.py +48 -27
  100. wagtail/admin/views/bulk_action/dispatcher.py +2 -2
  101. wagtail/admin/views/chooser.py +6 -6
  102. wagtail/admin/views/editing_sessions.py +20 -7
  103. wagtail/admin/views/generic/__init__.py +1 -0
  104. wagtail/admin/views/generic/chooser.py +5 -5
  105. wagtail/admin/views/generic/mixins.py +143 -26
  106. wagtail/admin/views/generic/models.py +157 -27
  107. wagtail/admin/views/generic/ordering.py +1 -1
  108. wagtail/admin/views/generic/preview.py +4 -4
  109. wagtail/admin/views/home.py +3 -1
  110. wagtail/admin/views/pages/create.py +77 -29
  111. wagtail/admin/views/pages/edit.py +160 -34
  112. wagtail/admin/views/pages/preview.py +4 -4
  113. wagtail/admin/views/pages/revisions.py +3 -0
  114. wagtail/admin/views/pages/search.py +4 -4
  115. wagtail/admin/views/pages/usage.py +2 -2
  116. wagtail/admin/views/tags.py +2 -2
  117. wagtail/admin/views/workflows.py +73 -54
  118. wagtail/admin/viewsets/model.py +34 -8
  119. wagtail/admin/widgets/button.py +11 -4
  120. wagtail/admin/widgets/chooser.py +6 -4
  121. wagtail/admin/widgets/slug.py +1 -1
  122. wagtail/api/v2/filters.py +27 -21
  123. wagtail/api/v2/pagination.py +4 -4
  124. wagtail/api/v2/views.py +7 -7
  125. wagtail/blocks/list_block.py +0 -8
  126. wagtail/blocks/migrations/migrate_operation.py +8 -1
  127. wagtail/blocks/stream_block.py +2 -10
  128. wagtail/blocks/struct_block.py +192 -12
  129. wagtail/compat.py +2 -2
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.mo +0 -0
  132. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.po +40 -2
  133. wagtail/contrib/frontend_cache/backends/azure.py +6 -6
  134. wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
  135. wagtail/contrib/frontend_cache/utils.py +1 -1
  136. wagtail/contrib/redirects/forms.py +1 -1
  137. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +14 -14
  138. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.mo +0 -0
  139. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.po +15 -2
  140. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.mo +0 -0
  141. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.po +16 -3
  142. wagtail/contrib/redirects/middleware.py +11 -7
  143. wagtail/contrib/redirects/models.py +17 -1
  144. wagtail/contrib/redirects/tests/test_import_admin_views.py +3 -3
  145. wagtail/contrib/redirects/tests/test_redirects.py +56 -8
  146. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +6 -6
  147. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
  148. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +43 -2
  149. wagtail/contrib/search_promotions/tests.py +1 -1
  150. wagtail/contrib/search_promotions/views/settings.py +24 -25
  151. wagtail/contrib/settings/jinja2tags.py +2 -2
  152. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -2
  153. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.mo +0 -0
  154. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.po +6 -2
  155. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.mo +0 -0
  156. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.po +3 -2
  157. wagtail/contrib/settings/tests/generic/test_admin.py +118 -2
  158. wagtail/contrib/settings/tests/site_specific/test_admin.py +78 -15
  159. wagtail/contrib/settings/tests/site_specific/test_model.py +2 -2
  160. wagtail/contrib/settings/views.py +7 -0
  161. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  162. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.mo +0 -0
  163. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.po +5 -2
  164. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  165. wagtail/contrib/styleguide/tests.py +2 -0
  166. wagtail/contrib/styleguide/views.py +4 -5
  167. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.mo +0 -0
  168. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.po +5 -1
  169. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  170. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
  171. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +5 -2
  172. wagtail/contrib/typed_table_block/blocks.py +37 -0
  173. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  174. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.mo +0 -0
  175. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.po +4 -3
  176. wagtail/contrib/typed_table_block/tests.py +108 -0
  177. wagtail/coreutils.py +4 -4
  178. wagtail/documents/__init__.py +4 -4
  179. wagtail/documents/locale/en/LC_MESSAGES/django.po +15 -15
  180. wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
  181. wagtail/documents/locale/it/LC_MESSAGES/django.po +32 -2
  182. wagtail/documents/locale/sv/LC_MESSAGES/django.mo +0 -0
  183. wagtail/documents/locale/sv/LC_MESSAGES/django.po +32 -4
  184. wagtail/documents/models.py +1 -1
  185. wagtail/documents/tests/test_admin_views.py +19 -4
  186. wagtail/documents/tests/test_search.py +0 -2
  187. wagtail/documents/tests/test_views.py +6 -4
  188. wagtail/documents/views/bulk_actions/add_tags.py +1 -1
  189. wagtail/embeds/finders/facebook.py +3 -3
  190. wagtail/embeds/finders/instagram.py +3 -3
  191. wagtail/embeds/finders/oembed.py +7 -2
  192. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  193. wagtail/embeds/oembed_providers.py +8 -0
  194. wagtail/embeds/tests/test_embeds.py +35 -0
  195. wagtail/images/__init__.py +4 -4
  196. wagtail/images/admin_urls.py +3 -3
  197. wagtail/images/blocks.py +1 -1
  198. wagtail/images/formats.py +2 -2
  199. wagtail/images/image_operations.py +2 -2
  200. wagtail/images/locale/en/LC_MESSAGES/django.po +44 -40
  201. wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
  202. wagtail/images/locale/it/LC_MESSAGES/django.po +50 -4
  203. wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
  204. wagtail/images/locale/nl/LC_MESSAGES/django.po +3 -3
  205. wagtail/images/locale/sv/LC_MESSAGES/django.mo +0 -0
  206. wagtail/images/locale/sv/LC_MESSAGES/django.po +49 -6
  207. wagtail/images/models.py +11 -10
  208. wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
  209. wagtail/images/templates/wagtailimages/images/url_generator.html +17 -38
  210. wagtail/images/templates/wagtailimages/images/url_generator_output.html +28 -0
  211. wagtail/images/templatetags/wagtailimages_tags.py +4 -4
  212. wagtail/images/tests/test_admin_views.py +432 -59
  213. wagtail/images/tests/test_image_operations.py +2 -2
  214. wagtail/images/tests/test_models.py +0 -2
  215. wagtail/images/views/bulk_actions/add_tags.py +1 -1
  216. wagtail/images/views/images.py +72 -39
  217. wagtail/locale/en/LC_MESSAGES/django.po +103 -105
  218. wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
  219. wagtail/locale/it/LC_MESSAGES/django.po +105 -2
  220. wagtail/locale/sv/LC_MESSAGES/django.mo +0 -0
  221. wagtail/locale/sv/LC_MESSAGES/django.po +95 -12
  222. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  223. wagtail/locales/locale/it/LC_MESSAGES/django.mo +0 -0
  224. wagtail/locales/locale/it/LC_MESSAGES/django.po +13 -2
  225. wagtail/locales/locale/sv/LC_MESSAGES/django.mo +0 -0
  226. wagtail/locales/locale/sv/LC_MESSAGES/django.po +4 -3
  227. wagtail/locales/tests.py +5 -5
  228. wagtail/models/i18n.py +4 -6
  229. wagtail/models/media.py +18 -0
  230. wagtail/models/pages.py +65 -11
  231. wagtail/models/reference_index.py +15 -0
  232. wagtail/models/revisions.py +40 -12
  233. wagtail/models/workflows.py +0 -3
  234. wagtail/permission_policies/base.py +2 -2
  235. wagtail/permission_policies/collections.py +2 -2
  236. wagtail/project_template/requirements.txt +2 -2
  237. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  238. wagtail/signal_handlers.py +1 -0
  239. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  240. wagtail/sites/locale/sv/LC_MESSAGES/django.mo +0 -0
  241. wagtail/sites/locale/sv/LC_MESSAGES/django.po +3 -2
  242. wagtail/sites/tests.py +7 -7
  243. wagtail/snippets/action_menu.py +2 -1
  244. wagtail/snippets/locale/en/LC_MESSAGES/django.po +23 -13
  245. wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
  246. wagtail/snippets/locale/sv/LC_MESSAGES/django.po +12 -1
  247. wagtail/snippets/tests/test_chooser_block.py +197 -0
  248. wagtail/snippets/tests/test_chooser_panel.py +149 -0
  249. wagtail/snippets/tests/test_chooser_views.py +375 -0
  250. wagtail/snippets/tests/test_chooser_widget.py +22 -0
  251. wagtail/snippets/tests/test_compare_revisions_view.py +167 -0
  252. wagtail/snippets/tests/test_copy_view.py +38 -0
  253. wagtail/snippets/tests/test_create_view.py +1159 -0
  254. wagtail/snippets/tests/test_delete_view.py +225 -0
  255. wagtail/snippets/tests/test_edit_view.py +2882 -0
  256. wagtail/snippets/tests/test_history_view.py +182 -0
  257. wagtail/snippets/tests/test_index_view.py +105 -0
  258. wagtail/snippets/tests/test_list_view.py +786 -0
  259. wagtail/snippets/tests/test_locking.py +28 -0
  260. wagtail/snippets/tests/test_permissions.py +167 -0
  261. wagtail/snippets/tests/test_preview.py +3 -3
  262. wagtail/snippets/tests/test_revert_view.py +343 -0
  263. wagtail/snippets/tests/test_snippet_models.py +112 -0
  264. wagtail/snippets/tests/test_unpublish_view.py +228 -0
  265. wagtail/snippets/tests/test_unschedule_view.py +151 -0
  266. wagtail/snippets/tests/test_viewset.py +38 -5
  267. wagtail/snippets/views/snippets.py +78 -30
  268. wagtail/snippets/widgets.py +2 -2
  269. wagtail/templatetags/wagtailcore_tags.py +2 -2
  270. wagtail/test/dummy_external_storage.py +1 -1
  271. wagtail/test/testapp/fixtures/test.json +22 -0
  272. wagtail/test/testapp/fixtures/test_empty.json +2 -0
  273. wagtail/test/testapp/fixtures/test_explorable_pages.json +10 -0
  274. wagtail/test/testapp/fixtures/test_specific.json +9 -0
  275. wagtail/test/testapp/migrations/0059_nopromotepage.py +25 -0
  276. wagtail/test/testapp/models.py +7 -0
  277. wagtail/test/testapp/views.py +5 -0
  278. wagtail/test/utils/page_tests.py +7 -7
  279. wagtail/test/utils/wagtail_factories/builder.py +2 -2
  280. wagtail/tests/streamfield_migrations/test_migrations.py +35 -0
  281. wagtail/tests/test_blocks.py +321 -61
  282. wagtail/tests/test_collection_model.py +12 -0
  283. wagtail/tests/test_page_model.py +190 -1
  284. wagtail/tests/test_page_privacy.py +5 -0
  285. wagtail/tests/test_reference_index.py +42 -0
  286. wagtail/tests/test_revision_model.py +118 -1
  287. wagtail/tests/test_utils.py +2 -2
  288. wagtail/users/locale/en/LC_MESSAGES/django.po +14 -14
  289. wagtail/users/locale/id_ID/LC_MESSAGES/django.mo +0 -0
  290. wagtail/users/locale/id_ID/LC_MESSAGES/django.po +6 -2
  291. wagtail/users/locale/it/LC_MESSAGES/django.mo +0 -0
  292. wagtail/users/locale/it/LC_MESSAGES/django.po +14 -2
  293. wagtail/users/locale/sv/LC_MESSAGES/django.mo +0 -0
  294. wagtail/users/locale/sv/LC_MESSAGES/django.po +48 -17
  295. wagtail/users/tests/test_admin_views.py +39 -25
  296. wagtail/users/utils.py +4 -1
  297. wagtail/users/views/groups.py +19 -5
  298. wagtail/users/wagtail_hooks.py +1 -1
  299. wagtail/utils/loading.py +2 -2
  300. wagtail-7.3rc1.data/data/AGENTS.md +21 -0
  301. wagtail-7.3rc1.data/data/CHANGELOG.txt +4941 -0
  302. wagtail-7.3rc1.data/data/CODE_OF_CONDUCT.md +71 -0
  303. wagtail-7.3rc1.data/data/CONTRIBUTORS.md +999 -0
  304. wagtail-7.3rc1.data/data/SPONSORS.md +55 -0
  305. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/METADATA +6 -6
  306. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/RECORD +310 -280
  307. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/WHEEL +1 -1
  308. wagtail/images/static/wagtailimages/js/image-url-generator.js +0 -1
  309. wagtail/snippets/tests/test_snippets.py +0 -6377
  310. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/entry_points.txt +0 -0
  311. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/licenses/LICENSE +0 -0
  312. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/top_level.txt +0 -0
wagtail/models/pages.py CHANGED
@@ -12,6 +12,7 @@ from django.contrib.contenttypes.models import ContentType
12
12
  from django.core import checks
13
13
  from django.core.exceptions import (
14
14
  FieldDoesNotExist,
15
+ PermissionDenied,
15
16
  ValidationError,
16
17
  )
17
18
  from django.db import models, transaction
@@ -22,11 +23,13 @@ from django.dispatch import receiver
22
23
  from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotAllowed
23
24
  from django.template.response import TemplateResponse
24
25
  from django.urls import NoReverseMatch, reverse
26
+ from django.utils import timezone
25
27
  from django.utils import translation as translation
26
28
  from django.utils.encoding import force_bytes, force_str
27
29
  from django.utils.functional import Promise, cached_property
28
30
  from django.utils.log import log_response
29
31
  from django.utils.text import capfirst, slugify
32
+ from django.utils.translation import gettext
30
33
  from django.utils.translation import gettext_lazy as _
31
34
  from modelcluster.fields import ParentalKey
32
35
  from modelcluster.models import (
@@ -139,6 +142,10 @@ class BasePageManager(models.Manager):
139
142
  def get_queryset(self):
140
143
  return self._queryset_class(self.model).order_by("path")
141
144
 
145
+ def get_by_natural_key(self, url_path):
146
+ """Get page by URL"""
147
+ return self.get(url_path=url_path)
148
+
142
149
  def first_common_ancestor_of(self, pages, include_self=False, strict=False):
143
150
  """
144
151
  This is similar to ``PageQuerySet.first_common_ancestor`` but works
@@ -637,7 +644,14 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
637
644
  Set default values for core fields (slug, draft_title, locale) that need to be
638
645
  in place before validating or saving
639
646
  """
640
- if not self.slug:
647
+
648
+ # If path is unset, then this cleaning step must be happening in advance of the
649
+ # final save, and there is no point trying to auto-generate a slug yet, as we
650
+ # don't know the set of sibling pages to de-duplicate against. In this case,
651
+ # this method will be called again as part of the call to `save()` after the
652
+ # path is populated, and we will auto-generate the slug at that point if
653
+ # necessary.
654
+ if (not self.slug) and self.path:
641
655
  # Try to auto-populate slug from title
642
656
  allow_unicode = getattr(settings, "WAGTAIL_ALLOW_UNICODE_SLUGS", True)
643
657
  base_slug = slugify(self.title, allow_unicode=allow_unicode)
@@ -903,8 +917,8 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
903
917
  # And update = False
904
918
  subpage._cached_parent_obj = self
905
919
 
906
- except Page.DoesNotExist:
907
- raise Http404
920
+ except Page.DoesNotExist as e:
921
+ raise Http404 from e
908
922
 
909
923
  return subpage.specific.route(request, remaining_components)
910
924
 
@@ -925,6 +939,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
925
939
  # in a fixture or migration that didn't explicitly handle draft_title)
926
940
  return self.draft_title or self.title
927
941
 
942
+ @transaction.atomic
928
943
  def save_revision(
929
944
  self,
930
945
  user=None,
@@ -933,6 +948,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
933
948
  log_action=False,
934
949
  previous_revision=None,
935
950
  clean=True,
951
+ overwrite_revision=None,
936
952
  ):
937
953
  # Raise error if this is not the specific version of the page
938
954
  if not isinstance(self, self.specific_class):
@@ -956,14 +972,42 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
956
972
  # We need to ensure comments have an id in the revision, so positions can be identified correctly
957
973
  comment.save()
958
974
 
959
- revision = Revision.objects.create(
960
- content_object=self,
961
- base_content_type=self.get_base_content_type(),
962
- user=user,
963
- approved_go_live_at=approved_go_live_at,
964
- content=self.serializable_data(),
965
- object_str=str(self),
966
- )
975
+ if overwrite_revision:
976
+ # the revision being overwritten must be the latest revision for the current page, and must match
977
+ # the current user (if any)
978
+ latest_revision = self.get_latest_revision()
979
+ if overwrite_revision != latest_revision:
980
+ raise PermissionDenied(
981
+ gettext(
982
+ "Cannot overwrite a revision that is not the latest for "
983
+ "this %(model_name)s."
984
+ )
985
+ % {"model_name": Page._meta.verbose_name}
986
+ )
987
+
988
+ if overwrite_revision.user_id != (user and user.pk):
989
+ raise PermissionDenied(
990
+ gettext(
991
+ "Cannot overwrite a revision that was not created "
992
+ "by the current user."
993
+ )
994
+ )
995
+
996
+ overwrite_revision.created_at = timezone.now()
997
+ overwrite_revision.content = self.serializable_data()
998
+ overwrite_revision.approved_go_live_at = approved_go_live_at
999
+ overwrite_revision.object_str = str(self)
1000
+ overwrite_revision.save()
1001
+ revision = overwrite_revision
1002
+ else:
1003
+ revision = Revision.objects.create(
1004
+ content_object=self,
1005
+ base_content_type=self.get_base_content_type(),
1006
+ user=user,
1007
+ approved_go_live_at=approved_go_live_at,
1008
+ content=self.serializable_data(),
1009
+ object_str=str(self),
1010
+ )
967
1011
 
968
1012
  for comment in new_comments:
969
1013
  comment.revision_created = revision
@@ -1893,6 +1937,12 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
1893
1937
  "wagtailcore/password_required.html",
1894
1938
  )
1895
1939
 
1940
+ # Ensuring preview attributes exist
1941
+ if not hasattr(request, "is_preview"):
1942
+ request.is_preview = False
1943
+ if not hasattr(request, "preview_mode"):
1944
+ request.preview_mode = None
1945
+
1896
1946
  context = self.get_context(request)
1897
1947
  context["form"] = form
1898
1948
  context["action_url"] = action_url
@@ -2028,6 +2078,10 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
2028
2078
  workflow = None
2029
2079
  return workflow
2030
2080
 
2081
+ def natural_key(self):
2082
+ """Return the URL path as the natural key"""
2083
+ return (self.url_path,)
2084
+
2031
2085
  class Meta:
2032
2086
  verbose_name = _("page")
2033
2087
  verbose_name_plural = _("pages")
@@ -564,6 +564,21 @@ class ReferenceIndex(models.Model):
564
564
  base_content_type=base_content_type, object_id=object.pk
565
565
  ).delete()
566
566
 
567
+ @classmethod
568
+ def remove_references_to(cls, object):
569
+ """
570
+ Deletes all inbound references to the given object.
571
+
572
+ Use this when deleting the object itself to prevent orphaned references.
573
+
574
+ Args:
575
+ object (Model): The model instance to delete ReferenceIndex records for (as a target)
576
+ """
577
+ base_content_type = cls._get_base_content_type(object)
578
+ cls.objects.filter(
579
+ to_content_type=base_content_type, to_object_id=object.pk
580
+ ).delete()
581
+
567
582
  @classmethod
568
583
  def get_references_for_object(cls, object):
569
584
  """
@@ -3,12 +3,14 @@ import logging
3
3
  from django.conf import settings
4
4
  from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
5
5
  from django.contrib.contenttypes.models import ContentType
6
+ from django.core.exceptions import PermissionDenied
6
7
  from django.core.serializers.json import DjangoJSONEncoder
7
- from django.db import models
8
+ from django.db import models, transaction
8
9
  from django.db.models import Q
9
10
  from django.db.models.expressions import OuterRef, Subquery
10
11
  from django.utils import timezone
11
12
  from django.utils.functional import cached_property
13
+ from django.utils.translation import gettext
12
14
  from django.utils.translation import gettext_lazy as _
13
15
  from modelcluster.models import (
14
16
  get_serializable_data_for_fields,
@@ -275,9 +277,6 @@ class RevisionMixin(models.Model):
275
277
  so it cannot be used for reverse-related queries from ``Revision`` back to
276
278
  this model. If the feature is desired, subclasses can define their own
277
279
  ``GenericRelation`` to ``Revision`` with a custom ``related_query_name``.
278
-
279
- .. versionadded:: 7.1
280
- The default ``GenericRelation`` :attr:`~wagtail.models.RevisionMixin._revisions` was added.
281
280
  """
282
281
 
283
282
  # An array of additional field names that will not be included when the object is copied.
@@ -378,6 +377,7 @@ class RevisionMixin(models.Model):
378
377
  self.latest_revision = revision
379
378
  self.save(update_fields=["latest_revision"])
380
379
 
380
+ @transaction.atomic
381
381
  def save_revision(
382
382
  self,
383
383
  user=None,
@@ -386,6 +386,7 @@ class RevisionMixin(models.Model):
386
386
  log_action=False,
387
387
  previous_revision=None,
388
388
  clean=True,
389
+ overwrite_revision=None,
389
390
  ):
390
391
  """
391
392
  Creates and saves a revision.
@@ -403,14 +404,41 @@ class RevisionMixin(models.Model):
403
404
  if clean:
404
405
  self.full_clean()
405
406
 
406
- revision = Revision.objects.create(
407
- content_object=self,
408
- base_content_type=self.get_base_content_type(),
409
- user=user,
410
- approved_go_live_at=approved_go_live_at,
411
- content=self.serializable_data(),
412
- object_str=str(self),
413
- )
407
+ if overwrite_revision:
408
+ # the revision being overwritten must be the latest revision for the current instance, and must match
409
+ # the current user (if any)
410
+ latest_revision = self.get_latest_revision()
411
+ if overwrite_revision != latest_revision:
412
+ raise PermissionDenied(
413
+ gettext(
414
+ "Cannot overwrite a revision that is not the latest for "
415
+ "this %(model_name)s."
416
+ )
417
+ % {"model_name": self._meta.verbose_name}
418
+ )
419
+ if overwrite_revision.user_id != (user and user.pk):
420
+ raise PermissionDenied(
421
+ gettext(
422
+ "Cannot overwrite a revision that was not created "
423
+ "by the current user."
424
+ )
425
+ )
426
+
427
+ overwrite_revision.created_at = timezone.now()
428
+ overwrite_revision.content = self.serializable_data()
429
+ overwrite_revision.approved_go_live_at = approved_go_live_at
430
+ overwrite_revision.object_str = str(self)
431
+ overwrite_revision.save()
432
+ revision = overwrite_revision
433
+ else:
434
+ revision = Revision.objects.create(
435
+ content_object=self,
436
+ base_content_type=self.get_base_content_type(),
437
+ user=user,
438
+ approved_go_live_at=approved_go_live_at,
439
+ content=self.serializable_data(),
440
+ object_str=str(self),
441
+ )
414
442
 
415
443
  self._update_from_revision(revision, changed)
416
444
 
@@ -1181,9 +1181,6 @@ class WorkflowMixin(models.Model):
1181
1181
  to this model. If the feature is desired, subclasses can define their own
1182
1182
  ``GenericRelation`` to ``WorkflowState`` with a custom
1183
1183
  ``related_query_name``.
1184
-
1185
- .. versionadded:: 7.1
1186
- The default ``GenericRelation`` :attr:`~wagtail.models.WorkflowMixin._workflow_states` was added.
1187
1184
  """
1188
1185
 
1189
1186
  class Meta:
@@ -327,12 +327,12 @@ class OwnershipPermissionPolicy(BaseDjangoAuthPermissionPolicy):
327
327
  # make sure owner_field_name is a field that exists on the model
328
328
  try:
329
329
  model._meta.get_field(self.owner_field_name)
330
- except FieldDoesNotExist:
330
+ except FieldDoesNotExist as e:
331
331
  raise ImproperlyConfigured(
332
332
  "%s has no field named '%s'. To use this model with OwnershipPermissionPolicy, "
333
333
  "you must specify a valid field name as owner_field_name."
334
334
  % (model, self.owner_field_name)
335
- )
335
+ ) from e
336
336
 
337
337
  def user_has_permission(self, user, action):
338
338
  if action == "add":
@@ -225,12 +225,12 @@ class CollectionOwnershipPermissionPolicy(
225
225
  # make sure owner_field_name is a field that exists on the model
226
226
  try:
227
227
  model._meta.get_field(self.owner_field_name)
228
- except FieldDoesNotExist:
228
+ except FieldDoesNotExist as e:
229
229
  raise ImproperlyConfigured(
230
230
  "%s has no field named '%s'. To use this model with "
231
231
  "CollectionOwnershipPermissionPolicy, you must specify a valid field name as "
232
232
  "owner_field_name." % (model, self.owner_field_name)
233
- )
233
+ ) from e
234
234
 
235
235
  def user_has_permission(self, user, action):
236
236
  if action == "add":
@@ -1,2 +1,2 @@
1
- Django>=5.2,<5.3
2
- wagtail>=7.2,<7.3
1
+ Django>=6,<6.1
2
+ wagtail==7.3rc1
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2025-10-23 16:45+0100\n"
11
+ "POT-Creation-Date: 2026-01-21 18:00+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -82,6 +82,7 @@ def remove_reference_index_on_delete(instance, **kwargs):
82
82
 
83
83
  with transaction.atomic():
84
84
  ReferenceIndex.remove_for_object(instance)
85
+ ReferenceIndex.remove_references_to(instance)
85
86
 
86
87
 
87
88
  def connect_reference_index_signal_handlers_for_model(model):
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2025-10-23 16:45+0100\n"
11
+ "POT-Creation-Date: 2026-01-21 18:00+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -3,6 +3,7 @@
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
4
  #
5
5
  # Translators:
6
+ # Jonathan Lindén, 2025
6
7
  # Ludwig Kjellström <ludwig.kjellstrom@amnesty.se>, 2015
7
8
  # Martin Sandström <martin@marteinn.se>, 2021
8
9
  # Philip Andersen <renegadevi@codeofmagi.net>, 2018
@@ -13,7 +14,7 @@ msgstr ""
13
14
  "Report-Msgid-Bugs-To: \n"
14
15
  "POT-Creation-Date: 2025-10-23 16:45+0100\n"
15
16
  "PO-Revision-Date: 2014-10-03 15:24+0000\n"
16
- "Last-Translator: Thomas Kunambi <kunambi@gmail.com>, 2017\n"
17
+ "Last-Translator: Jonathan Lindén, 2025\n"
17
18
  "Language-Team: Swedish (http://app.transifex.com/torchbox/wagtail/language/"
18
19
  "sv/)\n"
19
20
  "MIME-Version: 1.0\n"
@@ -54,7 +55,7 @@ msgid "Site '%(object)s' created."
54
55
  msgstr "Sajt '%(object)s' skapad."
55
56
 
56
57
  msgid "The site could not be saved due to errors."
57
- msgstr "Sajten kunde inte sparas pga fel."
58
+ msgstr "Sajten kunde inte sparas grund av fel."
58
59
 
59
60
  #, python-format
60
61
  msgid "Site '%(object)s' updated."
wagtail/sites/tests.py CHANGED
@@ -15,7 +15,7 @@ class TestSiteIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
15
15
  self.login()
16
16
  self.home_page = Page.objects.get(id=2)
17
17
 
18
- def get(self, params={}):
18
+ def get(self, params=None):
19
19
  return self.client.get(reverse("wagtailsites:index"), params)
20
20
 
21
21
  def test_simple(self):
@@ -49,10 +49,10 @@ class TestSiteCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
49
49
  self.home_page = Page.objects.get(id=2)
50
50
  self.localhost = Site.objects.all()[0]
51
51
 
52
- def get(self, params={}):
52
+ def get(self, params=None):
53
53
  return self.client.get(reverse("wagtailsites:add"), params)
54
54
 
55
- def post(self, post_data={}):
55
+ def post(self, post_data=None):
56
56
  return self.client.post(reverse("wagtailsites:add"), post_data)
57
57
 
58
58
  def create_site(
@@ -197,12 +197,12 @@ class TestSiteEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
197
197
  self.home_page = Page.objects.get(id=2)
198
198
  self.localhost = Site.objects.all()[0]
199
199
 
200
- def get(self, params={}, site_id=None):
200
+ def get(self, params=None, site_id=None):
201
201
  return self.client.get(
202
202
  reverse("wagtailsites:edit", args=(site_id or self.localhost.id,)), params
203
203
  )
204
204
 
205
- def post(self, post_data={}, site_id=None):
205
+ def post(self, post_data=None, site_id=None):
206
206
  site_id = site_id or self.localhost.id
207
207
  site = Site.objects.get(id=site_id)
208
208
  post_defaults = {
@@ -370,12 +370,12 @@ class TestSiteDeleteView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
370
370
  self.home_page = Page.objects.get(id=2)
371
371
  self.localhost = Site.objects.all()[0]
372
372
 
373
- def get(self, params={}, site_id=None):
373
+ def get(self, params=None, site_id=None):
374
374
  return self.client.get(
375
375
  reverse("wagtailsites:delete", args=(site_id or self.localhost.id,)), params
376
376
  )
377
377
 
378
- def post(self, post_data={}, site_id=None):
378
+ def post(self, post_data=None, site_id=None):
379
379
  return self.client.post(
380
380
  reverse("wagtailsites:delete", args=(site_id or self.localhost.id,)),
381
381
  post_data,
@@ -7,6 +7,7 @@ from django.contrib.admin.utils import quote
7
7
  from django.forms import Media
8
8
  from django.template.loader import render_to_string
9
9
  from django.urls import reverse
10
+ from django.utils import timezone
10
11
  from django.utils.functional import cached_property
11
12
  from django.utils.translation import gettext_lazy as _
12
13
 
@@ -54,8 +55,8 @@ class ActionMenuItem(Component):
54
55
  parent_context.get("draftstate_enabled")
55
56
  and instance
56
57
  and instance.go_live_at
58
+ and instance.go_live_at > timezone.now()
57
59
  )
58
-
59
60
  context.update(
60
61
  {
61
62
  "label": self.label,
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2025-10-23 16:45+0100\n"
11
+ "POT-Creation-Date: 2026-01-21 18:00+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,48 +18,48 @@ msgstr ""
18
18
  "Content-Transfer-Encoding: 8bit\n"
19
19
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
20
 
21
- #: action_menu.py:79
21
+ #: action_menu.py:80
22
22
  #: templates/wagtailsnippets/snippets/action_menu/publish.html:13
23
23
  msgid "Publish"
24
24
  msgstr ""
25
25
 
26
- #: action_menu.py:92
26
+ #: action_menu.py:93
27
27
  msgid "Submit for moderation"
28
28
  msgstr ""
29
29
 
30
- #: action_menu.py:120
30
+ #: action_menu.py:121
31
31
  #, python-format
32
32
  msgid "Resubmit to %(task_name)s"
33
33
  msgstr ""
34
34
 
35
- #: action_menu.py:130
35
+ #: action_menu.py:131
36
36
  #, python-format
37
37
  msgid "Submit to %(workflow_name)s"
38
38
  msgstr ""
39
39
 
40
- #: action_menu.py:171
40
+ #: action_menu.py:172
41
41
  msgid "Restart workflow "
42
42
  msgstr ""
43
43
 
44
- #: action_menu.py:192
44
+ #: action_menu.py:193
45
45
  msgid "Cancel workflow "
46
46
  msgstr ""
47
47
 
48
- #: action_menu.py:206
48
+ #: action_menu.py:207
49
49
  msgid "Unpublish"
50
50
  msgstr ""
51
51
 
52
- #: action_menu.py:226
52
+ #: action_menu.py:227
53
53
  #: templates/wagtailsnippets/snippets/action_menu/save.html:27
54
54
  msgid "Save"
55
55
  msgstr ""
56
56
 
57
- #: action_menu.py:233
57
+ #: action_menu.py:234
58
58
  #: templates/wagtailsnippets/snippets/action_menu/locked.html:4
59
59
  msgid "Locked"
60
60
  msgstr ""
61
61
 
62
- #: action_menu.py:309
62
+ #: action_menu.py:310
63
63
  #, python-format
64
64
  msgid "%(label)s and Publish"
65
65
  msgstr ""
@@ -178,7 +178,7 @@ msgstr ""
178
178
  msgid "Choose"
179
179
  msgstr ""
180
180
 
181
- #: views/snippets.py:87 views/snippets.py:875 wagtail_hooks.py:38
181
+ #: views/snippets.py:87 views/snippets.py:922 wagtail_hooks.py:38
182
182
  msgid "Snippets"
183
183
  msgstr ""
184
184
 
@@ -195,7 +195,17 @@ msgstr ""
195
195
  msgid "More options for '%(title)s'"
196
196
  msgstr ""
197
197
 
198
- #: views/snippets.py:867
198
+ #: views/snippets.py:244
199
+ #, python-format
200
+ msgid "Request to create %(model_name)s was blocked by hook."
201
+ msgstr ""
202
+
203
+ #: views/snippets.py:316
204
+ #, python-format
205
+ msgid "Request to edit %(model_name)s was blocked by hook."
206
+ msgstr ""
207
+
208
+ #: views/snippets.py:914
199
209
  msgid "Home"
200
210
  msgstr ""
201
211
 
@@ -5,6 +5,7 @@
5
5
  # Translators:
6
6
  # Alexander Holmbäck <alexander.holmback@creuna.se>, 2017
7
7
  # Jim Brouzoulis <jim.brouzoulis@chalmers.se>, 2017
8
+ # Jonathan Lindén, 2025
8
9
  # Martin Sandström <martin@marteinn.se>, 2020-2021
9
10
  # Matt Westcott <matthew@torchbox.com>, 2024
10
11
  # Philip Andersen <renegadevi@codeofmagi.net>, 2018-2019
@@ -17,7 +18,7 @@ msgstr ""
17
18
  "Report-Msgid-Bugs-To: \n"
18
19
  "POT-Creation-Date: 2025-10-23 16:45+0100\n"
19
20
  "PO-Revision-Date: 2014-02-19 19:01+0000\n"
20
- "Last-Translator: Tomas Walch <tomas@alternaliv.se>, 2022\n"
21
+ "Last-Translator: Jonathan Lindén, 2025\n"
21
22
  "Language-Team: Swedish (http://app.transifex.com/torchbox/wagtail/language/"
22
23
  "sv/)\n"
23
24
  "MIME-Version: 1.0\n"
@@ -121,9 +122,15 @@ msgstr ""
121
122
  "href=\"%(wagtailsnippets_create_snippet_url)s\" target=\"_blank\" "
122
123
  "rel=\"noreferrer\">skapa en nu</a>?"
123
124
 
125
+ msgid "Scheduling…"
126
+ msgstr "Schemalägger…"
127
+
124
128
  msgid "Publishing…"
125
129
  msgstr "Publicerar..."
126
130
 
131
+ msgid "Schedule to publish"
132
+ msgstr "Schemalägg publicering"
133
+
127
134
  msgid "Publish this version"
128
135
  msgstr "Publicera denna version"
129
136
 
@@ -154,6 +161,10 @@ msgstr "Namn"
154
161
  msgid "Instances"
155
162
  msgstr "Instans"
156
163
 
164
+ #, python-format
165
+ msgid "More options for '%(title)s'"
166
+ msgstr "Fler alternativ för '%(title)s'"
167
+
157
168
  msgid "Home"
158
169
  msgstr "Hem"
159
170