Implement PageQuerySet.defer_streamfields()

pull/6847/head
Andy Babic 2021-02-27 10:26:16 +00:00 zatwierdzone przez Andy Babic
rodzic 48f8970344
commit fa8402f633
4 zmienionych plików z 79 dodań i 0 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -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