Skip to content

Commit

Permalink
Prefetch page translations in page tree and page API
Browse files Browse the repository at this point in the history
  • Loading branch information
timobrembeck committed Jan 5, 2022
1 parent 83a2146 commit 6f79a65
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 89 deletions.
2 changes: 1 addition & 1 deletion integreat_cms/api/v3/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def pages(request, region_slug, language_slug):
"""
region = Region.get_current_region(request)
result = []
for page in region.get_pages():
for page in region.get_pages(prefetch_public_translations=True):
page_translation = page.get_public_translation(language_slug)
if page_translation:
result.append(transform_page(page_translation))
Expand Down
45 changes: 38 additions & 7 deletions integreat_cms/cms/models/pages/abstract_base_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,23 @@ def get_translation(self, language_slug):
if no translation exists
:rtype: ~integreat_cms.cms.models.pages.page_translation.PageTranslation
"""
return self.translations.filter(language__slug=language_slug).first()
try:
# Try to get the prefetched translations (which are already distinct per language)
return next(
(
translation
for translation in self.prefetched_translations
if translation.language.slug == language_slug
),
None,
)
except AttributeError:
# If the translations were not prefetched, query it from the database
return (
self.translations.filter(language__slug=language_slug)
.select_related("language")
.first()
)

def get_public_translation(self, language_slug):
"""
Expand All @@ -75,10 +91,25 @@ def get_public_translation(self, language_slug):
:return: The public translation of a page
:rtype: ~integreat_cms.cms.models.pages.page_translation.PageTranslation
"""
return self.translations.filter(
language__slug=language_slug,
status=status.PUBLIC,
).first()
try:
# Try to get the prefetched translations (which are already distinct per language)
return next(
(
translation
for translation in self.prefetched_public_translations
if translation.language.slug == language_slug
)
)
except (AttributeError, StopIteration):
# If the translations were not prefetched or the latest version was not public, query it from the database
return (
self.translations.filter(
language__slug=language_slug,
status=status.PUBLIC,
)
.select_related("language")
.first()
)

@property
def backend_translation(self):
Expand All @@ -88,7 +119,7 @@ def backend_translation(self):
:return: The backend translation of a page
:rtype: ~integreat_cms.cms.models.pages.page_translation.PageTranslation
"""
return self.translations.filter(language__slug=get_language()).first()
return self.get_translation(get_language())

@property
def default_translation(self):
Expand All @@ -100,7 +131,7 @@ def default_translation(self):
:return: The default translation of a page
:rtype: ~integreat_cms.cms.models.pages.page_translation.PageTranslation
"""
return self.translations.filter(language=self.region.default_language).first()
return self.get_translation(self.region.default_language.slug)

@property
def best_translation(self):
Expand Down
9 changes: 3 additions & 6 deletions integreat_cms/cms/models/pages/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

from .abstract_base_page import AbstractBasePage
from ..languages.language import Language
from ..regions.region import Region
from ..media.media_file import MediaFile
from ..users.organization import Organization
from ...utils.translation_utils import ugettext_many_lazy as __

