Commit c0b30b27 authored by Brian May's avatar Brian May

Import django-model-utils_2.4.orig.tar.gz

parent f99aada0
...@@ -12,6 +12,7 @@ env: ...@@ -12,6 +12,7 @@ env:
- TOXENV=py27-django16 - TOXENV=py27-django16
- TOXENV=py27-django17 - TOXENV=py27-django17
- TOXENV=py27-django18 - TOXENV=py27-django18
- TOXENV=py27-django19
- TOXENV=py27-django_trunk - TOXENV=py27-django_trunk
- TOXENV=py32-django15 - TOXENV=py32-django15
- TOXENV=py32-django16 - TOXENV=py32-django16
...@@ -25,6 +26,7 @@ env: ...@@ -25,6 +26,7 @@ env:
- TOXENV=py33-django_trunk - TOXENV=py33-django_trunk
- TOXENV=py34-django17 - TOXENV=py34-django17
- TOXENV=py34-django18 - TOXENV=py34-django18
- TOXENV=py34-django19
- TOXENV=py34-django_trunk - TOXENV=py34-django_trunk
install: install:
......
...@@ -16,6 +16,7 @@ Gregor Müllegger <gregor@muellegger.de> ...@@ -16,6 +16,7 @@ Gregor Müllegger <gregor@muellegger.de>
ivirabyan ivirabyan
James Oakley <jfunk@funktronics.ca> James Oakley <jfunk@funktronics.ca>
Jannis Leidel <jannis@leidel.info> Jannis Leidel <jannis@leidel.info>
Jarek Glowacki <github.com/jarekwg>
Javier García Sogo <jgsogo@gmail.com> Javier García Sogo <jgsogo@gmail.com>
Jeff Elmore <jeffelmore.org> Jeff Elmore <jeffelmore.org>
Keryn Knight <kerynknight.com> Keryn Knight <kerynknight.com>
...@@ -34,4 +35,5 @@ sayane ...@@ -34,4 +35,5 @@ sayane
Tony Aldridge <zaragopha@hotmail.com> Tony Aldridge <zaragopha@hotmail.com>
Travis Swicegood <travis@domain51.com> Travis Swicegood <travis@domain51.com>
Trey Hunner <trey@treyhunner.com> Trey Hunner <trey@treyhunner.com>
Karl Wan Nan Wo <karl.wnw@gmail.com>
zyegfryed zyegfryed
CHANGES CHANGES
======= =======
2.4 (2015-12-03)
----------------
* Add support for Django 1.9. Drop support for Django 1.6 and earlier.
2.3.1 (2015-07-20) 2.3.1 (2015-07-20)
------------------ ------------------
......
from .choices import Choices from .choices import Choices
from .tracker import FieldTracker, ModelTracker from .tracker import FieldTracker, ModelTracker
__version__ = '2.3.1' __version__ = '2.4'
from __future__ import unicode_literals from __future__ import unicode_literals
import django
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
...@@ -59,6 +60,8 @@ class StatusField(models.CharField): ...@@ -59,6 +60,8 @@ class StatusField(models.CharField):
"To use StatusField, the model '%s' must have a %s choices class attribute." \ "To use StatusField, the model '%s' must have a %s choices class attribute." \
% (sender.__name__, self.choices_name) % (sender.__name__, self.choices_name)
self._choices = getattr(sender, self.choices_name) self._choices = getattr(sender, self.choices_name)
if django.VERSION >= (1, 9, 0):
self.choices = self._choices
if not self.has_default(): if not self.has_default():
self.default = tuple(getattr(sender, self.choices_name))[0][0] # set first as default self.default = tuple(getattr(sender, self.choices_name))[0][0] # set first as default
...@@ -68,6 +71,8 @@ class StatusField(models.CharField): ...@@ -68,6 +71,8 @@ class StatusField(models.CharField):
# the STATUS class attr being available), but we need to set some dummy # the STATUS class attr being available), but we need to set some dummy
# choices now so the super method will add the get_FOO_display method # choices now so the super method will add the get_FOO_display method
self._choices = [(0, 'dummy')] self._choices = [(0, 'dummy')]
if django.VERSION >= (1, 9, 0):
self.choices = self._choices
super(StatusField, self).contribute_to_class(cls, name) super(StatusField, self).contribute_to_class(cls, name)
def deconstruct(self): def deconstruct(self):
......
...@@ -57,8 +57,10 @@ class InheritanceQuerySetMixin(object): ...@@ -57,8 +57,10 @@ class InheritanceQuerySetMixin(object):
for name in ['subclasses', '_annotated']: for name in ['subclasses', '_annotated']:
if hasattr(self, name): if hasattr(self, name):
kwargs[name] = getattr(self, name) kwargs[name] = getattr(self, name)
return super(InheritanceQuerySetMixin, self)._clone( if django.VERSION < (1, 9):
klass, setup, **kwargs) kwargs['klass'] = klass
kwargs['setup'] = setup
return super(InheritanceQuerySetMixin, self)._clone(**kwargs)
def annotate(self, *args, **kwargs): def annotate(self, *args, **kwargs):
qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs) qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs)
...@@ -224,91 +226,3 @@ class QueryManagerMixin(object): ...@@ -224,91 +226,3 @@ class QueryManagerMixin(object):
class QueryManager(QueryManagerMixin, models.Manager): class QueryManager(QueryManagerMixin, models.Manager):
pass pass
class PassThroughManagerMixin(object):
"""
A mixin that enables you to call custom QuerySet methods from your manager.
"""
# pickling causes recursion errors
_deny_methods = ['__getstate__', '__setstate__', '__getinitargs__',
'__getnewargs__', '__copy__', '__deepcopy__', '_db',
'__slots__']
def __init__(self, queryset_cls=None):
self._queryset_cls = queryset_cls
super(PassThroughManagerMixin, self).__init__()
def __getattr__(self, name):
if name in self._deny_methods:
raise AttributeError(name)
if django.VERSION < (1, 6, 0):
return getattr(self.get_query_set(), name)
return getattr(self.get_queryset(), name)
def __dir__(self):
"""
Allow introspection via dir() and ipythonesque tab-discovery.
We do dir(type(self)) because to do dir(self) would be a recursion
error.
We call dir(self.get_query_set()) because it is possible that the
queryset returned by get_query_set() is interesting, even if
self._queryset_cls is None.
"""
my_values = frozenset(dir(type(self)))
my_values |= frozenset(dir(self.get_query_set()))
return list(my_values)
def get_queryset(self):
try:
qs = super(PassThroughManagerMixin, self).get_queryset()
except AttributeError:
qs = super(PassThroughManagerMixin, self).get_query_set()
if self._queryset_cls is not None:
qs = qs._clone(klass=self._queryset_cls)
return qs
get_query_set = get_queryset
@classmethod
def for_queryset_class(cls, queryset_cls):
return create_pass_through_manager_for_queryset_class(
cls, queryset_cls)
class PassThroughManager(PassThroughManagerMixin, models.Manager):
"""
Inherit from this Manager to enable you to call any methods from your
custom QuerySet class from your manager. Simply define your QuerySet
class, and return an instance of it from your manager's `get_queryset`
method.
Alternately, if you don't need any extra methods on your manager that
aren't on your QuerySet, then just pass your QuerySet class to the
``for_queryset_class`` class method.
class PostQuerySet(QuerySet):
def enabled(self):
return self.filter(disabled=False)
class Post(models.Model):
objects = PassThroughManager.for_queryset_class(PostQuerySet)()
"""
pass
def create_pass_through_manager_for_queryset_class(base, queryset_cls):
class _PassThroughManager(base):
def __init__(self, *args, **kwargs):
return super(_PassThroughManager, self).__init__(*args, **kwargs)
def get_queryset(self):
qs = super(_PassThroughManager, self).get_queryset()
return qs._clone(klass=queryset_cls)
get_query_set = get_queryset
return _PassThroughManager
from __future__ import unicode_literals from __future__ import unicode_literals
import django
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.timezone import now if django.VERSION >= (1, 9, 0):
from django.db.models.functions import Now
now = Now()
else:
from django.utils.timezone import now
from model_utils.managers import QueryManager from model_utils.managers import QueryManager
from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \ from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
......
import django
from django.db import models from django.db import models
from django.utils.six import with_metaclass, string_types from django.utils.six import with_metaclass, string_types
class MutableField(with_metaclass(models.SubfieldBase, models.TextField)): def mutable_from_db(value):
if value == '':
def to_python(self, value): return None
if value == '': try:
return None if isinstance(value, string_types):
return [int(i) for i in value.split(',')]
try: except ValueError:
if isinstance(value, string_types): pass
return [int(i) for i in value.split(',')] return value
except ValueError:
pass
def mutable_to_db(value):
return value if value is None:
return ''
def get_db_prep_save(self, value, connection): if isinstance(value, list):
if value is None: value = ','.join((str(i) for i in value))
return '' return str(value)
if isinstance(value, list):
value = ','.join((str(i) for i in value)) if django.VERSION >= (1, 9, 0):
class MutableField(models.TextField):
return super(MutableField, self).get_db_prep_save(value, connection) def to_python(self, value):
return mutable_from_db(value)
def from_db_value(self, value, expression, connection, context):
return mutable_from_db(value)
def get_db_prep_save(self, value, connection):
value = super(MutableField, self).get_db_prep_save(value, connection)
return mutable_to_db(value)
else:
class MutableField(with_metaclass(models.SubfieldBase, models.TextField)):
def to_python(self, value):
return mutable_from_db(value)
def get_db_prep_save(self, value, connection):
value = mutable_to_db(value)
return super(MutableField, self).get_db_prep_save(value, connection)
...@@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel, StatusModel, TimeFramedModel from model_utils.models import TimeStampedModel, StatusModel, TimeFramedModel
from model_utils.tracker import FieldTracker, ModelTracker from model_utils.tracker import FieldTracker, ModelTracker
from model_utils.managers import QueryManager, InheritanceManager, PassThroughManager from model_utils.managers import QueryManager, InheritanceManager
from model_utils.fields import SplitField, MonitorField, StatusField from model_utils.fields import SplitField, MonitorField, StatusField
from model_utils.tests.fields import MutableField from model_utils.tests.fields import MutableField
from model_utils import Choices from model_utils import Choices
...@@ -201,80 +201,10 @@ class FeaturedManager(models.Manager): ...@@ -201,80 +201,10 @@ class FeaturedManager(models.Manager):
get_query_set = get_queryset get_query_set = get_queryset
class DudeQuerySet(models.query.QuerySet):
def abiding(self):
return self.filter(abides=True)
def rug_positive(self):
return self.filter(has_rug=True)
def rug_negative(self):
return self.filter(has_rug=False)
def by_name(self, name):
return self.filter(name__iexact=name)
class AbidingManager(PassThroughManager):
def get_queryset(self):
return DudeQuerySet(self.model).abiding()
get_query_set = get_queryset
def get_stats(self):
return {
"abiding_count": self.count(),
"rug_count": self.rug_positive().count(),
}
class Dude(models.Model):
abides = models.BooleanField(default=True)
name = models.CharField(max_length=20)
has_rug = models.BooleanField(default=False)
objects = PassThroughManager(DudeQuerySet)
abiders = AbidingManager()
class Car(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(Dude, related_name='cars_owned')
objects = PassThroughManager(DudeQuerySet)
class SpotManager(PassThroughManager):
def get_queryset(self):
return super(SpotManager, self).get_queryset().filter(secret=False)
get_query_set = get_queryset
class SpotQuerySet(models.query.QuerySet):
def closed(self):
return self.filter(closed=True)
def secured(self):
return self.filter(secure=True)
class Spot(models.Model):
name = models.CharField(max_length=20)
secure = models.BooleanField(default=True)
closed = models.BooleanField(default=False)
secret = models.BooleanField(default=False)
owner = models.ForeignKey(Dude, related_name='spots_owned')
objects = SpotManager.for_queryset_class(SpotQuerySet)()
class Tracked(models.Model): class Tracked(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
number = models.IntegerField() number = models.IntegerField()
mutable = MutableField() mutable = MutableField(default=None)
tracker = FieldTracker() tracker = FieldTracker()
...@@ -319,7 +249,7 @@ class InheritedTracked(Tracked): ...@@ -319,7 +249,7 @@ class InheritedTracked(Tracked):
class ModelTracked(models.Model): class ModelTracked(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
number = models.IntegerField() number = models.IntegerField()
mutable = MutableField() mutable = MutableField(default=None)
tracker = ModelTracker() tracker = ModelTracker()
......
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pickle
try: try:
from unittest import skipUnless from unittest import skipUnless
except ImportError: # Python 2.6 except ImportError: # Python 2.6
...@@ -25,7 +24,7 @@ from model_utils.tests.models import ( ...@@ -25,7 +24,7 @@ from model_utils.tests.models import (
InheritanceManagerTestParent, InheritanceManagerTestChild1, InheritanceManagerTestParent, InheritanceManagerTestChild1,
InheritanceManagerTestChild2, TimeStamp, Post, Article, Status, InheritanceManagerTestChild2, TimeStamp, Post, Article, Status,
StatusPlainTuple, TimeFrame, Monitored, MonitorWhen, MonitorWhenEmpty, StatusManagerAdded, StatusPlainTuple, TimeFrame, Monitored, MonitorWhen, MonitorWhenEmpty, StatusManagerAdded,
TimeFrameManagerAdded, Dude, SplitFieldAbstractParent, Car, Spot, TimeFrameManagerAdded, SplitFieldAbstractParent,
ModelTracked, ModelTrackedFK, ModelTrackedNotDefault, ModelTrackedMultiple, InheritedModelTracked, ModelTracked, ModelTrackedFK, ModelTrackedNotDefault, ModelTrackedMultiple, InheritedModelTracked,
Tracked, TrackedFK, TrackedNotDefault, TrackedNonFieldAttr, TrackedMultiple, Tracked, TrackedFK, TrackedNotDefault, TrackedNonFieldAttr, TrackedMultiple,
InheritedTracked, StatusFieldDefaultFilled, StatusFieldDefaultNotFilled, InheritedTracked, StatusFieldDefaultFilled, StatusFieldDefaultNotFilled,
...@@ -764,6 +763,11 @@ class InheritanceManagerTests(TestCase): ...@@ -764,6 +763,11 @@ class InheritanceManagerTests(TestCase):
set(expected_related_names)) set(expected_related_names))
def test_filter_on_values_queryset(self):
queryset = InheritanceManagerTestChild1.objects.values('id').filter(pk=self.child1.pk)
self.assertEqual(list(queryset), [{'id': self.child1.pk}])
class InheritanceManagerUsingModelsTests(TestCase): class InheritanceManagerUsingModelsTests(TestCase):
def setUp(self): def setUp(self):
...@@ -872,11 +876,10 @@ class InheritanceManagerUsingModelsTests(TestCase): ...@@ -872,11 +876,10 @@ class InheritanceManagerUsingModelsTests(TestCase):
""" """
Confirming that giving a stupid model doesn't work. Confirming that giving a stupid model doesn't work.
""" """
from django.contrib.auth.models import User
regex = '^.+? is not a subclass of .+$' regex = '^.+? is not a subclass of .+$'
with self.assertRaisesRegexp(ValueError, regex): with self.assertRaisesRegexp(ValueError, regex):
InheritanceManagerTestParent.objects.select_subclasses( InheritanceManagerTestParent.objects.select_subclasses(
User).order_by('pk') TimeFrame).order_by('pk')
...@@ -1206,115 +1209,6 @@ class SouthFreezingTests(TestCase): ...@@ -1206,115 +1209,6 @@ class SouthFreezingTests(TestCase):
self.assertEqual(kwargs['no_check_for_status'], 'True') self.assertEqual(kwargs['no_check_for_status'], 'True')
class PassThroughManagerTests(TestCase):
def setUp(self):
Dude.objects.create(name='The Dude', abides=True, has_rug=False)
Dude.objects.create(name='His Dudeness', abides=False, has_rug=True)
Dude.objects.create(name='Duder', abides=False, has_rug=False)
Dude.objects.create(name='El Duderino', abides=True, has_rug=True)
def test_chaining(self):
self.assertEqual(Dude.objects.by_name('Duder').count(), 1)
self.assertEqual(Dude.objects.all().by_name('Duder').count(), 1)
self.assertEqual(Dude.abiders.rug_positive().count(), 1)
self.assertEqual(Dude.abiders.all().rug_positive().count(), 1)
def test_manager_only_methods(self):
stats = Dude.abiders.get_stats()
self.assertEqual(stats['rug_count'], 1)
with self.assertRaises(AttributeError):
Dude.abiders.all().get_stats()
def test_queryset_pickling(self):
qs = Dude.objects.all()
saltyqs = pickle.dumps(qs)
unqs = pickle.loads(saltyqs)
self.assertEqual(unqs.by_name('The Dude').count(), 1)
def test_queryset_not_available_on_related_manager(self):
dude = Dude.objects.by_name('Duder').get()
Car.objects.create(name='Ford', owner=dude)
self.assertFalse(hasattr(dude.cars_owned, 'by_name'))
def test_using_dir(self):
# make sure introspecing via dir() doesn't actually cause queries,
# just as a sanity check.
with self.assertNumQueries(0):
querysets_to_dir = (
Dude.objects,
Dude.objects.by_name('Duder'),
Dude.objects.all().by_name('Duder'),
Dude.abiders,
Dude.abiders.rug_positive(),
Dude.abiders.all().rug_positive()
)
for qs in querysets_to_dir:
self.assertTrue('by_name' in dir(qs))
self.assertTrue('abiding' in dir(qs))
self.assertTrue('rug_positive' in dir(qs))
self.assertTrue('rug_negative' in dir(qs))
# some standard qs methods
self.assertTrue('count' in dir(qs))
self.assertTrue('order_by' in dir(qs))
self.assertTrue('select_related' in dir(qs))
# make sure it's been de-duplicated
self.assertEqual(1, dir(qs).count('distinct'))
# manager only method.
self.assertTrue('get_stats' in dir(Dude.abiders))
# manager only method shouldn't appear on the non AbidingManager
self.assertFalse('get_stats' in dir(Dude.objects))
# standard manager methods
self.assertTrue('get_query_set' in dir(Dude.abiders))
self.assertTrue('contribute_to_class' in dir(Dude.abiders))
class CreatePassThroughManagerTests(TestCase):
def setUp(self):
self.dude = Dude.objects.create(name='El Duderino')
self.other_dude = Dude.objects.create(name='Das Dude')
def test_reverse_manager(self):
Spot.objects.create(
name='The Crib', owner=self.dude, closed=True, secure=True,
secret=False)
self.assertEqual(self.dude.spots_owned.closed().count(), 1)
Spot.objects.create(
name='The Crux', owner=self.other_dude, closed=True, secure=True,
secret=False
)
self.assertEqual(self.dude.spots_owned.closed().all().count(), 1)
self.assertEqual(self.dude.spots_owned.closed().count(), 1)
def test_related_queryset_pickling(self):
Spot.objects.create(
name='The Crib', owner=self.dude, closed=True, secure=True,
secret=False)
qs = self.dude.spots_owned.closed()
pickled_qs = pickle.dumps(qs)
unpickled_qs = pickle.loads(pickled_qs)
self.assertEqual(unpickled_qs.secured().count(), 1)
def test_related_queryset_superclass_method(self):
Spot.objects.create(
name='The Crib', owner=self.dude, closed=True, secure=True,
secret=False)
Spot.objects.create(
name='The Secret Crib', owner=self.dude, closed=False, secure=True,
secret=True)
self.assertEqual(self.dude.spots_owned.count(), 1)
def test_related_manager_create(self):
self.dude.spots_owned.create(name='The Crib', closed=True, secure=True)
class FieldTrackerTestCase(TestCase): class FieldTrackerTestCase(TestCase):
tracker = None tracker = None
...@@ -1502,15 +1396,13 @@ class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests): ...@@ -1502,15 +1396,13 @@ class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests):
class FieldTrackerMultipleInstancesTests(TestCase): class FieldTrackerMultipleInstancesTests(TestCase):
def test_with_deferred_fields_access_multiple(self): def test_with_deferred_fields_access_multiple(self):
instances = [ Tracked.objects.create(pk=1, name='foo', number=1)
Tracked.objects.create(pk=1, name='foo', number=1), Tracked.objects.create(pk=2, name='bar', number=2)
Tracked.objects.create(pk=2, name='bar', number=2)
]
queryset = Tracked.objects.only('id') queryset = Tracked.objects.only('id')
for instance in queryset: for instance in queryset:
name = instance.name instance.name
class FieldTrackedModelCustomTests(FieldTrackerTestCase, class FieldTrackedModelCustomTests(FieldTrackerTestCase,
......
...@@ -35,6 +35,7 @@ setup( ...@@ -35,6 +35,7 @@ setup(
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Framework :: Django', 'Framework :: Django',
], ],
zip_safe=False, zip_safe=False,
......
...@@ -3,7 +3,8 @@ envlist = ...@@ -3,7 +3,8 @@ envlist =
py26-django{14,15,16}, py26-django{14,15,16},
py27-django14, py27-django15_nosouth, py27-django14, py27-django15_nosouth,
py{27,32,33}-django{15,16,17,18,_trunk}, py{27,32,33}-django{15,16,17,18,_trunk},
py34-django{17,18,_trunk}, py27-django19,
py34-django{17,18,19,_trunk},
[testenv] [testenv]
basepython = basepython =
...@@ -18,8 +19,9 @@ deps = ...@@ -18,8 +19,9 @@ deps =
django14: Django==1.4.18 django14: Django==1.4.18
django15{,_nosouth}: Django==1.5.12 django15{,_nosouth}: Django==1.5.12
django16: Django==1.6.10 django16: Django==1.6.10
django17: Django==1.7.3 django17: Django==1.7.7
django18: Django==1.8a1 django18: Django==1.8.5
django19: Django==1.9
django_trunk: https://github.com/django/django/tarball/master django_trunk: https://github.com/django/django/tarball/master
django{14,15,16}: South==1.0.2 django{14,15,16}: South==1.0.2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment