Commit 04a2a6b0 authored by Loic Bistuer's avatar Loic Bistuer Committed by Anssi Kääriäinen
Browse files

Fixed #3871 -- Custom managers when traversing reverse relations.

parent 83554b01
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -319,6 +319,23 @@ def create_generic_related_manager(superclass):
                '%s__exact' % object_id_field_name: instance._get_pk_val(),
            }

        def __call__(self, **kwargs):
            # We use **kwargs rather than a kwarg argument to enforce the
            # `manager='manager_name'` syntax.
            manager = getattr(self.model, kwargs.pop('manager'))
            manager_class = create_generic_related_manager(manager.__class__)
            return manager_class(
                model = self.model,
                instance = self.instance,
                symmetrical = self.symmetrical,
                source_col_name = self.source_col_name,
                target_col_name = self.target_col_name,
                content_type = self.content_type,
                content_type_field_name = self.content_type_field_name,
                object_id_field_name = self.object_id_field_name,
                prefetch_cache_name = self.prefetch_cache_name,
            )

        def get_queryset(self):
            try:
                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
+108 −80
Original line number Diff line number Diff line
@@ -365,37 +365,7 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
            setattr(value, self.field.related.get_cache_name(), instance)


class ForeignRelatedObjectsDescriptor(object):
    # This class provides the functionality that makes the related-object
    # managers available as attributes on a model class, for fields that have
    # multiple "remote" values and have a ForeignKey pointed at them by
    # some other model. In the example "poll.choice_set", the choice_set
    # attribute is a ForeignRelatedObjectsDescriptor instance.
    def __init__(self, related):
        self.related = related   # RelatedObject instance

    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        return self.related_manager_cls(instance)

    def __set__(self, instance, value):
        manager = self.__get__(instance)
        # If the foreign key can support nulls, then completely clear the related set.
        # Otherwise, just move the named objects into the set.
        if self.related.field.null:
            manager.clear()
        manager.add(*value)

    @cached_property
    def related_manager_cls(self):
        # Dynamically create a class that subclasses the related model's default
        # manager.
        superclass = self.related.model._default_manager.__class__
        rel_field = self.related.field
        rel_model = self.related.model

def create_foreign_related_manager(superclass, rel_field, rel_model):
    class RelatedManager(superclass):
        def __init__(self, instance):
            super(RelatedManager, self).__init__()
@@ -403,6 +373,13 @@ class ForeignRelatedObjectsDescriptor(object):
            self.core_filters = {'%s__exact' % rel_field.name: instance}
            self.model = rel_model

        def __call__(self, **kwargs):
            # We use **kwargs rather than a kwarg argument to enforce the
            # `manager='manager_name'` syntax.
            manager = getattr(self.model, kwargs.pop('manager'))
            manager_class = create_foreign_related_manager(manager.__class__, rel_field, rel_model)
            return manager_class(self.instance)

        def get_queryset(self):
            try:
                return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
@@ -474,6 +451,40 @@ class ForeignRelatedObjectsDescriptor(object):
    return RelatedManager


class ForeignRelatedObjectsDescriptor(object):
    # This class provides the functionality that makes the related-object
    # managers available as attributes on a model class, for fields that have
    # multiple "remote" values and have a ForeignKey pointed at them by
    # some other model. In the example "poll.choice_set", the choice_set
    # attribute is a ForeignRelatedObjectsDescriptor instance.
    def __init__(self, related):
        self.related = related   # RelatedObject instance

    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        return self.related_manager_cls(instance)

    def __set__(self, instance, value):
        manager = self.__get__(instance)
        # If the foreign key can support nulls, then completely clear the related set.
        # Otherwise, just move the named objects into the set.
        if self.related.field.null:
            manager.clear()
        manager.add(*value)

    @cached_property
    def related_manager_cls(self):
        # Dynamically create a class that subclasses the related model's default
        # manager.
        return create_foreign_related_manager(
            self.related.model._default_manager.__class__,
            self.related.field,
            self.related.model,
        )


