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

Allow developers to optimise the queryset used by chooser blocks when rendering #11932

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
71 changes: 65 additions & 6 deletions wagtail/blocks/field_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
from django.forms.fields import CallableChoiceIterator


USE_DEFAULT = "__use_default__"


class FieldBlock(Block):
"""A block that wraps a Django form field"""

Expand Down Expand Up @@ -781,22 +784,78 @@


class ChooserBlock(FieldBlock):
def __init__(self, required=True, help_text=None, validators=(), **kwargs):
"""Abstract superclass for fields that implement a chooser interface (page, image, snippet etc)"""

# Subclasses can override these attributes to apply 'select_related'
# and 'prefetch_related' to querysets when rendering, without having to
# supply them explicitly on inititalization each time
default_select_related = None
default_prefetch_related = None

def __init__(
self,
required=True,
help_text=None,
validators=(),
select_related=USE_DEFAULT,
prefetch_related=USE_DEFAULT,
**kwargs,
):
self._required = required
self._help_text = help_text
self._validators = validators
self._select_related = (
self.default_select_related
if select_related is USE_DEFAULT
else select_related
)
self._prefetch_related = (
self.default_prefetch_related
if prefetch_related is USE_DEFAULT
else prefetch_related
)
super().__init__(**kwargs)

"""Abstract superclass for fields that implement a chooser interface (page, image, snippet etc)"""
def deconstruct(self):
"""
Overrides ``FieldBlock.decontruct()`` to exclude 'select_related' and
'prefetch_related' values provided on initialisation, as they are considered
'rendering optimisations', that do not impact stored data or validation.
"""
name, args, kwargs = super().deconstruct()
try:
del kwargs["select_related"]
except KeyError:
pass
try:
del kwargs["prefetch_related"]
except KeyError:
pass
return name, args, kwargs

@cached_property
def model_class(self):
return resolve_model_string(self.target_model)

def get_base_queryset(self):
return self.model_class.objects.all()

def get_display_queryset(self):
"""
Override this method to apply any additional filtering to the queryset
for this block when converting raw values into full objects for display.
"""
qs = self.get_base_queryset()
if self._select_related is not None:
qs = qs.select_related(self._select_related)

Check warning on line 850 in wagtail/blocks/field_block.py

View check run for this annotation

Codecov / codecov/patch

wagtail/blocks/field_block.py#L850

Added line #L850 was not covered by tests
if self._prefetch_related is not None:
qs = qs.prefetch_related(self._prefetch_related)

Check warning on line 852 in wagtail/blocks/field_block.py

View check run for this annotation

Codecov / codecov/patch

wagtail/blocks/field_block.py#L852

Added line #L852 was not covered by tests
return qs

@cached_property
def field(self):
return forms.ModelChoiceField(
queryset=self.model_class.objects.all(),
queryset=self.get_base_queryset(),
widget=self.widget,
required=self._required,
validators=self._validators,
Expand All @@ -809,7 +868,7 @@
return value
else:
try:
return self.model_class.objects.get(pk=value)
return self.get_display_queryset().get(pk=value)
except self.model_class.DoesNotExist:
return None

Expand All @@ -818,7 +877,7 @@

The instances must be returned in the same order as the values and keep None values.
"""
objects = self.model_class.objects.in_bulk(values)
objects = self.get_display_queryset().in_bulk(values)
return [
objects.get(id) for id in values
] # Keeps the ordering the same as in values.
Expand All @@ -836,7 +895,7 @@
return value
else:
try:
return self.model_class.objects.get(pk=value)
return self.get_base_queryset().get(pk=value)
except self.model_class.DoesNotExist:
return None

Expand Down
Loading