Source code for django_prefetch_utils.descriptors.base
import abc
from django.db.models import Manager
[docs]class GenericPrefetchRelatedDescriptorManager(Manager):
"""
A :class:`django.db.models.Manager` to be used in conjunction
with :class:`RelatedQuerySetDescriptor`.
"""
def __init__(self, descriptor, instance):
self.descriptor = descriptor
self.instance = instance
@property
def cache_name(self):
"""
Returns the name used to store the prefetched related objects.
:rtype: str
"""
return self.descriptor.cache_name
def _apply_rel_filters(self, queryset):
"""
Returns *queryset* filtered to all of the objects which are
related to :attr:`instance`.
This internal method is used by Django's prefetch system.
:rtype: :class:`django.db.models.QuerySet`
"""
return self.descriptor.filter_queryset_for_instances(queryset, [self.instance])
[docs] def get_queryset(self):
"""
Returns a queryset of objects related to :attr:`instance`. This
method checks to see if the queryset has been cached in
:attr:`instance._prefetched_objects_cache`.
:rtype: :class:`django.db.models.QuerySet`
"""
try:
return self.instance._prefetched_objects_cache[self.cache_name]
except (AttributeError, KeyError):
return self._apply_rel_filters(self.descriptor.get_queryset())
[docs] def get_prefetch_queryset(self, instances, queryset=None):
"""
This is the primary method used by Django's prefetch system to
get all of the objects related to *instances*.
:param list instances: a list of instances of the class where this
descriptor appears
:param queryset: an optional queryset
:returns: the 5-tuple needed by Django's prefetch system.
"""
queryset = self.descriptor.get_queryset(queryset=queryset)
qs = self.descriptor.filter_queryset_for_instances(queryset, instances)
qs = self.descriptor.update_queryset_for_prefetching(qs)
qs._add_hints(instance=instances[0])
return (
qs,
self.descriptor.get_join_value_for_related_obj,
self.descriptor.get_join_value_for_instance,
self.descriptor.is_single,
self.cache_name,
True, # is_descriptor
)
[docs]class GenericPrefetchRelatedDescriptor(abc.ABC):
manager_class = GenericPrefetchRelatedDescriptorManager
is_single = False
# The following two instances attributes are defined by
# the contribute_to_class method.
name = None
model = None
[docs] @abc.abstractmethod
def get_prefetch_model_class(self):
"""
Returns the model class of the objects that are prefetched
by this descriptor.
:returns: subclass of :class:`django.db.models.Model`
"""
[docs] @abc.abstractmethod
def filter_queryset_for_instances(self, queryset, instances):
"""
Given a *queryset* for the related objects, returns that
queryset filtered down to the ones related to *instance*.
:returns: a queryset
"""
[docs] @abc.abstractmethod
def get_join_value_for_instance(self, instance):
"""
Returns the value used to associate *instance* with its related
objects.
:param instance: an instance of :attr:`model`
"""
[docs] def get_queryset(self, queryset=None):
"""
Returns the default queryset to use for the related objects.
The purpose of taking the optional *queryset* parameter is so that
a custom queryset can be passed in as part of the prefetching process,
and any subclasses can apply their own filters to that.
:param QuerySet queryset: an optional queryset to use instead of the
default queryset for the model
:rtype: :class:`django.db.models.QuerySet`
"""
if queryset is not None:
return queryset
model = self.get_prefetch_model_class()
return model._default_manager.all()
[docs] def contribute_to_class(self, cls, name):
"""
Sets the name of the descriptor and sets itself as an attribute on the
class with the same name.
This method is called by Django's
:class:`django.db.models.base.Modelbase` with the class the descriptor
is defined on as well as the name it is being set up.
:returns: ``None``
"""
setattr(cls, name, self)
self.model = cls
self.name = name
@property
def cache_name(self):
"""
Returns the dictionary key where the associated queryset will
be stored on :attr:`instance._prefetched_objects_cache` after
prefetching.
:rtype: str
"""
return self.name
[docs] def update_queryset_for_prefetching(self, queryset):
"""
Returns *queryset* updated with any additional changes needed
when it is used as a queryset within ``get_prefetch_queryset``.
:param QuerySet queryset: the queryset which will be returned
as part of the ``get_prefetch_queryset`` method.
:rtype: :class:`django.db.models.QuerySet`
"""
return queryset
def __get__(self, obj, type=None):
"""
Returns itself if accessed from a class; otherwise it returns
a :class:`RelatedQuerySetDescriptorManager` when accessed from
an instance.
:returns: *self* or an instance of :attr:`manager_class`
"""
if obj is None:
return self
return self.manager_class(self, obj)
class GenericSinglePrefetchRelatedDescriptorMixin(object):
is_single = True
def __get__(self, obj, type=None):
"""
Returns itself if accessed from a class; otherwise it returns
a :class:`RelatedQuerySetDescriptorManager` when accessed from
an instance.
:returns: *self* or an instance of :attr:`manager_class`
"""
if obj is None:
return self
manager = self.manager_class(self, obj)
try:
related_object = manager.get_queryset()[0]
except IndexError:
return None
setattr(obj, self.cache_name, related_object)
return related_object
def get_queryset(self, queryset=None):
"""
Returns the default queryset to use for the related objects.
The purpose of taking the optional *queryset* parameter is so that
a custom queryset can be passed in as part of the prefetching process,
and any subclasses can apply their own filters to that.
:param QuerySet queryset: an optional queryset to use instead of the
default queryset for the model
:rtype: :class:`django.db.models.QuerySet`
"""
qs = super().get_queryset(queryset=queryset)
# Remove warning from Django 3.1: RemovedInDjango31Warning:
# QuerySet won't use Meta.ordering in Django 3.1. Add .order_by('id') to
# retain the current query.
return qs.order_by("pk")
def is_cached(self, obj):
"""
Returns whether or not we've already fetched the related model
for *obj*.
:rtype: bool
"""
return self.cache_name in obj.__dict__
def get_prefetch_queryset(self, instances, queryset=None):
"""
This is the primary method used by Django's prefetch system to
get all of the objects related to *instances*.
:param list instances: a list of instances of the class where this
descriptor appears
:param queryset: an optional queryset
:returns: the 5-tuple needed by Django's prefetch system.
"""
# We piggy-back on the implementation of
# RelatedQuerySetDescriptorManager.get_prefetch_queryset
manager = self.manager_class(self, None)
return manager.get_prefetch_queryset(instances, queryset=queryset)