kopia lustrzana https://github.com/wagtail/wagtail
Implement PageQuerySet.defer_streamfields()
rodzic
48f8970344
commit
fa8402f633
|
@ -270,4 +270,18 @@ Reference
|
|||
|
||||
See also: :py:attr:`Page.specific <wagtail.core.models.Page.specific>`
|
||||
|
||||
.. automethod:: defer_streamfields
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Apply to a queryset to avoid fetching StreamField values
|
||||
# for a specific model
|
||||
EventPage.objects.all().defer_streamfields()
|
||||
|
||||
# Or combine with specific() to avoid fetching StreamField
|
||||
# values for all models
|
||||
homepage.get_children().defer_streamfields().specific()
|
||||
|
||||
.. automethod:: first_common_ancestor
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import functools
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
|
@ -40,6 +41,7 @@ from modelcluster.fields import ParentalKey, ParentalManyToManyField
|
|||
from modelcluster.models import ClusterableModel, get_all_child_relations
|
||||
from treebeard.mp_tree import MP_Node
|
||||
|
||||
from wagtail.core.fields import StreamField
|
||||
from wagtail.core.forms import TaskStateCommentForm
|
||||
from wagtail.core.query import PageQuerySet, TreeQuerySet
|
||||
from wagtail.core.signals import (
|
||||
|
@ -671,6 +673,14 @@ def get_default_page_content_type():
|
|||
return ContentType.objects.get_for_model(Page)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def get_streamfield_names(model_class):
|
||||
return tuple(
|
||||
field.name for field in model_class._meta.concrete_fields
|
||||
if isinstance(field, StreamField)
|
||||
)
|
||||
|
||||
|
||||
class BasePageManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return self._queryset_class(self.model).order_by('path')
|
||||
|
@ -893,6 +903,10 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@classmethod
|
||||
def get_streamfield_names(cls):
|
||||
return get_streamfield_names(cls)
|
||||
|
||||
def set_url_path(self, parent):
|
||||
"""
|
||||
Populate the url_path field based on this page's slug and the specified parent page.
|
||||
|
|
|
@ -135,6 +135,18 @@ class TreeQuerySet(MP_NodeQuerySet):
|
|||
|
||||
|
||||
class PageQuerySet(SearchableQuerySetMixin, TreeQuerySet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set custom instance attributes"""
|
||||
super().__init__(*args, **kwargs)
|
||||
# set by defer_streamfields()
|
||||
self._defer_streamfields = False
|
||||
|
||||
def _clone(self):
|
||||
"""Ensure clones inherit custom attribute values."""
|
||||
clone = super()._clone()
|
||||
clone._defer_streamfields = self._defer_streamfields
|
||||
return clone
|
||||
|
||||
def live_q(self):
|
||||
return Q(live=True)
|
||||
|
||||
|
@ -345,6 +357,20 @@ class PageQuerySet(SearchableQuerySetMixin, TreeQuerySet):
|
|||
for page in self.live():
|
||||
page.unpublish()
|
||||
|
||||
def defer_streamfields(self):
|
||||
"""
|
||||
Apply to a queryset to prevent fetching/decoding of StreamField values on
|
||||
evaluation. Useful when working with potentially large numbers of results,
|
||||
where StreamField values are unlikely to be needed. For example, when
|
||||
generating a sitemap or a long list of page links.
|
||||
"""
|
||||
clone = self._clone()
|
||||
clone._defer_streamfields = True # used by specific_iterator()
|
||||
streamfield_names = self.model.get_streamfield_names()
|
||||
if not streamfield_names:
|
||||
return clone
|
||||
return clone.defer(*streamfield_names)
|
||||
|
||||
def specific(self, defer=False):
|
||||
"""
|
||||
This efficiently gets all the specific pages for the queryset, using
|
||||
|
@ -434,6 +460,8 @@ def specific_iterator(qs, defer=False):
|
|||
# Defer all specific fields
|
||||
fields = [field.attname for field in Page._meta.get_fields() if field.concrete]
|
||||
pages = pages.only(*fields)
|
||||
elif qs._defer_streamfields:
|
||||
pages = pages.defer_streamfields()
|
||||
|
||||
pages_for_type = {page.pk: page for page in pages}
|
||||
pages_by_type[content_type] = pages_for_type
|
||||
|
|
|
@ -871,10 +871,19 @@ class TestFirstCommonAncestor(TestCase):
|
|||
fixtures = ['test_specific.json']
|
||||
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(url_path='/home/')
|
||||
self.all_events = Page.objects.type(EventPage)
|
||||
self.regular_events = Page.objects.type(EventPage)\
|
||||
.exclude(url_path__contains='/other/')
|
||||
|
||||
def _create_streampage(self):
|
||||
stream_page = StreamPage(
|
||||
title='stream page',
|
||||
slug='stream-page',
|
||||
body='[{"type": "text", "value": "foo"}]',
|
||||
)
|
||||
self.root_page.add_child(instance=stream_page)
|
||||
|
||||
def test_bookkeeping(self):
|
||||
self.assertEqual(self.all_events.count(), 4)
|
||||
self.assertEqual(self.regular_events.count(), 3)
|
||||
|
@ -936,3 +945,17 @@ class TestFirstCommonAncestor(TestCase):
|
|||
def test_empty_queryset_strict(self):
|
||||
with self.assertRaises(Page.DoesNotExist):
|
||||
Page.objects.none().first_common_ancestor(strict=True)
|
||||
|
||||
def test_defer_streamfields_without_specific(self):
|
||||
self._create_streampage()
|
||||
for page in StreamPage.objects.all().defer_streamfields():
|
||||
self.assertNotIn('body', page.__dict__)
|
||||
with self.assertNumQueries(1):
|
||||
page.body
|
||||
|
||||
def test_defer_streamfields_with_specific(self):
|
||||
self._create_streampage()
|
||||
for page in Page.objects.exact_type(StreamPage).defer_streamfields().specific():
|
||||
self.assertNotIn('body', page.__dict__)
|
||||
with self.assertNumQueries(1):
|
||||
page.body
|
||||
|
|
Ładowanie…
Reference in New Issue