[Python-modules-commits] [django-model-utils] 01/03: Import django-model-utils_2.3.1.orig.tar.gz
Brian May
bam at moszumanska.debian.org
Mon Oct 12 05:42:57 UTC 2015
This is an automated email from the git hooks/post-receive script.
bam pushed a commit to branch master
in repository django-model-utils.
commit f99aada01eb41c17243384427365ba326dc1fa1d
Author: Brian May <bam at debian.org>
Date: Mon Oct 12 16:19:26 2015 +1100
Import django-model-utils_2.3.1.orig.tar.gz
---
.gitignore | 2 +
.travis.yml | 55 +++++++------
AUTHORS.rst | 5 ++
CHANGES.rst | 60 ++++++++++++++
CONTRIBUTING.rst | 20 +++++
LICENSE.txt | 2 +-
MANIFEST.in | 1 +
Makefile | 6 ++
README.rst | 2 +-
docs/conf.py | 2 +-
docs/managers.rst | 7 ++
docs/utilities.rst | 13 +++
model_utils/__init__.py | 2 +-
model_utils/fields.py | 16 ++++
model_utils/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 760 bytes
model_utils/locale/de/LC_MESSAGES/django.po | 53 +++++++++++++
model_utils/managers.py | 50 ++++++++----
model_utils/models.py | 35 ++++----
model_utils/tests/tests.py | 61 +++++++++++++-
model_utils/tracker.py | 10 +--
runtests.py | 18 +++--
setup.py | 8 +-
tox.ini | 119 +++++-----------------------
runtests.py => translations.py | 24 +++---
update_travis_envs.sh | 13 +++
25 files changed, 396 insertions(+), 188 deletions(-)
diff --git a/.gitignore b/.gitignore
index 83041fe..5f1c259 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@ Django-*.egg
*.pyc
htmlcov/
docs/_build/
+.idea/
+.eggs/
diff --git a/.travis.yml b/.travis.yml
index edaae71..b24d579 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,36 +1,43 @@
language: python
-python:
- - 2.6
- - 2.7
- - 3.2
- - 3.3
+python: 2.7
env:
- - DJANGO=Django==1.4.10 SOUTH=1
- - DJANGO=Django==1.5.5 SOUTH=1
- - DJANGO=Django==1.6.1 SOUTH=1
- - DJANGO=https://github.com/django/django/tarball/master SOUTH=1
+ - TOXENV=py26-django14
+ - TOXENV=py26-django15
+ - TOXENV=py26-django16
+ - TOXENV=py27-django14
+ - TOXENV=py27-django15
+ - TOXENV=py27-django15_nosouth
+ - TOXENV=py27-django16
+ - TOXENV=py27-django17
+ - TOXENV=py27-django18
+ - TOXENV=py27-django_trunk
+ - TOXENV=py32-django15
+ - TOXENV=py32-django16
+ - TOXENV=py32-django17
+ - TOXENV=py32-django18
+ - TOXENV=py32-django_trunk
+ - TOXENV=py33-django15
+ - TOXENV=py33-django16
+ - TOXENV=py33-django17
+ - TOXENV=py33-django18
+ - TOXENV=py33-django_trunk
+ - TOXENV=py34-django17
+ - TOXENV=py34-django18
+ - TOXENV=py34-django_trunk
install:
- - pip install $DJANGO
- - pip install coverage coveralls
- - sh -c "if [ '$SOUTH' = '1' ]; then pip install South==0.8.1; fi"
+ - pip install --upgrade pip setuptools tox virtualenv coveralls
script:
- - coverage run -a setup.py test
- - coverage report
+ - tox
matrix:
- exclude:
- - python: 2.6
- env: DJANGO=https://github.com/django/django/tarball/master SOUTH=1
- - python: 3.2
- env: DJANGO=Django==1.4.10 SOUTH=1
- - python: 3.3
- env: DJANGO=Django==1.4.10 SOUTH=1
- include:
- - python: 2.7
- env: DJANGO=Django==1.5.5 SOUTH=0
+ allow_failures:
+ - env: TOXENV=py27-django_trunk
+ - env: TOXENV=py32-django_trunk
+ - env: TOXENV=py33-django_trunk
+ - env: TOXENV=py34-django_trunk
after_success: coveralls
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 5ee80df..b25a3cf 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -1,9 +1,12 @@
+ad-m <github.com/ad-m>
Alejandro Varas <alej0varas at gmail.com>
Alex Orange <crazycasta at gmail.com>
Andy Freeland <andy at andyfreeland.net>
+Bram Boogaard <b.boogaard at auto-interactive.nl>
Carl Meyer <carl at dirtcircle.com>
Curtis Maloney <curtis at tinbrain.net>
Den Lesnov
+Dmytro Kyrychuk <dmytro.kyrychuck at gmail.com>
Donald Stufft <donald.stufft at gmail.com>
Douglas Meehan <dmeehan at gmail.com>
Facundo Gaich <facugaich at gmail.com>
@@ -21,7 +24,9 @@ Michael van Tellingen <michaelvantellingen at gmail.com>
Mikhail Silonov <silonov.pro>
Patryk Zawadzki <patrys at room-303.com>
Paul McLanahan <paul at mclanahan.net>
+Philipp Steinhardt <steinhardt at myvision.de>
Rinat Shigapov <rinatshigapov at gmail.com>
+Rodney Folz <rodney at rodneyfolz.com>
rsenkbeil <github.com/rsenkbeil>
Ryan Kaskel <dev at ryankaskel.com>
Simon Meers <simon at simonmeers.com>
diff --git a/CHANGES.rst b/CHANGES.rst
index 1a87928..1fc9a62 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,66 @@
CHANGES
=======
+2.3.1 (2015-07-20)
+------------------
+
+* Remove all translation-related automation in `setup.py`. Fixes GH-178 and
+ GH-179. Thanks Joe Weiss, Matt Molyneaux, and others for the reports.
+
+
+2.3 (2015.07.17)
+----------------
+
+* Keep track of deferred fields on model instance instead of on
+ FieldInstanceTracker instance. Fixes accessing deferred fields for multiple
+ instances of a model from the same queryset. Thanks Bram Boogaard. Merge of
+ GH-151.
+
+* Fix Django 1.7 migrations compatibility for SplitField. Thanks ad-m. Merge of
+ GH-157; fixes GH-156.
+
+* Add German translations.
+
+* Django 1.8 compatibility.
+
+
+2.2 (2014.07.31)
+----------------
+
+* Revert GH-130, restoring ability to access ``FieldTracker`` changes in
+ overridden ``save`` methods or ``post_save`` handlers. This reopens GH-83
+ (inability to pickle models with ``FieldTracker``) until a solution can be
+ found that doesn't break behavior otherwise. Thanks Brian May for the
+ report. Fixes GH-143.
+
+
+2.1.1 (2014.07.28)
+------------------
+
+* ASCII-fold all non-ASCII characters in changelog; again. Argh. Apologies to
+ those whose names are mangled by this change. It seems that distutils makes
+ it impossible to handle non-ASCII content reliably under Python 3 in a
+ setup.py long_description, when the system encoding may be ASCII. Thanks
+ Brian May for the report. Fixes GH-141.
+
+
+2.1.0 (2014.07.25)
+------------------
+
+* Add support for Django's built-in migrations to ``MonitorField`` and
+ ``StatusField``.
+
+* ``PassThroughManager`` now has support for seeing exposed methods via
+ ``dir``, allowing `IPython`_ tab completion to be useful. Merge of GH-104,
+ fixes GH-55.
+
+* Add pickle support for models using ``FieldTracker``. Thanks Ondrej Slintak
+ for the report. Thanks Matthew Schinckel for the fix. Merge of GH-130,
+ fixes GH-83.
+
+.. _IPython: http://ipython.org/
+
+
2.0.3 (2014.03.19)
-------------------
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index a789156..6b2b848 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -22,11 +22,31 @@ When creating a pull request, try to:
- Note important changes in the `CHANGES`_ file
- Update the documentation if needed
- Add yourself to the `AUTHORS`_ file
+- If you have added or changed translated strings, run ``make messages`` to
+ update the ``.po`` translation files, and update translations for any
+ languages you know. Then run ``make compilemessages`` to compile the ``.mo``
+ files. If your pull request leaves some translations incomplete, please
+ mention that in the pull request and commit message.
.. _AUTHORS: AUTHORS.rst
.. _CHANGES: CHANGES.rst
+Translations
+------------
+
+If you are able to provide translations for a new language or to update an
+existing translation file, make sure to run makemessages beforehand::
+
+ python django-admin.py makemessages -l ISO_LANGUAGE_CODE
+
+This command will collect all translation strings from the source directory
+and create or update the translation file for the given language. Now open the
+translation file (.po) with a text-editor and start editing.
+After you finished editing add yourself to the list of translators.
+If you have created a new translation, make sure to copy the header from one
+of the existing translation files.
+
Testing
-------
diff --git a/LICENSE.txt b/LICENSE.txt
index ab400d7..0eadf47 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2009-2013, Carl Meyer and contributors
+Copyright (c) 2009-2015, Carl Meyer and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/MANIFEST.in b/MANIFEST.in
index ddc2005..48f0ac9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,3 +4,4 @@ include LICENSE.txt
include MANIFEST.in
include README.rst
include TODO.rst
+locale/*/LC_MESSAGES/django.po
\ No newline at end of file
diff --git a/Makefile b/Makefile
index c736206..4785478 100644
--- a/Makefile
+++ b/Makefile
@@ -13,3 +13,9 @@ docs: documentation
documentation:
python setup.py build_sphinx
+
+messages:
+ python translations.py make
+
+compilemessages:
+ python translations.py compile
diff --git a/README.rst b/README.rst
index 0f76cf9..b7ddd73 100644
--- a/README.rst
+++ b/README.rst
@@ -6,7 +6,7 @@ django-model-utils
:target: http://travis-ci.org/carljm/django-model-utils
.. image:: https://coveralls.io/repos/carljm/django-model-utils/badge.png?branch=master
:target: https://coveralls.io/r/carljm/django-model-utils
-.. image:: https://pypip.in/v/django-model-utils/badge.png
+.. image:: https://img.shields.io/pypi/v/django-model-utils.svg
:target: https://crate.io/packages/django-model-utils
Django model mixins and utilities.
diff --git a/docs/conf.py b/docs/conf.py
index d79adff..9f0c4e7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -41,7 +41,7 @@ master_doc = 'index'
# General information about the project.
project = u'django-model-utils'
-copyright = u'2013, Carl Meyer'
+copyright = u'2015, Carl Meyer'
parent_dir = os.path.dirname(os.path.dirname(__file__))
diff --git a/docs/managers.rst b/docs/managers.rst
index 4ea7b78..0fb3144 100644
--- a/docs/managers.rst
+++ b/docs/managers.rst
@@ -212,3 +212,10 @@ Now you have a "pass through manager" that can also take advantage of
GeoDjango's spatial lookups. You can similarly add additional functionality to
any manager by composing that manager with ``InheritanceManagerMixin`` or
``QueryManagerMixin``.
+
+(Note that any manager class using ``InheritanceManagerMixin`` must return a
+``QuerySet`` class using ``InheritanceQuerySetMixin`` from its ``get_queryset``
+method. This means that if composing ``InheritanceManagerMixin`` and
+``PassThroughManagerMixin``, the ``QuerySet`` class passed to
+``PassThroughManager.for_queryset_class`` must inherit
+``InheritanceQuerySetMixin``.)
diff --git a/docs/utilities.rst b/docs/utilities.rst
index 9f9bb93..e8d22d2 100644
--- a/docs/utilities.rst
+++ b/docs/utilities.rst
@@ -210,3 +210,16 @@ An example using the model specified above:
>>> a.body = 'First post!'
>>> a.title_tracker.changed()
{'title': None}
+
+
+Checking changes using signals
+------------------------------
+
+The field tracker methods may also be used in ``pre_save`` and ``post_save``
+signal handlers to identify field changes on model save.
+
+.. NOTE::
+
+ Due to the implementation of ``FieldTracker``, ``post_save`` signal
+ handlers relying on field tracker methods should only be registered after
+ model creation.
diff --git a/model_utils/__init__.py b/model_utils/__init__.py
index afe66ac..272f4ca 100644
--- a/model_utils/__init__.py
+++ b/model_utils/__init__.py
@@ -1,4 +1,4 @@
from .choices import Choices
from .tracker import FieldTracker, ModelTracker
-__version__ = '2.0.3'
+__version__ = '2.3.1'
diff --git a/model_utils/fields.py b/model_utils/fields.py
index 4daa30a..74c44b2 100644
--- a/model_utils/fields.py
+++ b/model_utils/fields.py
@@ -70,6 +70,11 @@ class StatusField(models.CharField):
self._choices = [(0, 'dummy')]
super(StatusField, self).contribute_to_class(cls, name)
+ def deconstruct(self):
+ name, path, args, kwargs = super(StatusField, self).deconstruct()
+ kwargs['no_check_for_status'] = True
+ return name, path, args, kwargs
+
class MonitorField(models.DateTimeField):
"""
@@ -113,6 +118,13 @@ class MonitorField(models.DateTimeField):
self._save_initial(model_instance.__class__, model_instance)
return super(MonitorField, self).pre_save(model_instance, add)
+ def deconstruct(self):
+ name, path, args, kwargs = super(MonitorField, self).deconstruct()
+ kwargs['monitor'] = self.monitor
+ if self.when is not None:
+ kwargs['when'] = self.when
+ return name, path, args, kwargs
+
SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '<!-- split -->')
@@ -217,6 +229,10 @@ class SplitField(models.TextField):
except AttributeError:
return value
+ def deconstruct(self):
+ name, path, args, kwargs = super(SplitField, self).deconstruct()
+ kwargs['no_excerpt_field'] = True
+ return name, path, args, kwargs
# allow South to handle these fields smoothly
try:
diff --git a/model_utils/locale/de/LC_MESSAGES/django.mo b/model_utils/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..7b80928
Binary files /dev/null and b/model_utils/locale/de/LC_MESSAGES/django.mo differ
diff --git a/model_utils/locale/de/LC_MESSAGES/django.po b/model_utils/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..342b3cf
--- /dev/null
+++ b/model_utils/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,53 @@
+# This file is distributed under the same license as the django-model-utils package.
+#
+# Translators:
+# Philipp Steinhardt <steinhardt at myvision.de>, 2015.
+msgid ""
+msgstr ""
+"Project-Id-Version: django-model-utils\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-20 10:17-0600\n"
+"PO-Revision-Date: 2015-07-01 10:12+0200\n"
+"Last-Translator: Philipp Steinhardt <steinhardt at myvision.de>\n"
+"Language-Team: \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: models.py:20
+msgid "created"
+msgstr "erstellt"
+
+#: models.py:21
+msgid "modified"
+msgstr "bearbeitet"
+
+#: models.py:33
+msgid "start"
+msgstr "Beginn"
+
+#: models.py:34
+msgid "end"
+msgstr "Ende"
+
+#: models.py:49
+msgid "status"
+msgstr "Status"
+
+#: models.py:50
+msgid "status changed"
+msgstr "Status geändert"
+
+#: tests/models.py:106 tests/models.py:115 tests/models.py:124
+msgid "active"
+msgstr "aktiv"
+
+#: tests/models.py:107 tests/models.py:116 tests/models.py:125
+msgid "deleted"
+msgstr "gelöscht"
+
+#: tests/models.py:108 tests/models.py:117 tests/models.py:126
+msgid "on hold"
+msgstr "wartend"
diff --git a/model_utils/managers.py b/model_utils/managers.py
index aac6d7c..b6496ba 100644
--- a/model_utils/managers.py
+++ b/model_utils/managers.py
@@ -8,7 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist
try:
from django.db.models.constants import LOOKUP_SEP
from django.utils.six import string_types
-except ImportError: # Django < 1.5
+except ImportError: # Django < 1.5
from django.db.models.sql.constants import LOOKUP_SEP
string_types = (basestring,)
@@ -53,20 +53,18 @@ class InheritanceQuerySetMixin(object):
new_qs.subclasses = subclasses
return new_qs
-
def _clone(self, klass=None, setup=False, **kwargs):
for name in ['subclasses', '_annotated']:
if hasattr(self, name):
kwargs[name] = getattr(self, name)
- return super(InheritanceQuerySetMixin, self)._clone(klass, setup, **kwargs)
-
+ return super(InheritanceQuerySetMixin, self)._clone(
+ klass, setup, **kwargs)
def annotate(self, *args, **kwargs):
qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs)
qset._annotated = [a.default_alias for a in args] + list(kwargs.keys())
return qset
-
def iterator(self):
iter = super(InheritanceQuerySetMixin, self).iterator()
if getattr(self, 'subclasses', False):
@@ -95,7 +93,6 @@ class InheritanceQuerySetMixin(object):
for obj in iter:
yield obj
-
def _get_subclasses_recurse(self, model, levels=None):
"""
Given a Model class, find all related objects, exploring children
@@ -115,11 +112,11 @@ class InheritanceQuerySetMixin(object):
if levels or levels is None:
for subclass in self._get_subclasses_recurse(
rel.field.model, levels=levels):
- subclasses.append(rel.get_accessor_name() + LOOKUP_SEP + subclass)
+ subclasses.append(
+ rel.get_accessor_name() + LOOKUP_SEP + subclass)
subclasses.append(rel.get_accessor_name())
return subclasses
-
def _get_ancestors_path(self, model, levels=None):
"""
Serves as an opposite to _get_subclasses_recurse, instead walking from
@@ -127,23 +124,27 @@ class InheritanceQuerySetMixin(object):
select_related string backwards.
"""
if not issubclass(model, self.model):
- raise ValueError("%r is not a subclass of %r" % (model, self.model))
+ raise ValueError(
+ "%r is not a subclass of %r" % (model, self.model))
ancestry = []
# should be a OneToOneField or None
- parent = model._meta.get_ancestor_link(self.model)
+ parent_link = model._meta.get_ancestor_link(self.model)
if levels:
levels -= 1
- while parent is not None:
- ancestry.insert(0, parent.related.get_accessor_name())
+ while parent_link is not None:
+ ancestry.insert(0, parent_link.related.get_accessor_name())
if levels or levels is None:
- parent = parent.related.parent_model._meta.get_ancestor_link(
+ if django.VERSION < (1, 8):
+ parent_model = parent_link.related.parent_model
+ else:
+ parent_model = parent_link.related.model
+ parent_link = parent_model._meta.get_ancestor_link(
self.model)
else:
- parent = None
+ parent_link = None
return LOOKUP_SEP.join(ancestry)
-
def _get_sub_obj_recurse(self, obj, s):
rel, _, s = s.partition(LOOKUP_SEP)
try:
@@ -170,6 +171,7 @@ class InheritanceQuerySetMixin(object):
levels = 1
return levels
+
class InheritanceManagerMixin(object):
use_for_related_fields = True
@@ -188,6 +190,7 @@ class InheritanceManagerMixin(object):
class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
pass
+
class InheritanceManager(InheritanceManagerMixin, models.Manager):
pass
@@ -244,6 +247,20 @@ class PassThroughManagerMixin(object):
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()
@@ -257,7 +274,8 @@ class PassThroughManagerMixin(object):
@classmethod
def for_queryset_class(cls, queryset_cls):
- return create_pass_through_manager_for_queryset_class(cls, queryset_cls)
+ return create_pass_through_manager_for_queryset_class(
+ cls, queryset_cls)
class PassThroughManager(PassThroughManagerMixin, models.Manager):
diff --git a/model_utils/models.py b/model_utils/models.py
index 8030bea..f343dfb 100644
--- a/model_utils/models.py
+++ b/model_utils/models.py
@@ -36,6 +36,7 @@ class TimeFramedModel(models.Model):
class Meta:
abstract = True
+
class StatusModel(models.Model):
"""
An abstract base class model with a ``status`` field that
@@ -51,6 +52,7 @@ class StatusModel(models.Model):
class Meta:
abstract = True
+
def add_status_query_managers(sender, **kwargs):
"""
Add a Querymanager for each status item dynamically.
@@ -59,16 +61,15 @@ def add_status_query_managers(sender, **kwargs):
if not issubclass(sender, StatusModel):
return
for value, display in getattr(sender, 'STATUS', ()):
- try:
- sender._meta.get_field(value)
- raise ImproperlyConfigured("StatusModel: Model '%s' has a field "
- "named '%s' which conflicts with a "
- "status of the same name."
- % (sender.__name__, value))
- except FieldDoesNotExist:
- pass
+ if _field_exists(sender, value):
+ raise ImproperlyConfigured(
+ "StatusModel: Model '%s' has a field named '%s' which "
+ "conflicts with a status of the same name."
+ % (sender.__name__, value)
+ )
sender.add_to_class(value, QueryManager(status=value))
+
def add_timeframed_query_manager(sender, **kwargs):
"""
Add a QueryManager for a specific timeframe.
@@ -76,14 +77,12 @@ def add_timeframed_query_manager(sender, **kwargs):
"""
if not issubclass(sender, TimeFramedModel):
return
- try:
- sender._meta.get_field('timeframed')
- raise ImproperlyConfigured("Model '%s' has a field named "
- "'timeframed' which conflicts with "
- "the TimeFramedModel manager."
- % sender.__name__)
- except FieldDoesNotExist:
- pass
+ if _field_exists(sender, 'timeframed'):
+ raise ImproperlyConfigured(
+ "Model '%s' has a field named 'timeframed' "
+ "which conflicts with the TimeFramedModel manager."
+ % sender.__name__
+ )
sender.add_to_class('timeframed', QueryManager(
(models.Q(start__lte=now) | models.Q(start__isnull=True)) &
(models.Q(end__gte=now) | models.Q(end__isnull=True))
@@ -92,3 +91,7 @@ def add_timeframed_query_manager(sender, **kwargs):
models.signals.class_prepared.connect(add_status_query_managers)
models.signals.class_prepared.connect(add_timeframed_query_manager)
+
+
+def _field_exists(model_class, field_name):
+ return field_name in [f.attname for f in model_class._meta.local_fields]
diff --git a/model_utils/tests/tests.py b/model_utils/tests/tests.py
index 63b16d1..bbacfd1 100644
--- a/model_utils/tests/tests.py
+++ b/model_utils/tests/tests.py
@@ -12,6 +12,7 @@ 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.core.management import call_command
from django.test import TestCase
from model_utils import Choices, FieldTracker
@@ -31,6 +32,12 @@ from model_utils.tests.models import (
InheritanceManagerTestChild3, StatusFieldChoicesName)
+class MigrationsTests(TestCase):
+ @skipUnless(django.VERSION >= (1, 7, 0), "test only applies to Django 1.7+")
+ def test_makemigrations(self):
+ call_command('makemigrations', dry_run=True)
+
+
class GetExcerptTests(TestCase):
def test_split(self):
e = get_excerpt("some content\n\n<!-- split -->\n\nsome more")
@@ -1235,6 +1242,40 @@ class PassThroughManagerTests(TestCase):
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')
@@ -1444,13 +1485,13 @@ class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests):
self.instance.number = 1
self.instance.save()
item = list(self.tracked_class.objects.only('name').all())[0]
- self.assertTrue(item.tracker.deferred_fields)
+ self.assertTrue(item._deferred_fields)
self.assertEqual(item.tracker.previous('number'), None)
- self.assertTrue('number' in item.tracker.deferred_fields)
+ self.assertTrue('number' in item._deferred_fields)
self.assertEqual(item.number, 1)
- self.assertTrue('number' not in item.tracker.deferred_fields)
+ self.assertTrue('number' not in item._deferred_fields)
self.assertEqual(item.tracker.previous('number'), 1)
self.assertFalse(item.tracker.has_changed('number'))
@@ -1458,6 +1499,20 @@ class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests):
self.assertTrue(item.tracker.has_changed('number'))
+class FieldTrackerMultipleInstancesTests(TestCase):
+
+ def test_with_deferred_fields_access_multiple(self):
+ instances = [
+ Tracked.objects.create(pk=1, name='foo', number=1),
+ Tracked.objects.create(pk=2, name='bar', number=2)
+ ]
+
+ queryset = Tracked.objects.only('id')
+
+ for instance in queryset:
+ name = instance.name
+
+
class FieldTrackedModelCustomTests(FieldTrackerTestCase,
FieldTrackerCommonTests):
diff --git a/model_utils/tracker.py b/model_utils/tracker.py
index 6cb4355..be5a7ff 100644
--- a/model_utils/tracker.py
+++ b/model_utils/tracker.py
@@ -32,10 +32,10 @@ class FieldInstanceTracker(object):
def current(self, fields=None):
"""Returns dict of current values for all tracked fields"""
if fields is None:
- if self.deferred_fields:
+ if self.instance._deferred_fields:
fields = [
field for field in self.fields
- if field not in self.deferred_fields
+ if field not in self.instance._deferred_fields
]
else:
fields = self.fields
@@ -62,7 +62,7 @@ class FieldInstanceTracker(object):
)
def init_deferred_fields(self):
- self.deferred_fields = []
+ self.instance._deferred_fields = []
if not self.instance._deferred:
return
@@ -70,7 +70,7 @@ class FieldInstanceTracker(object):
def __get__(field, instance, owner):
data = instance.__dict__
if data.get(field.field_name, field) is field:
- self.deferred_fields.remove(field.field_name)
+ instance._deferred_fields.remove(field.field_name)
value = super(DeferredAttributeTracker, field).__get__(
instance, owner)
self.saved_data[field.field_name] = deepcopy(value)
@@ -79,7 +79,7 @@ class FieldInstanceTracker(object):
for field in self.fields:
field_obj = self.instance.__class__.__dict__.get(field)
if isinstance(field_obj, DeferredAttribute):
- self.deferred_fields.append(field)
+ self.instance._deferred_fields.append(field)
# Django 1.4
model = None
diff --git a/runtests.py b/runtests.py
index 7522e87..a1f4d93 100755
--- a/runtests.py
+++ b/runtests.py
@@ -16,10 +16,11 @@ DEFAULT_SETTINGS = dict(
"ENGINE": "django.db.backends.sqlite3"
}
},
+ SILENCED_SYSTEM_CHECKS=["1_7.W001"],
)
-def runtests(*test_args):
+def runtests():
if not settings.configured:
settings.configure(**DEFAULT_SETTINGS)
@@ -27,14 +28,19 @@ def runtests(*test_args):
if hasattr(django, 'setup'):
django.setup()
- if not test_args:
- test_args = ['tests']
-
parent = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, parent)
- from django.test.simple import DjangoTestSuiteRunner
- failures = DjangoTestSuiteRunner(
+ try:
+ from django.test.runner import DiscoverRunner
+ runner_class = DiscoverRunner
+ test_args = ['model_utils.tests']
+ except ImportError:
+ from django.test.simple import DjangoTestSuiteRunner
+ runner_class = DjangoTestSuiteRunner
+ test_args = ['tests']
+
+ failures = runner_class(
verbosity=1, interactive=True, failfast=False).run_tests(test_args)
sys.exit(failures)
diff --git a/setup.py b/setup.py
index 25fca37..a1b49b6 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,6 @@ def get_version():
if line.startswith('__version__ ='):
return line.split('=')[1].strip().strip('"\'')
-
setup(
name='django-model-utils',
version=get_version(),
@@ -40,5 +39,10 @@ setup(
],
zip_safe=False,
tests_require=["Django>=1.4.2"],
- test_suite='runtests.runtests'
+ test_suite='runtests.runtests',
+ package_data={
+ 'model_utils': [
+ 'locale/*/LC_MESSAGES/django.po','locale/*/LC_MESSAGES/django.mo'
+ ],
+ },
)
diff --git a/tox.ini b/tox.ini
index b754bc7..5255cf5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,109 +1,26 @@
[tox]
envlist =
- py26-1.4, py26-1.5, py26-1.6,
- py27-1.4, py27-1.5, py27-1.6, py27-trunk, py27-1.5-nosouth,
- py32-1.5, py32-1.6, py32-trunk,
- py33-1.5, py33-1.6, py33-trunk
+ py26-django{14,15,16},
+ py27-django14, py27-django15_nosouth,
+ py{27,32,33}-django{15,16,17,18,_trunk},
+ py34-django{17,18,_trunk},
[testenv]
-deps =
- South == 0.8.1
- coverage == 3.6
-commands = coverage run -a setup.py test
-
-[testenv:py26-1.4]
-basepython = python2.6
-deps =
- Django == 1.4.10
- South == 0.7.6
- coverage == 3.6
-
-[testenv:py26-1.5]
-basepython = python2.6
-deps =
- Django == 1.5.5
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py26-1.6]
-basepython = python2.6
-deps =
- https://github.com/django/django/tarball/stable/1.6.x
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py27-1.4]
-basepython = python2.7
-deps =
- Django == 1.4.10
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py27-1.5]
-basepython = python2.7
-deps =
- Django == 1.5.5
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py27-1.6]
-basepython = python2.7
-deps =
- Django == 1.6.1
- South == 0.8.1
- coverage == 3.6
+basepython =
+ py26: python2.6
+ py27: python2.7
+ py32: python3.2
+ py33: python3.3
+ py34: python3.4
-[testenv:py27-trunk]
-basepython = python2.7
deps =
- https://github.com/django/django/tarball/master
- South == 0.8.1
coverage == 3.6
+ django14: Django==1.4.18
+ django15{,_nosouth}: Django==1.5.12
+ django16: Django==1.6.10
+ django17: Django==1.7.3
+ django18: Django==1.8a1
+ django_trunk: https://github.com/django/django/tarball/master
+ django{14,15,16}: South==1.0.2
-[testenv:py27-1.5-nosouth]
-basepython = python2.7
-deps =
- Django == 1.5.5
- coverage == 3.6
-
-[testenv:py32-1.5]
-basepython = python3.2
-deps =
- Django == 1.5.5
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py32-1.6]
-basepython = python3.2
-deps =
- Django == 1.6.1
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py32-trunk]
-basepython = python3.2
-deps =
- https://github.com/django/django/tarball/master
- South == 0.8.1
- coverage == 3.6
-
-[testenv:py33-1.5]
-basepython = python3.3
-deps =
... 93 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-model-utils.git
More information about the Python-modules-commits
mailing list