def create_many_related_manager(superclass, rel):
    """Creates a manager that subclasses 'superclass' (which is a Manager)
    and adds behavior for many-to-many related objects."""
@@ -513,6 +524,23 @@ def create_many_related_manager(superclass, rel):
                                 "a many-to-many relationship can be used." %
                                 instance.__class__.__name__)

        def __call__(self, **kwargs):
            # We use **kwargs rather than a kwarg argument to enforce the
            # `manager='manager_name'` syntax.
            manager = getattr(self.model, kwargs.pop('manager'))
            manager_class = create_many_related_manager(manager.__class__, rel)
            return manager_class(
                model=self.model,
                query_field_name=self.query_field_name,
                instance=self.instance,
                symmetrical=self.symmetrical,
                source_field_name=self.source_field_name,
                target_field_name=self.target_field_name,
                reverse=self.reverse,
                through=self.through,
                prefetch_cache_name=self.prefetch_cache_name,
            )

        def get_queryset(self):
            try:
                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
+6 −0
Original line number Diff line number Diff line
@@ -92,6 +92,12 @@ The :meth:`QuerySet.as_manager() <django.db.models.query.QuerySet.as_manager>`
class method has been added to :ref:`create Manager with QuerySet methods
<create-manager-with-queryset-methods>`.

Using a custom manager when traversing reverse relations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It is now possible to :ref:`specify a custom manager
<using-custom-reverse-manager>` when traversing a reverse relationship.

Admin shortcuts support time zones
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+25 −0
Original line number Diff line number Diff line
@@ -1136,6 +1136,31 @@ above example code would look like this::
    >>> b.entries.filter(headline__contains='Lennon')
    >>> b.entries.count()

.. _using-custom-reverse-manager:

Using a custom reverse manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 1.7

By default the :class:`~django.db.models.fields.related.RelatedManager` used
for reverse relations is a subclass of the :ref:`default manager <manager-names>`
for that model. If you would like to specify a different manager for a given
query you can use the following syntax::

    from django.db import models

    class Entry(models.Model):
        #...
        objects = models.Manager() # Default Manager
        entries = EntryManager() # Custom Manager

    >>> b = Blog.objects.get(id=1)
    >>> b.entry_set(manager='entries').all()

Additional methods to handle related objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to the :class:`~django.db.models.query.QuerySet` methods defined in
"Retrieving objects" above, the :class:`~django.db.models.ForeignKey`
:class:`~django.db.models.Manager` has additional methods used to handle the
+20 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ returns.

from __future__ import unicode_literals

from django.contrib.contenttypes import generic
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@@ -63,12 +64,28 @@ class BaseCustomManager(models.Manager):

CustomManager = BaseCustomManager.from_queryset(CustomQuerySet)

class FunPeopleManager(models.Manager):
    def get_queryset(self):
        return super(FunPeopleManager, self).get_queryset().filter(fun=True)

class BoringPeopleManager(models.Manager):
    def get_queryset(self):
        return super(BoringPeopleManager, self).get_queryset().filter(fun=False)

@python_2_unicode_compatible
class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    fun = models.BooleanField(default=False)

    favorite_book = models.ForeignKey('Book', null=True, related_name='favorite_books')
    favorite_thing_type = models.ForeignKey('contenttypes.ContentType', null=True)
    favorite_thing_id = models.IntegerField(null=True)
    favorite_thing = generic.GenericForeignKey('favorite_thing_type', 'favorite_thing_id')

    objects = PersonManager()
    fun_people = FunPeopleManager()
    boring_people = BoringPeopleManager()

    custom_queryset_default_manager = CustomQuerySet.as_manager()
    custom_queryset_custom_manager = CustomManager('hello')
@@ -84,6 +101,9 @@ class Book(models.Model):
    published_objects = PublishedBookManager()
    authors = models.ManyToManyField(Person, related_name='books')

    favorite_things = generic.GenericRelation(Person,
        content_type_field='favorite_thing_type', object_id_field='favorite_thing_id')

    def __str__(self):
        return self.title

Loading