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 @@
Your funding directly benefits the product, mainly through the @@ -50,10 +50,10 @@
The django CMS Association is a non-profit organisation that funds and steers the development of @@ -63,10 +63,10 @@
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():