diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 58877b59a2b..dafce931ec1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: django-5.1.txt, ] os: [ - ubuntu-20.04, + ubuntu-latest, ] exclude: - requirements-file: django-5.0.txt @@ -225,7 +225,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:17 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py index 306990fc014..20c4b7a52e1 100644 --- a/cms/admin/pageadmin.py +++ b/cms/admin/pageadmin.py @@ -813,18 +813,6 @@ def log_change(self, request, object, message): # Block the admin log for change. A signal takes care of this! return - def get_object(self, request, object_id, from_field=None): - """ - Return an instance matching the field and value provided, the primary - key is used if no field is provided. Return ``None`` if no match is - found or the object_id fails validation. - """ - obj = super().get_object(request, object_id, from_field) - - if obj: - obj.page.admin_content_cache[obj.language] = obj - return obj - def get_admin_url(self, action, *args): url_name = f"{self.opts.app_label}_{self.opts.model_name}_{action}" return admin_reverse(url_name, args=args) @@ -1402,7 +1390,7 @@ def get_tree(self, request): Prefetch( 'pagecontent_set', to_attr='filtered_translations', - queryset=PageContent.admin_manager.get_queryset(), + queryset=PageContent.admin_manager.get_queryset().latest_content(), ), ) rows = self.get_tree_rows( diff --git a/cms/cms_menus.py b/cms/cms_menus.py index 2271d3692ba..ed259dae384 100644 --- a/cms/cms_menus.py +++ b/cms/cms_menus.py @@ -140,7 +140,7 @@ def get_menu_node_for_page(renderer, page, language, fallbacks=None, endpoint=Fa for lang in [language] + fallbacks: translation = page.page_content_cache.get(lang) - if translation: + if translation and lang in page.urls_cache: page_url = page.urls_cache[lang] # Do we have a redirectURL? attr["redirect_url"] = translation.redirect # save redirect URL if any diff --git a/cms/models/managers.py b/cms/models/managers.py index a2cfb02a91d..9d7f8484f7e 100644 --- a/cms/models/managers.py +++ b/cms/models/managers.py @@ -310,7 +310,7 @@ def subordinate_to_user(self, user, site): from cms.models import PermissionTuple allow_list = Q() for perm_tuple in get_change_permissions_perm_tuples(user, site, check_global=False): - allow_list |= PermissionTuple(perm_tuple).allow_list("page__node") + allow_list |= PermissionTuple(perm_tuple).allow_list("page__node") # get permission set, but without objects targeting user, or any group # in which he can be diff --git a/cms/models/pagemodel.py b/cms/models/pagemodel.py index 557fc8eebb3..54371ee9f0d 100644 --- a/cms/models/pagemodel.py +++ b/cms/models/pagemodel.py @@ -21,7 +21,7 @@ from cms.utils import i18n from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning from cms.utils.conf import get_cms_setting -from cms.utils.i18n import get_current_language, get_fallback_languages +from cms.utils.i18n import get_current_language from cms.utils.page import get_clean_username from menus.menu_pool import menu_pool diff --git a/cms/models/permissionmodels.py b/cms/models/permissionmodels.py index daca7078d50..7dc5ca5608a 100644 --- a/cms/models/permissionmodels.py +++ b/cms/models/permissionmodels.py @@ -231,7 +231,7 @@ def contains(self, path: str, steplen: int = TreeNode.steplen) -> bool: return False def allow_list(self, filter: str = "", steplen: int = TreeNode.steplen) -> Q: - if filter !="": + if filter != "": filter = f"{filter}__" grant_on, path = self if grant_on == ACCESS_PAGE: diff --git a/cms/plugin_rendering.py b/cms/plugin_rendering.py index 4f14e0b43d2..30e0b96b9a4 100644 --- a/cms/plugin_rendering.py +++ b/cms/plugin_rendering.py @@ -557,7 +557,6 @@ def _get_cached_placeholder_content(self, placeholder, language): language_cache[placeholder.pk] = cached_value return language_cache.get(placeholder.pk) - def _get_content_object(self, page, slots=None): if self.toolbar.get_object() == page: # Current object belongs to the page itself diff --git a/cms/templates/cms/welcome.html b/cms/templates/cms/welcome.html index 2167f2b046a..f5c6001b594 100644 --- a/cms/templates/cms/welcome.html +++ b/cms/templates/cms/welcome.html @@ -40,7 +40,7 @@