logger = logging.getLogger(__name__)
Expand All @@ -33,15 +30,15 @@ class Page(MPTTModel, AbstractBasePage):
verbose_name=_("parent page"),
)
icon = models.ForeignKey(
MediaFile,
"cms.MediaFile",
verbose_name=_("icon"),
on_delete=models.SET_NULL,
related_name="icon_pages",
blank=True,
null=True,
)
region = models.ForeignKey(
Region,
"cms.Region",
on_delete=models.CASCADE,
related_name="pages",
verbose_name=_("region"),
Expand Down Expand Up @@ -91,7 +88,7 @@ class Page(MPTTModel, AbstractBasePage):
),
)
organization = models.ForeignKey(
Organization,
"cms.Organization",
null=True,
blank=True,
on_delete=models.SET_NULL,
Expand Down
59 changes: 52 additions & 7 deletions integreat_cms/cms/models/regions/region.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from html import escape

from django.apps import apps
Expand All @@ -12,13 +13,16 @@
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404

from ...constants import region_status, administrative_division
from ...constants import region_status, administrative_division, status
from ...utils.translation_utils import ugettext_many_lazy as __
from ...utils.matomo_api_manager import MatomoApiManager
from ..languages.language import Language
from ..offers.offer_template import OfferTemplate
from ..pages.page_translation import PageTranslation


logger = logging.getLogger(__name__)

# pylint: disable=too-few-public-methods
class RegionManager(models.Manager):
"""
Expand Down Expand Up @@ -376,14 +380,14 @@ def archived_pages(self):
def non_archived_pages(self):
"""
This property returns a QuerySet of all non-archived pages of this region.
A page is considered as "non-archived" if its ``explicitly_archived`` property is ``False`` and all of the
A page is considered as "non-archived" if its ``explicitly_archived`` property is ``False`` and all the
page's ancestors are not archived as well.
Per default, the returned queryset has some limitations because of the usage of
:meth:`~django.db.models.query.QuerySet.difference` (see :meth:`~django.db.models.query.QuerySet.union` for some
restrictions). To perform the extra effort of returning an unrestricted queryset, use
:meth:`~integreat_cms.cms.models.regions.region.Region.get_pages` with the parameter ``return_unrestricted_queryset`` set to
``True``.
:meth:`~integreat_cms.cms.models.regions.region.Region.get_pages` with the parameter
``return_unrestricted_queryset`` set to ``True``.
:return: A QuerySet of all non-archived pages of this region
:rtype: ~mptt.querysets.TreeQuerySet [ ~integreat_cms.cms.models.pages.page.Page ]
Expand All @@ -397,10 +401,16 @@ def non_archived_pages(self):
# "recursetree" of django-mptt (see :doc:`django-mptt:templates`)
return non_archived_pages.order_by("tree_id", "lft")

def get_pages(self, archived=False, return_unrestricted_queryset=False):
def get_pages(
self,
archived=False,
return_unrestricted_queryset=False,
prefetch_translations=False,
prefetch_public_translations=False,
):
"""
This method returns either all archived or all non-archived pages of this region.
To retrieve all pages independently from their archived-state, use the reverse foreign key
To retrieve all pages independently of their archived-state, use the reverse foreign key
:attr:`~integreat_cms.cms.models.regions.region.Region.pages`.
Per default, the returned queryset has some limitations because of the usage of
Expand All @@ -415,17 +425,52 @@ def get_pages(self, archived=False, return_unrestricted_queryset=False):
(default: ``False``)
:type return_unrestricted_queryset: bool
:param prefetch_translations: Whether the latest translations for each language should be prefetched
(default: ``False``)
:type prefetch_translations: bool
:param prefetch_public_translations: Whether the latest public translations for each language should be prefetched
(default: ``False``)
:type prefetch_public_translations: bool
:return: Either the archived or the non-archived pages of this region
:rtype: ~mptt.querysets.TreeQuerySet [ ~integreat_cms.cms.models.pages.page.Page ]
"""
if archived:
pages = self.archived_pages
else:
pages = self.non_archived_pages
if return_unrestricted_queryset:
if (
return_unrestricted_queryset
or prefetch_translations
or prefetch_public_translations
):
# Generate a new unrestricted queryset containing the same pages
page_ids = [page.id for page in pages]
pages = self.pages.filter(id__in=page_ids)
if prefetch_translations:
pages = pages.prefetch_related(
models.Prefetch(
"translations",
queryset=PageTranslation.objects.order_by(
"language__id", "-version"
)
.distinct("language")
.select_related("language"),
to_attr="prefetched_translations",
)
)
if prefetch_public_translations:
pages = pages.prefetch_related(
models.Prefetch(
"translations",
queryset=PageTranslation.objects.filter(status=status.PUBLIC)
.order_by("language__id", "-version")
.distinct("language")
.select_related("language"),
to_attr="prefetched_public_translations",
)
)
return pages

@classmethod
Expand Down
2 changes: 1 addition & 1 deletion integreat_cms/cms/views/pages/page_tree_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def get(self, request, *args, **kwargs):
)
context = self.get_context_data(**kwargs)

pages = region.get_pages(archived=self.archived)
pages = region.get_pages(archived=self.archived, prefetch_translations=True)
enable_drag_and_drop = True
query = None
# Filter pages according to given filters, if any
Expand Down

0 comments on commit 6f79a65

Please sign in to comment.