Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Prefetch page content version objects for faster page tree #418

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions djangocms_versioning/cms_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from cms.app_base import CMSAppConfig, CMSAppExtension
from cms.extensions.models import BaseExtension
from cms.models import PageContent, Placeholder
from cms.utils import get_language_from_request
from cms.utils.i18n import get_language_list, get_language_tuple
from cms.utils.plugins import copy_plugins_to_placeholder
from cms.utils.urlutils import admin_reverse
Expand All @@ -14,6 +13,7 @@
ObjectDoesNotExist,
PermissionDenied,
)
from django.db.models import Prefetch
from django.http import (
HttpResponse,
HttpResponseBadRequest,
Expand All @@ -23,7 +23,7 @@
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _

from . import indicators, versionables
from . import indicators
from .admin import VersioningAdminMixin
from .constants import INDICATOR_DESCRIPTIONS
from .datastructures import BaseVersionableItem, VersionableItem
Expand Down Expand Up @@ -284,18 +284,8 @@ def get_readonly_fields(self, request, obj=None):
return fields

def get_queryset(self, request):
urls = ("cms_pagecontent_get_tree",)
queryset = super().get_queryset(request)
if request.resolver_match.url_name in urls:
versionable = versionables.for_content(queryset.model)

# TODO: Improve the grouping filters to use anything defined in the
# apps versioning config extra_grouping_fields
grouping_filters = {}
if "language" in versionable.extra_grouping_fields:
grouping_filters["language"] = get_language_from_request(request)

return queryset.filter(pk__in=versionable.distinct_groupers(**grouping_filters))
queryset = super().get_queryset(request)\
.prefetch_related(Prefetch("versions", to_attr="prefetched_versions"))
return queryset

# CAVEAT:
Expand Down Expand Up @@ -361,7 +351,18 @@ def get_indicator_menu(cls, request, page_content):
"""Get the indicator menu for PageContent object taking into account the
currently available versions"""
menu_template = "admin/cms/page/tree/indicator_menu.html"
status = page_content.content_indicator()
if hasattr(page_content.page, "filtered_translations") and hasattr(page_content, "prefetched_versions"):
# get_tree has prefetched versions
versions = sorted(
[content.prefetched_versions[0] for content in page_content.page.filtered_translations],
key=lambda version: -version.pk,
)
for content in page_content.page.filtered_translations:
content.__dict__["content"] = content
status = page_content.content_indicator(versions)
else:
# No prefetched versions available, get them ourselves
status = page_content.content_indicator()
if not status or status == "empty": # pragma: no cover
return super().get_indicator_menu(request, page_content)
versions = page_content._version # Cache from .content_indicator()
Expand Down
16 changes: 10 additions & 6 deletions djangocms_versioning/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,11 @@ def content_is_unlocked_for_user(content: models.Model, user: settings.AUTH_USER
"""Check if lock doesn't exist or object is locked to provided user.
"""
try:
return version_is_unlocked_for_user(content.versions.first(), user)
if hasattr(content, "prefetched_versions"):
version = content.prefetched_versions[0]
else:
version = content.versions.first()
return version_is_unlocked_for_user(version, user)
except AttributeError:
return True

Expand Down Expand Up @@ -425,16 +429,16 @@ def send_email(


def get_latest_draft_version(version):
"""Get latest draft version of version object and caches it
"""
"""Get latest draft version of version object and caches it in the
content object"""
from djangocms_versioning.constants import DRAFT
from djangocms_versioning.models import Version

if not hasattr(version, "_latest_draft_version"):
if not hasattr(version.content, "_latest_draft_version"):
drafts = (
Version.objects
.filter_by_content_grouping_values(version.content)
.filter(state=DRAFT)
)
version._latest_draft_version = drafts.first()
return version._latest_draft_version
version.content._latest_draft_version = drafts.first()
return version.content._latest_draft_version
16 changes: 12 additions & 4 deletions djangocms_versioning/indicators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import typing

from cms.utils.urlutils import admin_reverse
from django.contrib.auth import get_permission_codename
from django.db import models
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -87,16 +90,21 @@ def content_indicator_menu(request, status, versions, back=""):
return menu


def content_indicator(content_obj):
def content_indicator(
content_obj: models.Model,
versions: typing.Optional[list[Version]] = None
) -> typing.Optional[str]:
"""Translates available versions into status to be reflected by the indicator.
Function caches the result in the page_content object"""

if not content_obj:
return None # pragma: no cover
elif not hasattr(content_obj, "_indicator_status"):
versions = Version.objects.filter_by_content_grouping_values(
content_obj
).order_by("-pk")
if versions is None:
# Get all versions for the content object if not available
versions = Version.objects.filter_by_content_grouping_values(
content_obj
).order_by("-pk")
version_states = dict(VERSION_STATES)
signature = {
version.state: version
Expand Down
4 changes: 2 additions & 2 deletions djangocms_versioning/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def filter_by_grouping_values(self, versionable, **kwargs):

def filter_by_content_grouping_values(self, content):
"""Returns a list of Version objects for grouping values taken
from provided content object. In other words:
it uses the content instance property values as filter parameters
from provided content object. In other words:
it uses the content instance property values as filter parameters
"""
versionable = versionables.for_content(content)
content_objects = versionable.for_content_grouping_values(content)
Expand Down
15 changes: 12 additions & 3 deletions djangocms_versioning/templatetags/djangocms_versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ def url_version_list(content):

@register.filter
def url_publish_version(content, user):
version = content.versions.first()
if hasattr(content, "prefetched_versions"):
version = content.prefetched_versions[0]
else:
version = content.versions.first()
if version:
if version.check_publish.as_bool(user) and version.can_be_published():
proxy_model = versionables.for_content(content).version_model_proxy
Expand All @@ -25,7 +28,10 @@ def url_publish_version(content, user):

@register.filter
def url_new_draft(content, user):
version = content.versions.first()
if hasattr(content, "prefetched_versions"):
version = content.prefetched_versions[0]
else:
version = content.versions.first()
if version:
if version.state == constants.PUBLISHED:
proxy_model = versionables.for_content(content).version_model_proxy
Expand All @@ -37,7 +43,10 @@ def url_new_draft(content, user):

@register.filter
def url_revert_version(content, user):
version = content.versions.first()
if hasattr(content, "prefetched_versions"):
version = content.prefetched_versions[0]
else:
version = content.versions.first()
if version:
if version.check_revert.as_bool(user):
proxy_model = versionables.for_content(content).version_model_proxy
Expand Down
Loading