[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