mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-05-28 03:23:58 +00:00
commit
fda1fd2cf3
9 changed files with 61 additions and 303 deletions
|
|
@ -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:
|
||||||
- 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:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
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):
|
||||||
"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):
|
||||||
# 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):
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,11 @@ class InheritanceQuerySetMixin(object):
|
||||||
new_qs.subclasses = subclasses
|
new_qs.subclasses = subclasses
|
||||||
return new_qs
|
return new_qs
|
||||||
|
|
||||||
def _clone(self, klass=None, setup=False, **kwargs):
|
def _clone(self, **kwargs):
|
||||||
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(
|
return super(InheritanceQuerySetMixin, self)._clone(**kwargs)
|
||||||
klass, setup, **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 +223,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
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
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, \
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,43 @@
|
||||||
|
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 == '':
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if isinstance(value, string_types):
|
||||||
|
return [int(i) for i in value.split(',')]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
if value == '':
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
def mutable_to_db(value):
|
||||||
if isinstance(value, string_types):
|
if value is None:
|
||||||
return [int(i) for i in value.split(',')]
|
return ''
|
||||||
except ValueError:
|
if isinstance(value, list):
|
||||||
pass
|
value = ','.join((str(i) for i in value))
|
||||||
|
return str(value)
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_db_prep_save(self, value, connection):
|
if django.VERSION >= (1, 9, 0):
|
||||||
if value is None:
|
class MutableField(models.TextField):
|
||||||
return ''
|
def to_python(self, value):
|
||||||
|
return mutable_from_db(value)
|
||||||
|
|
||||||
if isinstance(value, list):
|
def from_db_value(self, value, expression, connection, context):
|
||||||
value = ','.join((str(i) for i in value))
|
return mutable_from_db(value)
|
||||||
|
|
||||||
return super(MutableField, self).get_db_prep_save(value, connection)
|
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 _
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
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 (
|
||||||
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,
|
||||||
|
|
@ -872,11 +871,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 +1204,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 +1391,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,
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -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,
|
||||||
|
|
|
||||||
7
tox.ini
7
tox.ini
|
|
@ -3,7 +3,7 @@ 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},
|
py34-django{17,18,19,_trunk},
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
|
|
@ -18,8 +18,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.9b1
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue