Skip to content

Commit 734af77

Browse files
authored
feat: Pre-populate version.content cache when getting version object (#446)
* feat: Pre-populate `version.content` cache when getting version for content * Update djangocms_versioning/models.py * Bump python to 3.12 for tests against latest Django * Use reverse cache * add page prefetch for view published button * remove lru_cache from toolbar (memory leak) * remove unused import * Undo unnecessary changes for outdated django CMS versions * Sort imports * fix: Language menu created twice * fix lint issues * Update djangocms_versioning/cms_toolbars.py
1 parent f68190c commit 734af77

File tree

8 files changed

+57
-25
lines changed

8 files changed

+57
-25
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ jobs:
149149
strategy:
150150
fail-fast: false
151151
matrix:
152-
python-version: ['3.11']
152+
python-version: ['3.12']
153153
requirements-file: ['dj51_cms41.txt']
154154
cms-version: [
155155
'https://github.com/django-cms/django-cms/archive/develop-4.tar.gz'
@@ -183,7 +183,7 @@ jobs:
183183
strategy:
184184
fail-fast: false
185185
matrix:
186-
python-version: [ "3.11" ]
186+
python-version: [ "3.12" ]
187187
cms-version: [
188188
'https://github.com/django-cms/django-cms/archive/develop-4.tar.gz'
189189
]

djangocms_versioning/cms_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import collections
22

3+
from cms import __version__ as cms_version
34
from cms.app_base import CMSAppConfig, CMSAppExtension
45
from cms.extensions.models import BaseExtension
56
from cms.models import PageContent, Placeholder
@@ -22,6 +23,7 @@
2223
from django.utils.encoding import force_str
2324
from django.utils.functional import cached_property
2425
from django.utils.translation import gettext_lazy as _
26+
from packaging.version import Version as PackageVersion
2527

2628
from . import indicators
2729
from .admin import VersioningAdminMixin
@@ -393,6 +395,7 @@ class VersioningCMSConfig(CMSAppConfig):
393395
content_admin_mixin=VersioningCMSPageAdminMixin,
394396
)
395397
]
396-
cms_toolbar_mixin = CMSToolbarVersioningMixin
398+
if PackageVersion(cms_version) < PackageVersion("4.2"):
399+
cms_toolbar_mixin = CMSToolbarVersioningMixin
397400
PageContent.add_to_class("is_editable", is_editable)
398401
PageContent.add_to_class("content_indicator", indicators.content_indicator)

djangocms_versioning/cms_toolbars.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from cms.cms_toolbars import (
77
ADD_PAGE_LANGUAGE_BREAK,
88
LANGUAGE_MENU_IDENTIFIER,
9+
BasicToolbar,
910
PageToolbar,
1011
PlaceholderToolbar,
1112
)
@@ -242,7 +243,7 @@ def _get_published_page_version(self):
242243

243244
return PageContent._original_manager.filter(
244245
page=self.page, language=language, versions__state=PUBLISHED
245-
).first()
246+
).select_related("page").first()
246247

