Source code for django_prefetch_utils.descriptors.via_lookup

import abc

from django.apps import apps
from django.db.models import F
from django.utils.functional import cached_property

from .base import GenericPrefetchRelatedDescriptor
from .base import GenericSinglePrefetchRelatedDescriptorMixin


[docs]class RelatedQuerySetDescriptorViaLookupBase(GenericPrefetchRelatedDescriptor): """ This is a base class for descriptors which provide access to related objects where the relationship between the instances on which this descriptor is defined and the related objects can by specified by a Django "lookup" which specifies the path from the related object to the model on which the descriptor is defined. """ @abc.abstractproperty def lookup(self): """ Returns the Django lookup string which describes the relationship from the related object to the one on which this descriptor is defined. :rtype: str """ @cached_property def obj_pk_annotation(self): """ Returns the name of an annotation to be used on the queryset so that we can easily get the primary key for the original object without having to instantiate any intermediary objects. :rtype: str """ return "_{}_".format(type(self).__name__.lower())
[docs] def filter_queryset_for_instances(self, queryset, instances): """ Returns *queryset* filtered to the objects which are related to *instances*. If *queryset* is ``None``, then :meth:`get_queryset` will be used instead. :param list instances: instances of the class on which this descriptor is found :param QuerySet queryset: the queryset to filter for *instances* :rtype: :class:`django.db.models.QuerySet` """ return queryset.filter(**{"{}__in".format(self.lookup): [obj.pk for obj in instances]})
[docs] def update_queryset_for_prefetching(self, queryset): """ Returns an updated *queryset* for use in ``get_prefetch_queryset``. We need to add an annotation to the queryset so that know which related model to associate with which original instance. :param QuerySet queryset: the queryset which will be returned as part of the ``get_prefetch_queryset`` method. :rtype: :class:`django.db.models.QuerySet` """ queryset = super().update_queryset_for_prefetching(queryset) return queryset.annotate(**{self.obj_pk_annotation: F(self.lookup)})
[docs] def get_join_value_for_instance(self, instance): """ Returns the value used to join the *instance* with the related object. In this case, it is the primary key of the instance. :rtype: int """ return instance.pk
[docs]class RelatedQuerySetDescriptorViaLookup(RelatedQuerySetDescriptorViaLookupBase): """ This provides a descriptor for access to related objects where the relationship between the instances on which this descriptor is defined and the related objects can be specified by a Django "lookup":: >>> class Author(models.Model): ... pass ... >>> class Book(models.Model): ... authors = models.ManyToManyField(Author, related_name='books') ... >>> class Reader(models.Model): ... books_read = models.ManyToManyField(Book, related_name='read_by') ... authors_read = RelatedQuerySetDescriptorViaLookupBase( ... Author, 'books__read_by' ... ) ... >>> reader = Reader.objects.prefetch_related('authors_read').first() >>> reader.authors_read.count() # no queries 42 The lookup specifies the path from the related object to the model on which the descriptor is defined. """ def __init__(self, prefetch_model, lookup): self._prefetch_model = prefetch_model self._lookup = lookup @property def lookup(self): return self._lookup
[docs] def get_prefetch_model_class(self): if isinstance(self._prefetch_model, str): self._prefetch_model = apps.get_model(self._prefetch_model) return self._prefetch_model
[docs]class RelatedSingleObjectDescriptorViaLookup( GenericSinglePrefetchRelatedDescriptorMixin, RelatedQuerySetDescriptorViaLookup ): """ This provides a descriptor for access to a related object where the relationship to the instances on which this descriptor is defined and the related objects can be specified by a Django "lookup":: >>> class Author(models.Model): ... pass ... >>> class Book(models.Model): ... authors = models.ManyToManyField(Author, related_name='books') ... >>> class Reader(models.Model): ... books_read = models.ManyToManyField(Book, related_name='read_by') ... some_read_author = RelatedSingleObjectDescriptorViaLookup( ... Author, 'books__read_by' ... ) ... >>> reader = Reader.objects.prefetch_related('some_read_author').first() >>> reader.some_read_author # no queries <Author: Jane> The lookup specifies the path from the related object to the model on which the descriptor is defined. """