- {% blocktranslate %} + {% blocktrans %}

Help Funding

Your funding directly benefits the product, mainly through the @@ -50,10 +50,10 @@

Help Funding

A quick way for yourself or your organisation to donate is on Github Sponsors.

- {% endblocktranslate %} + {% endblocktrans %}
- {% blocktranslate %} + {% blocktrans %}

Join Us

The django CMS Association is a non-profit organisation that funds and steers the development of @@ -63,10 +63,10 @@

Join Us

You can join both as an individual or as an organisation.

- {% endblocktranslate %} + {% endblocktrans %}
- {% blocktranslate %} + {% blocktrans %}

Contribute

- {% endblocktranslate %} + {% endblocktrans %}
diff --git a/cms/templatetags/cms_admin.py b/cms/templatetags/cms_admin.py index 65d5485a953..0cd2f0fc2d8 100644 --- a/cms/templatetags/cms_admin.py +++ b/cms/templatetags/cms_admin.py @@ -202,6 +202,7 @@ def boolean_icon(value): mapped_icon, ) + @register.tag(name="page_submit_row") class PageSubmitRow(InclusionTag): name = 'page_submit_row' diff --git a/cms/tests/test_admin.py b/cms/tests/test_admin.py index bf6a61f3405..83a3d39533f 100644 --- a/cms/tests/test_admin.py +++ b/cms/tests/test_admin.py @@ -800,6 +800,7 @@ def test_smart_link_pages(self): )) ) + class PagePropsMovedToPageContentTests(CMSTestCase): def test_moved_fields(self): @@ -823,6 +824,7 @@ def test_moved_fields(self): for field in filtered_page_content_fields: self.assertIn(field, change_page_form_fieldsets) + class AdminPageEditContentSizeTests(AdminTestsBase): """ System user count influences the size of the page edit page, diff --git a/cms/tests/test_extensions.py b/cms/tests/test_extensions.py index 619f281b5c5..22ff8bdd279 100644 --- a/cms/tests/test_extensions.py +++ b/cms/tests/test_extensions.py @@ -16,7 +16,7 @@ ) from cms.test_utils.testcases import CMSTestCase from cms.toolbar_pool import toolbar_pool -from cms.utils.compat.warnings import RemovedInDjangoCMS42Warning, RemovedInDjangoCMS43Warning +from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning from cms.utils.urlutils import admin_reverse @@ -427,6 +427,7 @@ def test_deprecated_title_extension(self): class SampleExtensionToolbar2(ExtensionToolbar): model = MyPageContentExtension + def populate(self): nonlocal urls urls = self.get_title_extension_admin() diff --git a/cms/tests/test_i18n.py b/cms/tests/test_i18n.py index 05af7e9fb56..10ac997ccbf 100644 --- a/cms/tests/test_i18n.py +++ b/cms/tests/test_i18n.py @@ -1,4 +1,3 @@ -from collections import deque from importlib import import_module from unittest.mock import patch @@ -7,11 +6,6 @@ from django.test.utils import override_settings from cms import api -from cms.plugin_rendering import ( - ContentRenderer, - LegacyRenderer, - StructureRenderer, -) from cms.test_utils.testcases import CMSTestCase from cms.utils import get_language_from_request, i18n from cms.utils.compat import DJANGO_2_2 @@ -519,6 +513,7 @@ def test_redirect_on_fallback(self): response_en = self.client.get(page.get_absolute_url(language="en")) self.assertRedirects(response_en, page.get_absolute_url(language="fr")) + @override_settings( LANGUAGE_CODE='en', LANGUAGES=(('fr', 'French'), diff --git a/cms/tests/test_management.py b/cms/tests/test_management.py index b165455bb45..8c03bc3b1d4 100644 --- a/cms/tests/test_management.py +++ b/cms/tests/test_management.py @@ -288,7 +288,6 @@ def test_delete_orphaned_plugins(self): max_positon = placeholder.cmsplugin_set.aggregate(models.Max('position'))['position__max'] self.assertEqual(max_positon, 3) - def test_uninstall_plugins_without_plugin(self): out = StringIO() management.call_command('cms', 'uninstall', 'plugins', PLUGIN, interactive=False, stdout=out) diff --git a/cms/tests/test_page_admin.py b/cms/tests/test_page_admin.py index b267556a342..14d97c32dd8 100644 --- a/cms/tests/test_page_admin.py +++ b/cms/tests/test_page_admin.py @@ -1604,6 +1604,7 @@ def test_add_page_no_redirect(self): self.assertRedirects(response, redirect_url) self.assertEqual(Page.objects.all().count(), 2) + class PermissionsTestCase(PageTestBase): def assertContainsPermissions(self, response): try: @@ -2695,7 +2696,6 @@ def test_permission_cache_invalidation_on_group_add(self): self.assertIsNone(get_permission_cache(staff_user, "change_page")) - def test_permission_cache_invalidation_on_group_remove(self): """ Permissions cache is invalidated if the group relationship of a user is changed @@ -2714,7 +2714,6 @@ def test_permission_cache_invalidation_on_group_remove(self): self.assertIsNone(get_permission_cache(staff_user, "change_page")) - def test_user_can_copy_page(self): """ Test that a page can be copied via the admin diff --git a/cms/tests/test_permmod.py b/cms/tests/test_permmod.py index d45919cdf4f..e75e61010cc 100644 --- a/cms/tests/test_permmod.py +++ b/cms/tests/test_permmod.py @@ -165,7 +165,6 @@ def test_default_plugins(self): self.assertEqual(response.status_code, 200) self.assertEqual(CMSPlugin.objects.count(), 1) - def test_super_can_add_plugin(self): self._add_plugin(self.user_super, page=self.slave_page) diff --git a/cms/tests/test_placeholder.py b/cms/tests/test_placeholder.py index e43a52836ab..384423a4d22 100644 --- a/cms/tests/test_placeholder.py +++ b/cms/tests/test_placeholder.py @@ -761,14 +761,14 @@ def test_plugins_prepopulate(self): conf = { 'col_left': { - 'default_plugins' : [ + 'default_plugins': [ { - 'plugin_type':'TextPlugin', - 'values':{'body':'

en default body 1

'}, + 'plugin_type': 'TextPlugin', + 'values': {'body': '

en default body 1

'}, }, { - 'plugin_type':'TextPlugin', - 'values':{'body':'

en default body 2

'}, + 'plugin_type': 'TextPlugin', + 'values': {'body': '

en default body 2

'}, }, ] }, @@ -1376,6 +1376,10 @@ def test_delete(self): .values_list('pk', flat=True) ) + new_tree = self.get_plugins().values_list('pk', 'position') + expected = [(pk, pos) for pos, pk in enumerate(plugin_tree_all, 1)] + self.assertSequenceEqual(new_tree, expected) + for plugin in self.get_plugins().filter(parent__isnull=True): for plugin_id in [plugin.pk] + tree[plugin.pk]: plugin_tree_all.remove(plugin_id) diff --git a/cms/tests/test_plugins.py b/cms/tests/test_plugins.py index e26f73f0105..c5f7d69e3c0 100644 --- a/cms/tests/test_plugins.py +++ b/cms/tests/test_plugins.py @@ -51,7 +51,7 @@ ) from cms.test_utils.testcases import CMSTestCase from cms.toolbar.toolbar import CMSToolbar -from cms.toolbar.utils import get_object_edit_url, get_toolbar_from_request +from cms.toolbar.utils import get_object_edit_url from cms.utils.plugins import copy_plugins_to_placeholder, get_plugins diff --git a/cms/tests/test_templatetags.py b/cms/tests/test_templatetags.py index f2f2806b102..711f3274d42 100644 --- a/cms/tests/test_templatetags.py +++ b/cms/tests/test_templatetags.py @@ -57,7 +57,6 @@ def test_get_preview_url(self): self.assertIn("/en", page_preview_url) self.assertIn("/de/", german_content_preview_url) - def test_get_admin_tree_title(self): page = create_page("page_a", "nav_playground.html", "en") self.assertEqual(get_page_display_name(page), 'page_a') diff --git a/cms/tests/test_views.py b/cms/tests/test_views.py index 745c482aa1d..42055b2b140 100644 --- a/cms/tests/test_views.py +++ b/cms/tests/test_views.py @@ -12,7 +12,6 @@ from django.urls import clear_url_caches, reverse from django.utils.translation import override as force_language -from cms import api from cms.api import create_page, create_page_content from cms.middleware.toolbar import ToolbarMiddleware from cms.models import PageContent, PagePermission, Placeholder, UserSettings @@ -432,6 +431,7 @@ def test_context_current_page(self): template = Variable('CMS_TEMPLATE').resolve(response.context) self.assertEqual(template, page_template) + class EndpointTests(CMSTestCase): def setUp(self) -> None: @@ -463,7 +463,7 @@ def test_render_object_structure_i18n(self): self._add_plugin_to_placeholder(placeholder, "TextPlugin", language="fr") with force_language("fr"): setting, _ = UserSettings.objects.get_or_create(user=self.get_superuser()) - setting.language="fr" + setting.language = "fr" setting.save() structure_endpoint_url = admin_reverse( "cms_placeholder_render_object_structure", diff --git a/cms/toolbar/utils.py b/cms/toolbar/utils.py index b1be7f77e8b..358aa234fe6 100644 --- a/cms/toolbar/utils.py +++ b/cms/toolbar/utils.py @@ -14,7 +14,6 @@ ) from cms.constants import PLACEHOLDER_TOOLBAR_JS, PLUGIN_TOOLBAR_JS -from cms.models import PageContent from cms.utils import get_language_list from cms.utils.conf import get_cms_setting from cms.utils.urlutils import admin_reverse @@ -170,7 +169,7 @@ def get_object_edit_url(obj: models.Model, language: str = None) -> str: return url -def get_object_preview_url(obj:models.Model, language: str = None) -> str: +def get_object_preview_url(obj: models.Model, language: str = None) -> str: """ Returns the url of the preview endpoint for the given object. The object must be frontend-editable and registered as such with cms. @@ -207,6 +206,7 @@ def get_object_structure_url(obj: models.Model, language: str = None) -> str: with force_language(language): return admin_reverse('cms_placeholder_render_object_structure', args=[content_type.pk, obj.pk]) + def get_object_for_language(obj: models.Model, language: str, latest: bool = False) -> Optional[models.Model]: """ Retrieves the correct content object for the target language. The object must be frontend-editable diff --git a/cms/utils/permissions.py b/cms/utils/permissions.py index b4a6e0a3ebe..04c17a73d43 100644 --- a/cms/utils/permissions.py +++ b/cms/utils/permissions.py @@ -10,7 +10,7 @@ from cms.constants import ROOT_USER_LEVEL, SCRIPT_USERNAME from cms.exceptions import NoPermissionsException -from cms.models import GlobalPagePermission, Page, PagePermission +from cms.models import GlobalPagePermission, PagePermission from cms.utils.compat.dj import available_attrs from cms.utils.conf import get_cms_setting from cms.utils.page import get_clean_username diff --git a/cms/views.py b/cms/views.py index 4b37d7340d0..f16736293ae 100644 --- a/cms/views.py +++ b/cms/views.py @@ -94,7 +94,6 @@ def details(request, slug): # Get a Page model object from the request site = get_current_site() page = get_page_from_request(request, use_path=slug) - toolbar = get_toolbar_from_request(request) tree_nodes = TreeNode.objects.get_for_site(site) if not page and not slug and not tree_nodes.exists():