Fixed #20625 -- Chainable Manager/QuerySet methods.
Additionally this patch solves the orthogonal problem that specialized `QuerySet` like `ValuesQuerySet` didn't inherit from the current `QuerySet` type. This wasn't an issue until now because we didn't officially support custom `QuerySet` but it became necessary with the introduction of this new feature. Thanks aaugustin, akaariai, carljm, charettes, mjtamlyn, shaib and timgraham for the reviews.
This commit is contained in:
parent
8f3aefdec3
commit
31fadc1202
|
@ -2,7 +2,7 @@ from functools import wraps
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
||||||
from django.db.models.loading import get_apps, get_app_path, get_app_paths, get_app, get_models, get_model, register_models, UnavailableApp
|
from django.db.models.loading import get_apps, get_app_path, get_app_paths, get_app, get_models, get_model, register_models, UnavailableApp
|
||||||
from django.db.models.query import Q
|
from django.db.models.query import Q, QuerySet
|
||||||
from django.db.models.expressions import F
|
from django.db.models.expressions import F
|
||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import copy
|
import copy
|
||||||
|
import inspect
|
||||||
|
|
||||||
from django.db import router
|
from django.db import router
|
||||||
from django.db.models.query import QuerySet, insert_query, RawQuerySet
|
from django.db.models.query import QuerySet, insert_query, RawQuerySet
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
@ -56,17 +58,51 @@ class RenameManagerMethods(RenameMethodsBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Manager(six.with_metaclass(RenameManagerMethods)):
|
class BaseManager(six.with_metaclass(RenameManagerMethods)):
|
||||||
# Tracks each time a Manager instance is created. Used to retain order.
|
# Tracks each time a Manager instance is created. Used to retain order.
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Manager, self).__init__()
|
super(BaseManager, self).__init__()
|
||||||
self._set_creation_counter()
|
self._set_creation_counter()
|
||||||
self.model = None
|
self.model = None
|
||||||
self._inherited = False
|
self._inherited = False
|
||||||
self._db = None
|
self._db = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_queryset_methods(cls, queryset_class):
|
||||||
|
def create_method(name, method):
|
||||||
|
def manager_method(self, *args, **kwargs):
|
||||||
|
return getattr(self.get_queryset(), name)(*args, **kwargs)
|
||||||
|
manager_method.__name__ = method.__name__
|
||||||
|
manager_method.__doc__ = method.__doc__
|
||||||
|
return manager_method
|
||||||
|
|
||||||
|
new_methods = {}
|
||||||
|
# Refs http://bugs.python.org/issue1785.
|
||||||
|
predicate = inspect.isfunction if six.PY3 else inspect.ismethod
|
||||||
|
for name, method in inspect.getmembers(queryset_class, predicate=predicate):
|
||||||
|
# Only copy missing methods.
|
||||||
|
if hasattr(cls, name):
|
||||||
|
continue
|
||||||
|
# Only copy public methods or methods with the attribute `queryset_only=False`.
|
||||||
|
queryset_only = getattr(method, 'queryset_only', None)
|
||||||
|
if queryset_only or (queryset_only is None and name.startswith('_')):
|
||||||
|
continue
|
||||||
|
# Copy the method onto the manager.
|
||||||
|
new_methods[name] = create_method(name, method)
|
||||||
|
return new_methods
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_queryset(cls, queryset_class, class_name=None):
|
||||||
|
if class_name is None:
|
||||||
|
class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
|
||||||
|
class_dict = {
|
||||||
|
'_queryset_class': queryset_class,
|
||||||
|
}
|
||||||
|
class_dict.update(cls._get_queryset_methods(queryset_class))
|
||||||
|
return type(class_name, (cls,), class_dict)
|
||||||
|
|
||||||
def contribute_to_class(self, model, name):
|
def contribute_to_class(self, model, name):
|
||||||
# TODO: Use weakref because of possible memory leak / circular reference.
|
# TODO: Use weakref because of possible memory leak / circular reference.
|
||||||
self.model = model
|
self.model = model
|
||||||
|
@ -92,8 +128,8 @@ class Manager(six.with_metaclass(RenameManagerMethods)):
|
||||||
Sets the creation counter value for this instance and increments the
|
Sets the creation counter value for this instance and increments the
|
||||||
class-level copy.
|
class-level copy.
|
||||||
"""
|
"""
|
||||||
self.creation_counter = Manager.creation_counter
|
self.creation_counter = BaseManager.creation_counter
|
||||||
Manager.creation_counter += 1
|
BaseManager.creation_counter += 1
|
||||||
|
|
||||||
def _copy_to_model(self, model):
|
def _copy_to_model(self, model):
|
||||||
"""
|
"""
|
||||||
|
@ -117,130 +153,30 @@ class Manager(six.with_metaclass(RenameManagerMethods)):
|
||||||
def db(self):
|
def db(self):
|
||||||
return self._db or router.db_for_read(self.model)
|
return self._db or router.db_for_read(self.model)
|
||||||
|
|
||||||
#######################
|
|
||||||
# PROXIES TO QUERYSET #
|
|
||||||
#######################
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Returns a new QuerySet object. Subclasses can override this method
|
|
||||||
to easily customize the behavior of the Manager.
|
|
||||||
"""
|
"""
|
||||||
return QuerySet(self.model, using=self._db)
|
Returns a new QuerySet object. Subclasses can override this method to
|
||||||
|
easily customize the behavior of the Manager.
|
||||||
def none(self):
|
"""
|
||||||
return self.get_queryset().none()
|
return self._queryset_class(self.model, using=self._db)
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
# We can't proxy this method through the `QuerySet` like we do for the
|
||||||
|
# rest of the `QuerySet` methods. This is because `QuerySet.all()`
|
||||||
|
# works by creating a "copy" of the current queryset and in making said
|
||||||
|
# copy, all the cached `prefetch_related` lookups are lost. See the
|
||||||
|
# implementation of `RelatedManager.get_queryset()` for a better
|
||||||
|
# understanding of how this comes into play.
|
||||||
return self.get_queryset()
|
return self.get_queryset()
|
||||||
|
|
||||||
def count(self):
|
|
||||||
return self.get_queryset().count()
|
|
||||||
|
|
||||||
def dates(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().dates(*args, **kwargs)
|
|
||||||
|
|
||||||
def datetimes(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().datetimes(*args, **kwargs)
|
|
||||||
|
|
||||||
def distinct(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().distinct(*args, **kwargs)
|
|
||||||
|
|
||||||
def extra(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().extra(*args, **kwargs)
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().get(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_or_create(self, **kwargs):
|
|
||||||
return self.get_queryset().get_or_create(**kwargs)
|
|
||||||
|
|
||||||
def update_or_create(self, **kwargs):
|
|
||||||
return self.get_queryset().update_or_create(**kwargs)
|
|
||||||
|
|
||||||
def create(self, **kwargs):
|
|
||||||
return self.get_queryset().create(**kwargs)
|
|
||||||
|
|
||||||
def bulk_create(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().bulk_create(*args, **kwargs)
|
|
||||||
|
|
||||||
def filter(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().filter(*args, **kwargs)
|
|
||||||
|
|
||||||
def aggregate(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().aggregate(*args, **kwargs)
|
|
||||||
|
|
||||||
def annotate(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().annotate(*args, **kwargs)
|
|
||||||
|
|
||||||
def complex_filter(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().complex_filter(*args, **kwargs)
|
|
||||||
|
|
||||||
def exclude(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().exclude(*args, **kwargs)
|
|
||||||
|
|
||||||
def in_bulk(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().in_bulk(*args, **kwargs)
|
|
||||||
|
|
||||||
def iterator(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().iterator(*args, **kwargs)
|
|
||||||
|
|
||||||
def earliest(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().earliest(*args, **kwargs)
|
|
||||||
|
|
||||||
def latest(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().latest(*args, **kwargs)
|
|
||||||
|
|
||||||
def first(self):
|
|
||||||
return self.get_queryset().first()
|
|
||||||
|
|
||||||
def last(self):
|
|
||||||
return self.get_queryset().last()
|
|
||||||
|
|
||||||
def order_by(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().order_by(*args, **kwargs)
|
|
||||||
|
|
||||||
def select_for_update(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().select_for_update(*args, **kwargs)
|
|
||||||
|
|
||||||
def select_related(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().select_related(*args, **kwargs)
|
|
||||||
|
|
||||||
def prefetch_related(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().prefetch_related(*args, **kwargs)
|
|
||||||
|
|
||||||
def values(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().values(*args, **kwargs)
|
|
||||||
|
|
||||||
def values_list(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().values_list(*args, **kwargs)
|
|
||||||
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().update(*args, **kwargs)
|
|
||||||
|
|
||||||
def reverse(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().reverse(*args, **kwargs)
|
|
||||||
|
|
||||||
def defer(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().defer(*args, **kwargs)
|
|
||||||
|
|
||||||
def only(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().only(*args, **kwargs)
|
|
||||||
|
|
||||||
def using(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().using(*args, **kwargs)
|
|
||||||
|
|
||||||
def exists(self, *args, **kwargs):
|
|
||||||
return self.get_queryset().exists(*args, **kwargs)
|
|
||||||
|
|
||||||
def _insert(self, objs, fields, **kwargs):
|
def _insert(self, objs, fields, **kwargs):
|
||||||
return insert_query(self.model, objs, fields, **kwargs)
|
return insert_query(self.model, objs, fields, **kwargs)
|
||||||
|
|
||||||
def _update(self, values, **kwargs):
|
|
||||||
return self.get_queryset()._update(values, **kwargs)
|
|
||||||
|
|
||||||
def raw(self, raw_query, params=None, *args, **kwargs):
|
def raw(self, raw_query, params=None, *args, **kwargs):
|
||||||
return RawQuerySet(raw_query=raw_query, model=self.model, params=params, using=self._db, *args, **kwargs)
|
return RawQuerySet(raw_query=raw_query, model=self.model, params=params, using=self._db, *args, **kwargs)
|
||||||
|
|
||||||
|
Manager = BaseManager.from_queryset(QuerySet, class_name='Manager')
|
||||||
|
|
||||||
|
|
||||||
class ManagerDescriptor(object):
|
class ManagerDescriptor(object):
|
||||||
# This class ensures managers aren't accessible via model instances.
|
# This class ensures managers aren't accessible via model instances.
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.conf import settings
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.db import connections, router, transaction, DatabaseError, IntegrityError
|
from django.db import connections, router, transaction, DatabaseError, IntegrityError
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.fields import AutoField
|
from django.db.models.fields import AutoField, Empty
|
||||||
from django.db.models.query_utils import (Q, select_related_descend,
|
from django.db.models.query_utils import (Q, select_related_descend,
|
||||||
deferred_class_factory, InvalidQuery)
|
deferred_class_factory, InvalidQuery)
|
||||||
from django.db.models.deletion import Collector
|
from django.db.models.deletion import Collector
|
||||||
|
@ -30,10 +30,23 @@ REPR_OUTPUT_SIZE = 20
|
||||||
EmptyResultSet = sql.EmptyResultSet
|
EmptyResultSet = sql.EmptyResultSet
|
||||||
|
|
||||||
|
|
||||||
|
def _pickle_queryset(class_bases, class_dict):
|
||||||
|
"""
|
||||||
|
Used by `__reduce__` to create the initial version of the `QuerySet` class
|
||||||
|
onto which the output of `__getstate__` will be applied.
|
||||||
|
|
||||||
|
See `__reduce__` for more details.
|
||||||
|
"""
|
||||||
|
new = Empty()
|
||||||
|
new.__class__ = type(class_bases[0].__name__, class_bases, class_dict)
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
class QuerySet(object):
|
class QuerySet(object):
|
||||||
"""
|
"""
|
||||||
Represents a lazy database lookup for a set of objects.
|
Represents a lazy database lookup for a set of objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model=None, query=None, using=None):
|
def __init__(self, model=None, query=None, using=None):
|
||||||
self.model = model
|
self.model = model
|
||||||
self._db = using
|
self._db = using
|
||||||
|
@ -45,6 +58,13 @@ class QuerySet(object):
|
||||||
self._prefetch_done = False
|
self._prefetch_done = False
|
||||||
self._known_related_objects = {} # {rel_field, {pk: rel_obj}}
|
self._known_related_objects = {} # {rel_field, {pk: rel_obj}}
|
||||||
|
|
||||||
|
def as_manager(cls):
|
||||||
|
# Address the circular dependency between `Queryset` and `Manager`.
|
||||||
|
from django.db.models.manager import Manager
|
||||||
|
return Manager.from_queryset(cls)()
|
||||||
|
as_manager.queryset_only = True
|
||||||
|
as_manager = classmethod(as_manager)
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# PYTHON MAGIC METHODS #
|
# PYTHON MAGIC METHODS #
|
||||||
########################
|
########################
|
||||||
|
@ -70,6 +90,26 @@ class QuerySet(object):
|
||||||
obj_dict = self.__dict__.copy()
|
obj_dict = self.__dict__.copy()
|
||||||
return obj_dict
|
return obj_dict
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
"""
|
||||||
|
Used by pickle to deal with the types that we create dynamically when
|
||||||
|
specialized queryset such as `ValuesQuerySet` are used in conjunction
|
||||||
|
with querysets that are *subclasses* of `QuerySet`.
|
||||||
|
|
||||||
|
See `_clone` implementation for more details.
|
||||||
|
"""
|
||||||
|
if hasattr(self, '_specialized_queryset_class'):
|
||||||
|
class_bases = (
|
||||||
|
self._specialized_queryset_class,
|
||||||
|
self._base_queryset_class,
|
||||||
|
)
|
||||||
|
class_dict = {
|
||||||
|
'_specialized_queryset_class': self._specialized_queryset_class,
|
||||||
|
'_base_queryset_class': self._base_queryset_class,
|
||||||
|
}
|
||||||
|
return _pickle_queryset, (class_bases, class_dict), self.__getstate__()
|
||||||
|
return super(QuerySet, self).__reduce__()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
data = list(self[:REPR_OUTPUT_SIZE + 1])
|
data = list(self[:REPR_OUTPUT_SIZE + 1])
|
||||||
if len(data) > REPR_OUTPUT_SIZE:
|
if len(data) > REPR_OUTPUT_SIZE:
|
||||||
|
@ -528,6 +568,7 @@ class QuerySet(object):
|
||||||
# Clear the result cache, in case this QuerySet gets reused.
|
# Clear the result cache, in case this QuerySet gets reused.
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
delete.alters_data = True
|
delete.alters_data = True
|
||||||
|
delete.queryset_only = True
|
||||||
|
|
||||||
def _raw_delete(self, using):
|
def _raw_delete(self, using):
|
||||||
"""
|
"""
|
||||||
|
@ -567,6 +608,7 @@ class QuerySet(object):
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
return query.get_compiler(self.db).execute_sql(None)
|
return query.get_compiler(self.db).execute_sql(None)
|
||||||
_update.alters_data = True
|
_update.alters_data = True
|
||||||
|
_update.queryset_only = False
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
if self._result_cache is None:
|
if self._result_cache is None:
|
||||||
|
@ -886,6 +928,15 @@ class QuerySet(object):
|
||||||
def _clone(self, klass=None, setup=False, **kwargs):
|
def _clone(self, klass=None, setup=False, **kwargs):
|
||||||
if klass is None:
|
if klass is None:
|
||||||
klass = self.__class__
|
klass = self.__class__
|
||||||
|
elif not issubclass(self.__class__, klass):
|
||||||
|
base_queryset_class = getattr(self, '_base_queryset_class', self.__class__)
|
||||||
|
class_bases = (klass, base_queryset_class)
|
||||||
|
class_dict = {
|
||||||
|
'_base_queryset_class': base_queryset_class,
|
||||||
|
'_specialized_queryset_class': klass,
|
||||||
|
}
|
||||||
|
klass = type(klass.__name__, class_bases, class_dict)
|
||||||
|
|
||||||
query = self.query.clone()
|
query = self.query.clone()
|
||||||
if self._sticky_filter:
|
if self._sticky_filter:
|
||||||
query.filter_is_sticky = True
|
query.filter_is_sticky = True
|
||||||
|
|
|
@ -121,9 +121,7 @@ described here.
|
||||||
QuerySet API
|
QuerySet API
|
||||||
============
|
============
|
||||||
|
|
||||||
Though you usually won't create one manually — you'll go through a
|
Here's the formal declaration of a ``QuerySet``:
|
||||||
:class:`~django.db.models.Manager` — here's the formal declaration of a
|
|
||||||
``QuerySet``:
|
|
||||||
|
|
||||||
.. class:: QuerySet([model=None, query=None, using=None])
|
.. class:: QuerySet([model=None, query=None, using=None])
|
||||||
|
|
||||||
|
@ -1866,6 +1864,17 @@ DO_NOTHING do not prevent taking the fast-path in deletion.
|
||||||
Note that the queries generated in object deletion is an implementation
|
Note that the queries generated in object deletion is an implementation
|
||||||
detail subject to change.
|
detail subject to change.
|
||||||
|
|
||||||
|
as_manager
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. classmethod:: as_manager()
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Class method that returns an instance of :class:`~django.db.models.Manager`
|
||||||
|
with a copy of the ``QuerySet``'s methods. See
|
||||||
|
:ref:`create-manager-with-queryset-methods` for more details.
|
||||||
|
|
||||||
.. _field-lookups:
|
.. _field-lookups:
|
||||||
|
|
||||||
Field lookups
|
Field lookups
|
||||||
|
|
|
@ -30,6 +30,13 @@ security support until the release of Django 1.8.
|
||||||
What's new in Django 1.7
|
What's new in Django 1.7
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
Calling custom ``QuerySet`` methods from the ``Manager``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
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>`.
|
||||||
|
|
||||||
Admin shortcuts support time zones
|
Admin shortcuts support time zones
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,125 @@ attribute on the manager class. This is documented fully below_.
|
||||||
|
|
||||||
.. _below: manager-types_
|
.. _below: manager-types_
|
||||||
|
|
||||||
|
.. _calling-custom-queryset-methods-from-manager:
|
||||||
|
|
||||||
|
Calling custom ``QuerySet`` methods from the ``Manager``
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
While most methods from the standard ``QuerySet`` are accessible directly from
|
||||||
|
the ``Manager``, this is only the case for the extra methods defined on a
|
||||||
|
custom ``QuerySet`` if you also implement them on the ``Manager``::
|
||||||
|
|
||||||
|
class PersonQuerySet(models.QuerySet):
|
||||||
|
def male(self):
|
||||||
|
return self.filter(sex='M')
|
||||||
|
|
||||||
|
def female(self):
|
||||||
|
return self.filter(sex='F')
|
||||||
|
|
||||||
|
class PersonManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return PersonQuerySet()
|
||||||
|
|
||||||
|
def male(self):
|
||||||
|
return self.get_queryset().male()
|
||||||
|
|
||||||
|
def female(self):
|
||||||
|
return self.get_queryset().female()
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
first_name = models.CharField(max_length=50)
|
||||||
|
last_name = models.CharField(max_length=50)
|
||||||
|
sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female')))
|
||||||
|
people = PersonManager()
|
||||||
|
|
||||||
|
This example allows you to call both ``male()`` and ``female()`` directly from
|
||||||
|
the manager ``Person.people``.
|
||||||
|
|
||||||
|
.. _create-manager-with-queryset-methods:
|
||||||
|
|
||||||
|
Creating ``Manager`` with ``QuerySet`` methods
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
In lieu of the above approach which requires duplicating methods on both the
|
||||||
|
``QuerySet`` and the ``Manager``, :meth:`QuerySet.as_manager()
|
||||||
|
<django.db.models.query.QuerySet.as_manager>` can be used to create an instance
|
||||||
|
of ``Manager`` with a copy of a custom ``QuerySet``'s methods::
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
...
|
||||||
|
people = PersonQuerySet.as_manager()
|
||||||
|
|
||||||
|
The ``Manager`` instance created by :meth:`QuerySet.as_manager()
|
||||||
|
<django.db.models.query.QuerySet.as_manager>` will be virtually
|
||||||
|
identical to the ``PersonManager`` from the previous example.
|
||||||
|
|
||||||
|
Not every ``QuerySet`` method makes sense at the ``Manager`` level; for
|
||||||
|
instance we intentionally prevent the :meth:`QuerySet.delete()
|
||||||
|
<django.db.models.query.QuerySet.delete>` method from being copied onto
|
||||||
|
the ``Manager`` class.
|
||||||
|
|
||||||
|
Methods are copied according to the following rules:
|
||||||
|
|
||||||
|
- Public methods are copied by default.
|
||||||
|
- Private methods (starting with an underscore) are not copied by default.
|
||||||
|
- Methods with a `queryset_only` attribute set to `False` are always copied.
|
||||||
|
- Methods with a `queryset_only` attribute set to `True` are never copied.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
class CustomQuerySet(models.QuerySet):
|
||||||
|
# Available on both Manager and QuerySet.
|
||||||
|
def public_method(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Available only on QuerySet.
|
||||||
|
def _private_method(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Available only on QuerySet.
|
||||||
|
def opted_out_public_method(self):
|
||||||
|
return
|
||||||
|
opted_out_public_method.queryset_only = True
|
||||||
|
|
||||||
|
# Available on both Manager and QuerySet.
|
||||||
|
def _opted_in_private_method(self):
|
||||||
|
return
|
||||||
|
_opted_in_private_method.queryset_only = False
|
||||||
|
|
||||||
|
from_queryset
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. classmethod:: from_queryset(queryset_class)
|
||||||
|
|
||||||
|
For advance usage you might want both a custom ``Manager`` and a custom
|
||||||
|
``QuerySet``. You can do that by calling ``Manager.from_queryset()`` which
|
||||||
|
returns a *subclass* of your base ``Manager`` with a copy of the custom
|
||||||
|
``QuerySet`` methods::
|
||||||
|
|
||||||
|
class BaseManager(models.Manager):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
|
def manager_only_method(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
class CustomQuerySet(models.QuerySet):
|
||||||
|
def manager_and_queryset_method(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
objects = BaseManager.from_queryset(CustomQueryset)(*args, **kwargs)
|
||||||
|
|
||||||
|
You may also store the generated class into a variable::
|
||||||
|
|
||||||
|
CustomManager = BaseManager.from_queryset(CustomQueryset)
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
objects = CustomManager(*args, **kwargs)
|
||||||
|
|
||||||
.. _custom-managers-and-inheritance:
|
.. _custom-managers-and-inheritance:
|
||||||
|
|
||||||
Custom managers and model inheritance
|
Custom managers and model inheritance
|
||||||
|
|
|
@ -6,6 +6,7 @@ import threading
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.db import connections, DEFAULT_DB_ALIAS
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
from django.db.models.fields import Field, FieldDoesNotExist
|
from django.db.models.fields import Field, FieldDoesNotExist
|
||||||
|
from django.db.models.manager import BaseManager
|
||||||
from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
|
from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
|
||||||
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -734,3 +735,57 @@ class ConcurrentSaveTests(TransactionTestCase):
|
||||||
t.join()
|
t.join()
|
||||||
a.save()
|
a.save()
|
||||||
self.assertEqual(Article.objects.get(pk=a.pk).headline, 'foo')
|
self.assertEqual(Article.objects.get(pk=a.pk).headline, 'foo')
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerTest(TestCase):
|
||||||
|
QUERYSET_PROXY_METHODS = [
|
||||||
|
'none',
|
||||||
|
'count',
|
||||||
|
'dates',
|
||||||
|
'datetimes',
|
||||||
|
'distinct',
|
||||||
|
'extra',
|
||||||
|
'get',
|
||||||
|
'get_or_create',
|
||||||
|
'update_or_create',
|
||||||
|
'create',
|
||||||
|
'bulk_create',
|
||||||
|
'filter',
|
||||||
|
'aggregate',
|
||||||
|
'annotate',
|
||||||
|
'complex_filter',
|
||||||
|
'exclude',
|
||||||
|
'in_bulk',
|
||||||
|
'iterator',
|
||||||
|
'earliest',
|
||||||
|
'latest',
|
||||||
|
'first',
|
||||||
|
'last',
|
||||||
|
'order_by',
|
||||||
|
'select_for_update',
|
||||||
|
'select_related',
|
||||||
|
'prefetch_related',
|
||||||
|
'values',
|
||||||
|
'values_list',
|
||||||
|
'update',
|
||||||
|
'reverse',
|
||||||
|
'defer',
|
||||||
|
'only',
|
||||||
|
'using',
|
||||||
|
'exists',
|
||||||
|
'_update',
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_manager_methods(self):
|
||||||
|
"""
|
||||||
|
This test ensures that the correct set of methods from `QuerySet`
|
||||||
|
are copied onto `Manager`.
|
||||||
|
|
||||||
|
It's particularly useful to prevent accidentally leaking new methods
|
||||||
|
into `Manager`. New `QuerySet` methods that should also be copied onto
|
||||||
|
`Manager` will need to be added to `ManagerTest.QUERYSET_PROXY_METHODS`.
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(BaseManager._get_queryset_methods(QuerySet).keys()),
|
||||||
|
sorted(self.QUERYSET_PROXY_METHODS),
|
||||||
|
)
|
||||||
|
|
|
@ -20,6 +20,49 @@ class PersonManager(models.Manager):
|
||||||
def get_fun_people(self):
|
def get_fun_people(self):
|
||||||
return self.filter(fun=True)
|
return self.filter(fun=True)
|
||||||
|
|
||||||
|
# An example of a custom manager that sets get_queryset().
|
||||||
|
|
||||||
|
class PublishedBookManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super(PublishedBookManager, self).get_queryset().filter(is_published=True)
|
||||||
|
|
||||||
|
# An example of a custom queryset that copies its methods onto the manager.
|
||||||
|
|
||||||
|
class CustomQuerySet(models.QuerySet):
|
||||||
|
def filter(self, *args, **kwargs):
|
||||||
|
queryset = super(CustomQuerySet, self).filter(fun=True)
|
||||||
|
queryset._filter_CustomQuerySet = True
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def public_method(self, *args, **kwargs):
|
||||||
|
return self.all()
|
||||||
|
|
||||||
|
def _private_method(self, *args, **kwargs):
|
||||||
|
return self.all()
|
||||||
|
|
||||||
|
def optout_public_method(self, *args, **kwargs):
|
||||||
|
return self.all()
|
||||||
|
optout_public_method.queryset_only = True
|
||||||
|
|
||||||
|
def _optin_private_method(self, *args, **kwargs):
|
||||||
|
return self.all()
|
||||||
|
_optin_private_method.queryset_only = False
|
||||||
|
|
||||||
|
class BaseCustomManager(models.Manager):
|
||||||
|
def __init__(self, arg):
|
||||||
|
super(BaseCustomManager, self).__init__()
|
||||||
|
self.init_arg = arg
|
||||||
|
|
||||||
|
def filter(self, *args, **kwargs):
|
||||||
|
queryset = super(BaseCustomManager, self).filter(fun=True)
|
||||||
|
queryset._filter_CustomManager = True
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def manager_only(self):
|
||||||
|
return self.all()
|
||||||
|
|
||||||
|
CustomManager = BaseCustomManager.from_queryset(CustomQuerySet)
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
first_name = models.CharField(max_length=30)
|
first_name = models.CharField(max_length=30)
|
||||||
|
@ -27,15 +70,12 @@ class Person(models.Model):
|
||||||
fun = models.BooleanField()
|
fun = models.BooleanField()
|
||||||
objects = PersonManager()
|
objects = PersonManager()
|
||||||
|
|
||||||
|
custom_queryset_default_manager = CustomQuerySet.as_manager()
|
||||||
|
custom_queryset_custom_manager = CustomManager('hello')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s %s" % (self.first_name, self.last_name)
|
return "%s %s" % (self.first_name, self.last_name)
|
||||||
|
|
||||||
# An example of a custom manager that sets get_queryset().
|
|
||||||
|
|
||||||
class PublishedBookManager(models.Manager):
|
|
||||||
def get_queryset(self):
|
|
||||||
return super(PublishedBookManager, self).get_queryset().filter(is_published=True)
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Book(models.Model):
|
class Book(models.Model):
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
|
|
|
@ -11,12 +11,54 @@ class CustomManagerTests(TestCase):
|
||||||
p1 = Person.objects.create(first_name="Bugs", last_name="Bunny", fun=True)
|
p1 = Person.objects.create(first_name="Bugs", last_name="Bunny", fun=True)
|
||||||
p2 = Person.objects.create(first_name="Droopy", last_name="Dog", fun=False)
|
p2 = Person.objects.create(first_name="Droopy", last_name="Dog", fun=False)
|
||||||
|
|
||||||
|
# Test a custom `Manager` method.
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Person.objects.get_fun_people(), [
|
Person.objects.get_fun_people(), [
|
||||||
"Bugs Bunny"
|
"Bugs Bunny"
|
||||||
],
|
],
|
||||||
six.text_type
|
six.text_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Test that the methods of a custom `QuerySet` are properly
|
||||||
|
# copied onto the default `Manager`.
|
||||||
|
for manager in ['custom_queryset_default_manager',
|
||||||
|
'custom_queryset_custom_manager']:
|
||||||
|
manager = getattr(Person, manager)
|
||||||
|
|
||||||
|
# Copy public methods.
|
||||||
|
manager.public_method()
|
||||||
|
# Don't copy private methods.
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
manager._private_method()
|
||||||
|
# Copy methods with `manager=True` even if they are private.
|
||||||
|
manager._optin_private_method()
|
||||||
|
# Don't copy methods with `manager=False` even if they are public.
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
manager.optout_public_method()
|
||||||
|
|
||||||
|
# Test that the overriden method is called.
|
||||||
|
queryset = manager.filter()
|
||||||
|
self.assertQuerysetEqual(queryset, ["Bugs Bunny"], six.text_type)
|
||||||
|
self.assertEqual(queryset._filter_CustomQuerySet, True)
|
||||||
|
|
||||||
|
# Test that specialized querysets inherit from our custom queryset.
|
||||||
|
queryset = manager.values_list('first_name', flat=True).filter()
|
||||||
|
self.assertEqual(list(queryset), [six.text_type("Bugs")])
|
||||||
|
self.assertEqual(queryset._filter_CustomQuerySet, True)
|
||||||
|
|
||||||
|
# Test that the custom manager `__init__()` argument has been set.
|
||||||
|
self.assertEqual(Person.custom_queryset_custom_manager.init_arg, 'hello')
|
||||||
|
|
||||||
|
# Test that the custom manager method is only available on the manager.
|
||||||
|
Person.custom_queryset_custom_manager.manager_only()
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
Person.custom_queryset_custom_manager.all().manager_only()
|
||||||
|
|
||||||
|
# Test that the queryset method doesn't override the custom manager method.
|
||||||
|
queryset = Person.custom_queryset_custom_manager.filter()
|
||||||
|
self.assertQuerysetEqual(queryset, ["Bugs Bunny"], six.text_type)
|
||||||
|
self.assertEqual(queryset._filter_CustomManager, True)
|
||||||
|
|
||||||
# The RelatedManager used on the 'books' descriptor extends the default
|
# The RelatedManager used on the 'books' descriptor extends the default
|
||||||
# manager
|
# manager
|
||||||
self.assertIsInstance(p2.books, PublishedBookManager)
|
self.assertIsInstance(p2.books, PublishedBookManager)
|
||||||
|
|
|
@ -90,3 +90,7 @@ class PickleabilityTestCase(TestCase):
|
||||||
reloaded = pickle.loads(dumped)
|
reloaded = pickle.loads(dumped)
|
||||||
self.assertEqual(original, reloaded)
|
self.assertEqual(original, reloaded)
|
||||||
self.assertIs(reloaded.__class__, dynclass)
|
self.assertIs(reloaded.__class__, dynclass)
|
||||||
|
|
||||||
|
def test_specialized_queryset(self):
|
||||||
|
self.assert_pickles(Happening.objects.values('name'))
|
||||||
|
self.assert_pickles(Happening.objects.values('name').dates('when', 'year'))
|
||||||
|
|
Loading…
Reference in New Issue