Merge pull request #36 from carljm/python3

Support Python 3
This commit is contained in:
Trey Hunner 2013-04-12 18:28:17 -07:00
commit 02cc04b0f7
11 changed files with 142 additions and 133 deletions

View file

@ -5,13 +5,10 @@ python:
- "2.7"
env:
- DJANGO=Django==1.2.7 SOUTH=1
- DJANGO=Django==1.3.7 SOUTH=1
- DJANGO=Django==1.4.5 SOUTH=1
- DJANGO=Django==1.4.5 SOUTH=1
- DJANGO=Django==1.5 SOUTH=1
- DJANGO=Django==1.5.1 SOUTH=1
- DJANGO=https://github.com/django/django/tarball/master SOUTH=1
- DJANGO=Django==1.4.5 SOUTH=0
- DJANGO=Django==1.5.1 SOUTH=0
install:
- pip install $DJANGO --use-mirrors
@ -20,4 +17,15 @@ install:
script: coverage run -a --branch --include="model_utils/*" --omit="model_utils/tests/*" setup.py test
matrix:
include:
- python: 3.2
env: DJANGO=Django==1.5.1 SOUTH=0
- python: 3.2
env: DJANGO=https://github.com/django/django/tarball/master SOUTH=0
- python: 3.3
env: DJANGO=Django==1.5.1 SOUTH=0
- python: 3.3
env: DJANGO=https://github.com/django/django/tarball/master SOUTH=0
after_success: coveralls

View file

@ -12,6 +12,8 @@ tip (unreleased)
compatibility.
- Fix intermittent ``StatusField`` bug. Fixes GH-29.
- Added Python 3 support
- Dropped support for Django 1.2 and 1.3. Django 1.4.2+ required.
1.3.0 (2013.03.27)

View file

@ -29,7 +29,8 @@ your ``INSTALLED_APPS`` setting.
Dependencies
------------
``django-model-utils`` is tested with `Django`_ 1.2 and later on Python 2.6 and 2.7.
``django-model-utils`` supports `Django`_ 1.4.2 and later on Python 2.6, 2.7,
3.2, and 3.3.
.. _Django: http://www.djangoproject.com/

View file

@ -1,3 +1,6 @@
from __future__ import unicode_literals
class Choices(object):
"""
A class to encapsulate handy functionality for lists of choices
@ -72,4 +75,4 @@ class Choices(object):
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
', '.join(("%s" % str(i) for i in self._full)))
', '.join(("%s" % repr(i) for i in self._full)))

View file

@ -1,13 +1,9 @@
from datetime import datetime
from __future__ import unicode_literals
from django.db import models
from django.conf import settings
try:
from django.utils.timezone import now as now
except ImportError:
now = datetime.now
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
class AutoCreatedField(models.DateTimeField):
@ -128,6 +124,7 @@ def get_excerpt(content):
return '\n'.join(default_excerpt)
@python_2_unicode_compatible
class SplitText(object):
def __init__(self, instance, field_name, excerpt_field_name):
# instead of storing actual values store a reference to the instance
@ -153,8 +150,7 @@ class SplitText(object):
return self.excerpt.strip() != self.content.strip()
has_more = property(_get_has_more)
# allows display via templates without .content necessary
def __unicode__(self):
def __str__(self):
return self.content
class SplitDescriptor(object):

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import django
from django.db import models
from django.db.models.fields.related import OneToOneField
@ -38,7 +39,7 @@ class InheritanceQuerySet(QuerySet):
def annotate(self, *args, **kwargs):
qset = super(InheritanceQuerySet, self).annotate(*args, **kwargs)
qset._annotated = [a.default_alias for a in args] + kwargs.keys()
qset._annotated = [a.default_alias for a in args] + list(kwargs.keys())
return qset

View file

@ -1,20 +1,15 @@
from datetime import datetime
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import ImproperlyConfigured
from django.utils.timezone import now
from model_utils.managers import QueryManager
from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
StatusField, MonitorField
try:
from django.utils.timezone import now as now
except ImportError:
now = datetime.now
class TimeStampedModel(models.Model):
"""