247248
def _add_view_published_button(self):
248249
"""Helper method to add a publish button to the toolbar
@@ -320,7 +321,6 @@ def populate(self):
320321
self.page_content = self.get_page_content() if self.page else None
321322
self.permissions_activated = get_cms_setting("PERMISSION")
322323

323-
self.override_language_menu()
324324
self.change_admin_menu()
325325
self.add_page_menu()
326326
self.change_language_menu()
@@ -401,8 +401,9 @@ def change_language_menu(self):
401401
url = add_url_parameters(translation_delete_url, language=code)
402402
on_close = REFRESH_PAGE
403403
if self.toolbar.get_object() == pagecontent and not disabled:
404-
other_content = next((self.page.get_admin_content(lang)for lang in self.page.get_languages()
405-
if lang != pagecontent.language and lang in languages), None)
404+
other_content = next(
405+
(self.page.get_admin_content(lang) for lang in self.page.get_languages()
406+
if lang != pagecontent.language and lang in languages), None)
406407
on_close = get_object_preview_url(other_content)
407408
remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled, on_close=on_close)
408409

@@ -432,10 +433,31 @@ def change_language_menu(self):
432433
)
433434

434435

436+
class VersioningBasicToolbar(BasicToolbar):
437+
def add_language_menu(self):
438+
"""
439+
Originally did override the default language menu for pages that are versioned.
440+
Now creates the menu from scratch, since VersiongBasicToolbar prevents the
441+
core from creating the too generic default language menu.
442+
"""
443+
if not settings.USE_I18N or not self.request.current_page:
444+
# Only add if no page is shown
445+
super().add_language_menu()
446+
return
447+
448+
language_menu = self.toolbar.get_or_create_menu(
449+
LANGUAGE_MENU_IDENTIFIER, _("Language"), position=-1
450+
)
451+
for code, name in get_language_tuple(self.current_site.pk):
452+
# Get the page content, it could be draft too!
453+
page_content = self.page.get_admin_content(language=code)
454+
if page_content:
455+
url = get_object_preview_url(page_content, code)
456+
language_menu.add_link_item(name, url=url, active=self.current_lang == code)
457+
458+
435459
def replace_toolbar(old, new):
436-
"""Replace `old` toolbar class with `new` class,
437-
while keeping its position in toolbar_pool.
438-
"""
460+
"""Replace `old` toolbar class with `new` class, while keeping its position in toolbar_pool."""
439461
new_name = ".".join((new.__module__, new.__name__))
440462
old_name = ".".join((old.__module__, old.__name__))
441463
toolbar_pool.toolbars = OrderedDict(
@@ -446,3 +468,4 @@ def replace_toolbar(old, new):
446468

447469
replace_toolbar(PageToolbar, VersioningPageToolbar)
448470
replace_toolbar(PlaceholderToolbar, VersioningToolbar)
471+
replace_toolbar(BasicToolbar, VersioningBasicToolbar)

djangocms_versioning/datastructures.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ def content_types(self):
176176

177177

178178
class PolymorphicVersionableItem(VersionableItem):
179-
"""VersionableItem for use by base polymorphic class
180-
(for example filer.File).
179+
"""VersionableItem for use by base polymorphic class (for example filer.File).
181180
"""
182181

183182
def _get_content_types(self):

djangocms_versioning/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def get_for_content(self, content_object):
5151
version = self.get(
5252
object_id=content_object.pk, content_type__in=versionable.content_types
5353
)
54+
version._state.fields_cache["content"] = content_object
5455
content_object._version_cache = version
5556
return version
5657

@@ -243,7 +244,11 @@ def convert_to_proxy(self):
243244
"""Returns a copy of current Version object, but as an instance
244245
of its correct proxy model"""
245246

247+
cache = self._state.fields_cache
248+
del self._state.fields_cache # Remove cache before creating deep copy
246249
new_obj = copy.deepcopy(self)
250+
new_obj._state.fields_cache = cache # Recover caches
251+
self._state.fields_cache = cache # Recover caches
247252
new_obj.__class__ = self.versionable.version_model_proxy
248253
return new_obj
249254

djangocms_versioning/plugin_rendering.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from functools import lru_cache
21

32
from cms import __version__ as cms_version
43
from cms.plugin_rendering import ContentRenderer, StructureRenderer
54
from cms.utils.placeholder import rescan_placeholders_for_obj
5+
from django.utils.functional import cached_property
66

77
from . import versionables
88
from .constants import DRAFT, PUBLISHED
@@ -77,12 +77,10 @@ def render_plugin(self, instance, page=None):
7777

7878

7979
class CMSToolbarVersioningMixin:
80-
@property
81-
@lru_cache(16)
80+
@cached_property
8281
def content_renderer(self):
8382
return VersionContentRenderer(request=self.request)
8483

85-
@property
86-
@lru_cache(16)
84+
@cached_property
8785
def structure_renderer(self):
8886
return VersionStructureRenderer(request=self.request)

tests/test_integration_with_core.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from cms.toolbar.toolbar import CMSToolbar
66
from cms.utils.urlutils import admin_reverse
77
from django.template import Context
8+
from packaging.version import Version as PackageVersion
89

