mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-03-16 20:00:23 +00:00
added manager_from (thanks George Sakkis)
This commit is contained in:
parent
726f0de1dd
commit
aed583f763
5 changed files with 118 additions and 3 deletions
|
|
@ -4,6 +4,7 @@ CHANGES
|
|||
tip (unreleased)
|
||||
----------------
|
||||
|
||||
- added manager_from (thanks George Sakkis)
|
||||
- added StatusField, MonitorField, TimeFramedModel, and StatusModel
|
||||
(thanks Jannis Leidel)
|
||||
- deprecated ChoiceEnum and replaced with Choices
|
||||
|
|
|
|||
43
README.rst
43
README.rst
|
|
@ -290,3 +290,46 @@ set the ordering of the ``QuerySet`` returned by the ``QueryManager``
|
|||
by chaining a call to ``.order_by()`` on the ``QueryManager`` (this is
|
||||
not required).
|
||||
|
||||
manager_from
|
||||
============
|
||||
|
||||
A common "gotcha" when defining methods on a custom manager class is
|
||||
that those same methods are not automatically also available on the
|
||||
QuerySet used by that model, so are not "chainable". This can be
|
||||
counterintuitive, as most of the public QuerySet API is also available
|
||||
on managers. It is possible to create a custom Manager that returns
|
||||
QuerySets that have the same additional methods, but this requires
|
||||
boilerplate code.
|
||||
|
||||
The ``manager_from`` function (`created by George Sakkis`_ and
|
||||
included here by permission) solves this problem with zero
|
||||
boilerplate. It creates and returns a Manager subclass with additional
|
||||
behavior defined by mixin subclasses or functions you pass it, and the
|
||||
returned Manager will return instances of a custom QuerySet with those
|
||||
same additional methods::
|
||||
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
|
||||
class AuthorMixin(object):
|
||||
def by_author(self, user):
|
||||
return self.filter(user=user)
|
||||
|
||||
class PublishedMixin(object):
|
||||
def published(self):
|
||||
return self.filter(published__lte=datetime.now())
|
||||
|
||||
def unpublished(self):
|
||||
return self.filter(published__gte=datetime.now())
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
published = models.DateTimeField()
|
||||
|
||||
objects = manager_from(AuthorMixin, PublishedMixin, unpublished)
|
||||
|
||||
Post.objects.published()
|
||||
Post.objects.by_author(user=request.user).unpublished()
|
||||
|
||||
.. _created by George Sakkis: http://djangosnippets.org/snippets/2117/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
from types import ClassType
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
class QueryManager(models.Manager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -17,4 +21,41 @@ class QueryManager(models.Manager):
|
|||
if hasattr(self, '_order_by'):
|
||||
return qs.order_by(*self._order_by)
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
def manager_from(*mixins, **kwds):
|
||||
'''
|
||||
Returns a Manager instance with extra methods, also available and
|
||||
chainable on generated querysets.
|
||||
|
||||
(By George Sakkis, originally posted at
|
||||
http://djangosnippets.org/snippets/2117/)
|
||||
|
||||
:param mixins: Each ``mixin`` can be either a class or a function. The
|
||||
generated manager and associated queryset subclasses extend the mixin
|
||||
classes and include the mixin functions (as methods).
|
||||
|
||||
:keyword queryset_cls: The base queryset class to extend from
|
||||
(``django.db.models.query.QuerySet`` by default).
|
||||
|
||||
:keyword manager_cls: The base manager class to extend from
|
||||
(``django.db.models.manager.Manager`` by default).
|
||||
'''
|
||||
bases = [kwds.get('queryset_cls', QuerySet)]
|
||||
attrs = {}
|
||||
for mixin in mixins:
|
||||
if isinstance(mixin, (ClassType, type)):
|
||||
bases.append(mixin)
|
||||
else:
|
||||
try: attrs[mixin.__name__] = mixin
|
||||
except AttributeError:
|
||||
raise TypeError('Mixin must be class or function, not %s' %
|
||||
mixin.__class__)
|
||||
# create the QuerySet subclass
|
||||
id = hash(mixins + tuple(kwds.iteritems()))
|
||||
qset_cls = type('Queryset_%d' % id, tuple(bases), attrs)
|
||||
# create the Manager subclass
|
||||
bases[0] = kwds.get('manager_cls', Manager)
|
||||
attrs['get_query_set'] = lambda self: qset_cls(self.model, using=self._db)
|
||||
manager_cls = type('Manager_%d' % id, tuple(bases), attrs)
|
||||
return manager_cls()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from model_utils.models import InheritanceCastModel, TimeStampedModel, StatusModel, TimeFramedModel
|
||||
from model_utils.managers import QueryManager
|
||||
from model_utils.managers import QueryManager, manager_from
|
||||
from model_utils.fields import SplitField, MonitorField
|
||||
from model_utils import Choices
|
||||
|
||||
|
|
@ -74,3 +74,20 @@ class NoRendered(models.Model):
|
|||
|
||||
"""
|
||||
body = SplitField(no_excerpt_field=True)
|
||||
|
||||
class AuthorMixin(object):
|
||||
def by_author(self, name):
|
||||
return self.filter(author=name)
|
||||
|
||||
class PublishedMixin(object):
|
||||
def published(self):
|
||||
return self.filter(published=True)
|
||||
|
||||
def unpublished(self):
|
||||
return self.filter(published=False)
|
||||
|
||||
class Entry(models.Model):
|
||||
author = models.CharField(max_length=20)
|
||||
published = models.BooleanField()
|
||||
|
||||
objects = manager_from(AuthorMixin, PublishedMixin, unpublished)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from model_utils.fields import get_excerpt
|
|||
from model_utils.managers import QueryManager
|
||||
from model_utils.tests.models import (InheritParent, InheritChild, TimeStamp,
|
||||
Post, Article, Status, StatusPlainTuple, TimeFrame, Monitored,
|
||||
StatusManagerAdded, TimeFrameManagerAdded)
|
||||
StatusManagerAdded, TimeFrameManagerAdded, Entry)
|
||||
|
||||
|
||||
class GetExcerptTests(TestCase):
|
||||
|
|
@ -318,3 +318,16 @@ if 'south' in settings.INSTALLED_APPS:
|
|||
self.assertRaises(FieldDoesNotExist,
|
||||
NoRendered._meta.get_field,
|
||||
'_body_excerpt')
|
||||
|
||||
class ManagerFromTests(TestCase):
|
||||
def setUp(self):
|
||||
Entry.objects.create(author='George', published=True)
|
||||
Entry.objects.create(author='George', published=False)
|
||||
Entry.objects.create(author='Paul', published=True)
|
||||
|
||||
def test_chaining(self):
|
||||
self.assertEqual(Entry.objects.by_author('George').published().count(),
|
||||
1)
|
||||
|
||||
def test_function(self):
|
||||
self.assertEqual(Entry.objects.unpublished().count(), 1)
|
||||
|
|
|
|||
Loading…
Reference in a new issue