View file

@ -1,4 +1,4 @@
from __future__ import with_statement
from __future__ import unicode_literals
import pickle
from datetime import datetime, timedelta
@ -6,6 +6,7 @@ from datetime import datetime, timedelta
import django
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils.six import text_type
from django.core.exceptions import ImproperlyConfigured, FieldError
from django.test import TestCase
@ -27,28 +28,28 @@ from model_utils.tests.models import (
class GetExcerptTests(TestCase):
def test_split(self):
e = get_excerpt("some content\n\n<!-- split -->\n\nsome more")
self.assertEquals(e, 'some content\n')
self.assertEqual(e, 'some content\n')
def test_auto_split(self):
e = get_excerpt("para one\n\npara two\n\npara three")
self.assertEquals(e, 'para one\n\npara two')
self.assertEqual(e, 'para one\n\npara two')
def test_middle_of_para(self):
e = get_excerpt("some text\n<!-- split -->\nmore text")
self.assertEquals(e, 'some text')
self.assertEqual(e, 'some text')
def test_middle_of_line(self):
e = get_excerpt("some text <!-- split --> more text")
self.assertEquals(e, "some text <!-- split --> more text")
self.assertEqual(e, "some text <!-- split --> more text")
class SplitFieldTests(TestCase):
full_text = u'summary\n\n<!-- split -->\n\nmore'
excerpt = u'summary\n'
full_text = 'summary\n\n<!-- split -->\n\nmore'
excerpt = 'summary\n'
def setUp(self):
@ -57,45 +58,45 @@ class SplitFieldTests(TestCase):
def test_unicode_content(self):
self.assertEquals(unicode(self.post.body), self.full_text)
self.assertEqual(text_type(self.post.body), self.full_text)
def test_excerpt(self):
self.assertEquals(self.post.body.excerpt, self.excerpt)
self.assertEqual(self.post.body.excerpt, self.excerpt)
def test_content(self):
self.assertEquals(self.post.body.content, self.full_text)
self.assertEqual(self.post.body.content, self.full_text)
def test_has_more(self):
self.failUnless(self.post.body.has_more)
self.assertTrue(self.post.body.has_more)
def test_not_has_more(self):
post = Article.objects.create(title='example 2',
body='some text\n\nsome more\n')
self.failIf(post.body.has_more)
self.assertFalse(post.body.has_more)
def test_load_back(self):
post = Article.objects.get(pk=self.post.pk)
self.assertEquals(post.body.content, self.post.body.content)
self.assertEquals(post.body.excerpt, self.post.body.excerpt)
self.assertEqual(post.body.content, self.post.body.content)
self.assertEqual(post.body.excerpt, self.post.body.excerpt)
def test_assign_to_body(self):
new_text = u'different\n\n<!-- split -->\n\nother'
new_text = 'different\n\n<!-- split -->\n\nother'
self.post.body = new_text
self.post.save()
self.assertEquals(unicode(self.post.body), new_text)
self.assertEqual(text_type(self.post.body), new_text)
def test_assign_to_content(self):
new_text = u'different\n\n<!-- split -->\n\nother'
new_text = 'different\n\n<!-- split -->\n\nother'
self.post.body.content = new_text
self.post.save()
self.assertEquals(unicode(self.post.body), new_text)
self.assertEqual(text_type(self.post.body), new_text)
def test_assign_to_excerpt(self):
@ -112,18 +113,18 @@ class SplitFieldTests(TestCase):
def test_none(self):
a = Article(title='Some Title', body=None)
self.assertEquals(a.body, None)
self.assertEqual(a.body, None)
def test_assign_splittext(self):
a = Article(title='Some Title')
a.body = self.post.body
self.assertEquals(a.body.excerpt, u'summary\n')
self.assertEqual(a.body.excerpt, 'summary\n')
def test_value_to_string(self):
f = self.post._meta.get_field('body')
self.assertEquals(f.value_to_string(self.post), self.full_text)
self.assertEqual(f.value_to_string(self.post), self.full_text)
def test_abstract_inheritance(self):
@ -144,13 +145,13 @@ class MonitorFieldTests(TestCase):
def test_save_no_change(self):
self.instance.save()
self.assertEquals(self.instance.name_changed, self.created)
self.assertEqual(self.instance.name_changed, self.created)
def test_save_changed(self):
self.instance.name = 'Maria'
self.instance.save()
self.failUnless(self.instance.name_changed > self.created)
self.assertTrue(self.instance.name_changed > self.created)
def test_double_save(self):
@ -158,7 +159,7 @@ class MonitorFieldTests(TestCase):
self.instance.save()
changed = self.instance.name_changed
self.instance.save()
self.assertEquals(self.instance.name_changed, changed)
self.assertEqual(self.instance.name_changed, changed)
def test_no_monitor_arg(self):
@ -169,11 +170,11 @@ class StatusFieldTests(TestCase):
def test_status_with_default_filled(self):
instance = StatusFieldDefaultFilled()
self.assertEquals(instance.status, instance.STATUS.yes)
self.assertEqual(instance.status, instance.STATUS.yes)
def test_status_with_default_not_filled(self):
instance = StatusFieldDefaultNotFilled()
self.assertEquals(instance.status, instance.STATUS.no)
self.assertEqual(instance.status, instance.STATUS.no)
def test_no_check_for_status(self):
field = StatusField(no_check_for_status=True)
@ -187,15 +188,15 @@ class ChoicesTests(TestCase):
def test_getattr(self):
self.assertEquals(self.STATUS.DRAFT, 'DRAFT')
self.assertEqual(self.STATUS.DRAFT, 'DRAFT')
def test_indexing(self):
self.assertEquals(self.STATUS[1], ('PUBLISHED', 'PUBLISHED'))
self.assertEqual(self.STATUS[1], ('PUBLISHED', 'PUBLISHED'))
def test_iteration(self):
self.assertEquals(tuple(self.STATUS), (('DRAFT', 'DRAFT'), ('PUBLISHED', 'PUBLISHED')))
self.assertEqual(tuple(self.STATUS), (('DRAFT', 'DRAFT'), ('PUBLISHED', 'PUBLISHED')))
def test_len(self):
@ -203,10 +204,10 @@ class ChoicesTests(TestCase):
def test_repr(self):
self.assertEquals(repr(self.STATUS),
"Choices("
"('DRAFT', 'DRAFT', 'DRAFT'), "
"('PUBLISHED', 'PUBLISHED', 'PUBLISHED'))")
self.assertEqual(repr(self.STATUS), "Choices" + repr((
('DRAFT', 'DRAFT', 'DRAFT'),
('PUBLISHED', 'PUBLISHED', 'PUBLISHED'),
)))
def test_wrong_length_tuple(self):
@ -224,7 +225,7 @@ class LabelChoicesTests(ChoicesTests):
def test_iteration(self):
self.assertEquals(tuple(self.STATUS), (
self.assertEqual(tuple(self.STATUS), (
('DRAFT', 'is draft'),
('PUBLISHED', 'is published'),
('DELETED', 'DELETED'))
@ -232,15 +233,15 @@ class LabelChoicesTests(ChoicesTests):
def test_indexing(self):
self.assertEquals(self.STATUS[1], ('PUBLISHED', 'is published'))
self.assertEqual(self.STATUS[1], ('PUBLISHED', 'is published'))
def test_default(self):
self.assertEquals(self.STATUS.DELETED, 'DELETED')
self.assertEqual(self.STATUS.DELETED, 'DELETED')
def test_provided(self):
self.assertEquals(self.STATUS.DRAFT, 'DRAFT')
self.assertEqual(self.STATUS.DRAFT, 'DRAFT')
def test_len(self):
@ -248,11 +249,11 @@ class LabelChoicesTests(ChoicesTests):
def test_repr(self):
self.assertEquals(repr(self.STATUS),
"Choices("
"('DRAFT', 'DRAFT', 'is draft'), "
"('PUBLISHED', 'PUBLISHED', 'is published'), "
"('DELETED', 'DELETED', 'DELETED'))")
self.assertEqual(repr(self.STATUS), "Choices" + repr((
('DRAFT', 'DRAFT', 'is draft'),
('PUBLISHED', 'PUBLISHED', 'is published'),
('DELETED', 'DELETED', 'DELETED'),
)))
@ -272,11 +273,11 @@ class IdentifierChoicesTests(ChoicesTests):
def test_indexing(self):
self.assertEquals(self.STATUS[1], (1, 'is published'))
self.assertEqual(self.STATUS[1], (1, 'is published'))
def test_getattr(self):
self.assertEquals(self.STATUS.DRAFT, 0)
self.assertEqual(self.STATUS.DRAFT, 0)
def test_len(self):
@ -284,12 +285,11 @@ class IdentifierChoicesTests(ChoicesTests):
def test_repr(self):
self.assertEquals(repr(self.STATUS),
"Choices("
"(0, 'DRAFT', 'is draft'), "
"(1, 'PUBLISHED', 'is published'), "
"(2, 'DELETED', 'is deleted'))")
self.assertEqual(repr(self.STATUS), "Choices" + repr((
(0, 'DRAFT', 'is draft'),
(1, 'PUBLISHED', 'is published'),
(2, 'DELETED', 'is deleted'),
)))
class InheritanceManagerTests(TestCase):
@ -309,7 +309,7 @@ class InheritanceManagerTests(TestCase):
InheritanceManagerTestParent(pk=self.child2.pk),
InheritanceManagerTestParent(pk=self.grandchild1.pk),
])
self.assertEquals(set(self.get_manager().all()), children)
self.assertEqual(set(self.get_manager().all()), children)
def test_select_all_subclasses(self):
@ -318,7 +318,7 @@ class InheritanceManagerTests(TestCase):
children.add(self.grandchild1)
else:
children.add(InheritanceManagerTestChild1(pk=self.grandchild1.pk))
self.assertEquals(
self.assertEqual(
set(self.get_manager().select_subclasses()), children)
@ -328,7 +328,7 @@ class InheritanceManagerTests(TestCase):
InheritanceManagerTestParent(pk=self.child2.pk),
InheritanceManagerTestChild1(pk=self.grandchild1.pk),
])
self.assertEquals(
self.assertEqual(
set(
self.get_manager().select_subclasses(
"inheritancemanagertestchild1")
@ -344,7 +344,7 @@ class InheritanceManagerTests(TestCase):
InheritanceManagerTestParent(pk=self.child2.pk),
self.grandchild1,
])
self.assertEquals(
self.assertEqual(
set(
self.get_manager().select_subclasses(
"inheritancemanagertestchild1__"
@ -356,7 +356,7 @@ class InheritanceManagerTests(TestCase):
def test_get_subclass(self):
self.assertEquals(
self.assertEqual(
self.get_manager().get_subclass(pk=self.child1.pk),
self.child1)
@ -422,14 +422,14 @@ class TimeStampedModelTests(TestCase):
def test_created(self):
t1 = TimeStamp.objects.create()
t2 = TimeStamp.objects.create()
self.assert_(t2.created > t1.created)
self.assertTrue(t2.created > t1.created)
def test_modified(self):
t1 = TimeStamp.objects.create()
t2 = TimeStamp.objects.create()
t1.save()
self.assert_(t2.modified < t1.modified)
self.assertTrue(t2.modified < t1.modified)
@ -440,34 +440,34 @@ class TimeFramedModelTests(TestCase):
def test_not_yet_begun(self):
TimeFrame.objects.create(start=self.now+timedelta(days=2))
self.assertEquals(TimeFrame.timeframed.count(), 0)
self.assertEqual(TimeFrame.timeframed.count(), 0)
def test_finished(self):
TimeFrame.objects.create(end=self.now-timedelta(days=1))
self.assertEquals(TimeFrame.timeframed.count(), 0)
self.assertEqual(TimeFrame.timeframed.count(), 0)
def test_no_end(self):
TimeFrame.objects.create(start=self.now-timedelta(days=10))
self.assertEquals(TimeFrame.timeframed.count(), 1)
self.assertEqual(TimeFrame.timeframed.count(), 1)
def test_no_start(self):
TimeFrame.objects.create(end=self.now+timedelta(days=2))
self.assertEquals(TimeFrame.timeframed.count(), 1)
self.assertEqual(TimeFrame.timeframed.count(), 1)
def test_within_range(self):
TimeFrame.objects.create(start=self.now-timedelta(days=1),
end=self.now+timedelta(days=1))
self.assertEquals(TimeFrame.timeframed.count(), 1)
self.assertEqual(TimeFrame.timeframed.count(), 1)
class TimeFrameManagerAddedTests(TestCase):
def test_manager_available(self):
self.assert_(isinstance(TimeFrameManagerAdded.timeframed, QueryManager))
self.assertTrue(isinstance(TimeFrameManagerAdded.timeframed, QueryManager))
def test_conflict_error(self):
@ -488,9 +488,9 @@ class StatusModelTests(TestCase):
def test_created(self):
c1 = self.model.objects.create()
c2 = self.model.objects.create()
self.assert_(c2.status_changed > c1.status_changed)
self.assertEquals(self.model.active.count(), 2)
self.assertEquals(self.model.deleted.count(), 0)
self.assertTrue(c2.status_changed > c1.status_changed)
self.assertEqual(self.model.active.count(), 2)
self.assertEqual(self.model.deleted.count(), 0)
def test_modification(self):
@ -498,16 +498,16 @@ class StatusModelTests(TestCase):
date_created = t1.status_changed
t1.status = self.on_hold
t1.save()
self.assertEquals(self.model.active.count(), 0)
self.assertEquals(self.model.on_hold.count(), 1)
self.assert_(t1.status_changed > date_created)
self.assertEqual(self.model.active.count(), 0)
self.assertEqual(self.model.on_hold.count(), 1)
self.assertTrue(t1.status_changed > date_created)
date_changed = t1.status_changed
t1.save()
self.assertEquals(t1.status_changed, date_changed)
self.assertEqual(t1.status_changed, date_changed)
date_active_again = t1.status_changed
t1.status = self.active
t1.save()
self.assert_(t1.status_changed > date_active_again)
self.assertTrue(t1.status_changed > date_active_again)
@ -521,7 +521,7 @@ class StatusModelPlainTupleTests(StatusModelTests):
class StatusManagerAddedTests(TestCase):
def test_manager_available(self):
self.assert_(isinstance(StatusManagerAdded.active, QueryManager))
self.assertTrue(isinstance(StatusManagerAdded.active, QueryManager))
def test_conflict_error(self):
@ -550,17 +550,17 @@ class QueryManagerTests(TestCase):
def test_passing_kwargs(self):
qs = Post.public.all()
self.assertEquals([p.order for p in qs], [0, 1, 4, 5])
self.assertEqual([p.order for p in qs], [0, 1, 4, 5])
def test_passing_Q(self):
qs = Post.public_confirmed.all()
self.assertEquals([p.order for p in qs], [0, 1])
self.assertEqual([p.order for p in qs], [0, 1])
def test_ordering(self):
qs = Post.public_reversed.all()
self.assertEquals([p.order for p in qs], [5, 4, 1, 0])
self.assertEqual([p.order for p in qs], [5, 4, 1, 0])
@ -575,7 +575,7 @@ if introspector:
def test_introspector_adds_no_excerpt_field(self):
mf = Article._meta.get_field('body')
args, kwargs = introspector(mf)
self.assertEquals(kwargs['no_excerpt_field'], 'True')
self.assertEqual(kwargs['no_excerpt_field'], 'True')
def test_no_excerpt_field_works(self):
@ -654,7 +654,7 @@ class ModelTrackerTestCase(TestCase):
def assertHasChanged(self, **kwargs):
tracker = kwargs.pop('tracker', self.tracker)
for field, value in kwargs.iteritems():
for field, value in kwargs.items():
if value is None:
self.assertRaises(FieldError, tracker.has_changed, field)
else:
@ -662,7 +662,7 @@ class ModelTrackerTestCase(TestCase):
def assertPrevious(self, **kwargs):
tracker = kwargs.pop('tracker', self.tracker)
for field, value in kwargs.iteritems():
for field, value in kwargs.items():
self.assertEqual(tracker.previous(field), value)
def assertChanged(self, **kwargs):
@ -674,7 +674,7 @@ class ModelTrackerTestCase(TestCase):
self.assertEqual(tracker.current(), kwargs)
def update_instance(self, **kwargs):
for field, value in kwargs.iteritems():
for field, value in kwargs.items():
setattr(self.instance, field, value)
self.instance.save()

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.db import models
from django.core.exceptions import FieldError
@ -74,6 +75,6 @@ class ModelInstanceTracker(object):
"""Returns dict of fields that changed since save (with old values)"""
if not self.instance.pk:
return {}
saved = self.saved_data.iteritems()
saved = self.saved_data.items()
current = self.current()
return dict((k, v) for k, v in saved if v != current[k])

View file

@ -15,6 +15,7 @@ setup(
author_email='carl@oddbird.net',
url='https://github.com/carljm/django-model-utils/',
packages=find_packages(),
install_requires=['django>=1.4.2'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
@ -22,6 +23,11 @@ setup(
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Framework :: Django',
],
zip_safe=False,

54
tox.ini
View file

@ -1,5 +1,5 @@
[tox]
envlist=py26-1.2,py26-1.3,py26-1.4,py26,py26-trunk,py27-1.2,py27-1.3,py27-1.4,py27,py27-trunk,py27-nosouth
envlist=py26-1.4,py26,py26-trunk,py27-1.4,py27,py27-trunk,py27-nosouth,py32-1.5-nosouth,py32-trunk-nosouth,py33-1.5-nosouth,py33-trunk-nosouth
[testenv]
deps=
@ -8,20 +8,6 @@ deps=
coverage==3.6
commands=coverage run -a --branch setup.py test
[testenv:py26-1.2]
basepython=python2.6
deps=
django==1.2.7
South==0.7.6
coverage==3.6
[testenv:py26-1.3]
basepython=python2.6
deps=
django==1.3.7
South==0.7.6
coverage==3.6
[testenv:py26-1.4]
basepython=python2.6
deps=
@ -36,20 +22,6 @@ deps=
South==0.7.6
coverage==3.6
[testenv:py27-1.2]
basepython=python2.7
deps=
django==1.2.7
South==0.7.6
coverage==3.6
[testenv:py27-1.3]
basepython=python2.7
deps=
django==1.3.7
South==0.7.6
coverage==3.6
[testenv:py27-1.4]
basepython=python2.7
deps=
@ -64,6 +36,30 @@ deps=
South==0.7.6
coverage==3.6
[testenv:py32-1.5-nosouth]
basepython=python3.2
deps=
django==1.5.0
coverage==3.6
[testenv:py32-trunk-nosouth]
basepython=python3.2
deps=
https://github.com/django/django/tarball/master
coverage==3.6
[testenv:py33-1.5-nosouth]
basepython=python3.3
deps=
django==1.5.0
coverage==3.6
[testenv:py33-trunk-nosouth]
basepython=python3.3
deps=
https://github.com/django/django/tarball/master
coverage==3.6
[testenv:py27-nosouth]
deps=