910
from djangocms_versioning import constants
10-
from djangocms_versioning.plugin_rendering import VersionContentRenderer
11+
from djangocms_versioning.plugin_rendering import CMSToolbarVersioningMixin, VersionContentRenderer
1112
from djangocms_versioning.test_utils.factories import (
1213
PageFactory,
1314
PageVersionFactory,
@@ -18,12 +19,14 @@
1819
)
1920

2021

22+
@skipIf(PackageVersion(cms_version) >= PackageVersion("4.2"), "Toolbar integration not necessary for django CMS 4.2+")
2123
class CMSToolbarTestCase(CMSTestCase):
2224
def test_content_renderer(self):
2325
"""Test that cms.toolbar.toolbar.CMSToolbar.content_renderer
2426
is replaced with a property returning VersionContentRenderer
2527
"""
2628
request = self.get_request("/")
29+
self.assertIn(CMSToolbarVersioningMixin, CMSToolbar.__mro__)
2730
self.assertEqual(
2831
CMSToolbar(request).content_renderer.__class__, VersionContentRenderer
2932
)
@@ -38,7 +41,6 @@ def test_cmstoolbar_mixin(self):
3841

3942

4043
class PageContentAdminTestCase(CMSTestCase):
41-
4244
def test_get_admin_model_object(self):
4345
"""
4446
PageContent normally won't be able to fetch objects in draft. Test if the RequestToolbarForm
@@ -70,7 +72,6 @@ def test_get_title_cache(self):
7072

7173

7274
class PageAdminCopyLanguageTestCase(CMSTestCase):
73-
7475
def setUp(self):
7576
self.user = self.get_superuser()
7677
page = PageFactory()
@@ -280,7 +281,8 @@ def setUp(self):
280281
self.page.save()
281282

282283

283-
@skipIf(cms_version < "4.1.4", "Bug only fixed in django CMS 4.1.4")
284+
@skipIf(PackageVersion(cms_version) < PackageVersion("4.1.4"),
285+
"Bug only fixed in django CMS 4.1.4")
284286
def test_get_admin_url_for_language(self):
285287
"""Regression fixed that made unpublished and archived versions invisible to get_admin_url_for_language
286288
template tag. See: https://github.com/django-cms/django-cms/pull/7967"""

tests/test_toolbars.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from cms.toolbar.utils import get_object_edit_url, get_object_preview_url
99
from cms.utils.urlutils import admin_reverse
1010
from django.contrib.auth.models import Permission
11+
from django.test import override_settings
1112
from django.utils.text import slugify
1213
from packaging.version import Version
1314

@@ -669,6 +670,7 @@ def test_change_language_menu_page_toolbar_language_selector_version_link(self):
669670
self.assertEqual(de_item.url, de_preview_url)
670671
self.assertEqual(it_item.url, it_preview_url)
671672

673+
@override_settings(USE_I18N=False)
672674
def test_page_toolbar_wo_language_menu(self):
673675
from django.utils.translation import gettext as _
674676

@@ -681,13 +683,13 @@ def test_page_toolbar_wo_language_menu(self):
681683
user=self.get_superuser(),
682684
)
683685
# Remove language menu from request's toolbar
684-
del request.toolbar.menus[LANGUAGE_MENU_IDENTIFIER]
686+
self.assertNotIn(LANGUAGE_MENU_IDENTIFIER, request.toolbar.menus)
685687

686-
# find VersioningPageToolbar
688+
# find VersioningBasicToolbar
687689
for cls, toolbar in request.toolbar.toolbars.items():
688-
if cls == "djangocms_versioning.cms_toolbars.VersioningPageToolbar":
690+
if cls == "djangocms_versioning.cms_toolbars.VersioningBasicToolbar":
689691
# and call override_language_menu
690-
toolbar.override_language_menu()
692+
toolbar.add_language_menu()
691693
break
692694

693695
language_menu = request.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER, _("Language"))

0 commit comments

Comments
 (0)