diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e2c34ad..c429830 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,3 +2,8 @@ django-model-utils changelog ============================ +v0.3.0 +------ + +* Added ``QueryManager`` + diff --git a/README.txt b/README.txt index 8f222a9..fe9b4c8 100644 --- a/README.txt +++ b/README.txt @@ -4,8 +4,8 @@ django-model-utils Django model mixins and utilities. -InheritanceCastModel -==================== +models.InheritanceCastModel +=========================== This abstract base class can be inherited by the root (parent) model in a model-inheritance tree. It allows each model in the tree to @@ -43,8 +43,34 @@ return an instance of the proper subtype, ``Restaurant`` or ``Bar``:: QuerySet subclass that could reduce this to k queries, where there are k subtypes in the inheritance tree. -TimeStampedModel -================ +models.TimeStampedModel +======================= This abstract base class just provides self-updating ``created`` and ``modified`` fields on any model that inherits it. + +managers.QueryManager +===================== + +Many custom model managers do nothing more than return a QuerySet that +is filtered in some way. ``QueryManager`` allows you to express this +pattern with a minimum of boilerplate:: + + from django.db import models + from model_utils.managers import QueryManager + + class Post(models.Model): + ... + published = models.BooleanField() + pub_date = models.DateField() + ... + + objects = models.Manager() + public = QueryManager(published=True).order_by('-pub_date') + +The kwargs passed to ``QueryManager`` will be passed as-is to the +``QuerySet.filter()`` method. You can also pass a ``Q`` object to +``QueryManager`` to express more complex conditions. Note that you can +set the ordering of the ``QuerySet`` returned by the ``QueryManager`` +by chaining a call to ``.order_by()`` on the ``QueryManager`` (this is +not required). diff --git a/model_utils/managers.py b/model_utils/managers.py new file mode 100644 index 0000000..424c48b --- /dev/null +++ b/model_utils/managers.py @@ -0,0 +1,20 @@ +from django.db import models + +class QueryManager(models.Manager): + def __init__(self, *args, **kwargs): + if args: + self._q = args[0] + else: + self._q = models.Q(**kwargs) + super(QueryManager, self).__init__() + + def order_by(self, *args): + self._order_by = args + return self + + def get_query_set(self): + qs = super(QueryManager, self).get_query_set().filter(self._q) + if hasattr(self, '_order_by'): + return qs.order_by(*self._order_by) + return qs + diff --git a/model_utils/tests/models.py b/model_utils/tests/models.py index 83fe9af..cb2796d 100644 --- a/model_utils/tests/models.py +++ b/model_utils/tests/models.py @@ -1,6 +1,8 @@ from django.db import models from model_utils.models import InheritanceCastModel, TimeStampedModel +from model_utils.managers import QueryManager + class InheritParent(InheritanceCastModel): pass @@ -10,3 +12,17 @@ class InheritChild(InheritParent): class TimeStamp(TimeStampedModel): pass + +class Post(models.Model): + published = models.BooleanField() + confirmed = models.BooleanField() + order = models.IntegerField() + + objects = models.Manager() + public = QueryManager(published=True) + public_confirmed = QueryManager(models.Q(published=True) & + models.Q(confirmed=True)) + public_reversed = QueryManager(published=True).order_by('-order') + + class Meta: + ordering = ('order',) diff --git a/model_utils/tests/tests.py b/model_utils/tests/tests.py index 3ddde4a..40dcebd 100644 --- a/model_utils/tests/tests.py +++ b/model_utils/tests/tests.py @@ -1,7 +1,8 @@ from django.test import TestCase from django.contrib.contenttypes.models import ContentType -from model_utils.tests.models import InheritParent, InheritChild, TimeStamp +from model_utils.tests.models import InheritParent, InheritChild, TimeStamp, \ + Post class InheritanceCastModelTests(TestCase): def setUp(self): @@ -32,3 +33,26 @@ class TimeStampedModelTests(TestCase): t2 = TimeStamp.objects.create() t1.save() self.assert_(t2.modified < t1.modified) + +class QueryManagerTests(TestCase): + def setUp(self): + data = ((True, True, 0), + (True, False, 4), + (False, False, 2), + (False, True, 3), + (True, True, 1), + (True, False, 5)) + for p, c, o in data: + Post.objects.create(published=p, confirmed=c, order=o) + + def testPassingKwargs(self): + qs = Post.public.all() + self.assertEquals([p.order for p in qs], [0, 1, 4, 5]) + + def testPassingQ(self): + qs = Post.public_confirmed.all() + self.assertEquals([p.order for p in qs], [0, 1]) + + def testOrdering(self): + qs = Post.public_reversed.all() + self.assertEquals([p.order for p in qs], [5, 4, 1, 0]) diff --git a/setup.py b/setup.py index fec1725..93d5092 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='django-model-utils', - version='0.2.1', + version='0.3.0', description='Django model mixins and utilities', long_description=open('README.txt').read(), author='Carl Meyer',