[Python-modules-commits] [django-model-utils] 01/05: New upstream version 3.0.0
Brian May
bam at moszumanska.debian.org
Sun Jul 2 22:27:47 UTC 2017
This is an automated email from the git hooks/post-receive script.
bam pushed a commit to branch debian/master
in repository django-model-utils.
commit 9c39ae8052402c292fcf33bb659e3ec50d18457a
Author: Brian May <bam at debian.org>
Date: Mon Jul 3 08:15:51 2017 +1000
New upstream version 3.0.0
---
.github/ISSUE_TEMPLATE.md | 14 +
.github/PULL_REQUEST_TEMPLATE.md | 15 +
.travis.yml | 32 +-
AUTHORS.rst | 3 +
CHANGES.rst | 42 +
README.rst | 5 +-
docs/managers.rst | 61 +-
docs/models.rst | 8 +
docs/setup.rst | 4 +-
model_utils/__init__.py | 2 +-
model_utils/fields.py | 32 +-
model_utils/managers.py | 116 +-
model_utils/models.py | 39 +-
model_utils/tests/tests.py | 1873 --------------------
model_utils/tracker.py | 87 +-
requirements.txt | 2 +
runtests.py | 26 +-
setup.py | 9 +-
{model_utils/tests => tests}/__init__.py | 0
{model_utils/tests => tests}/fields.py | 0
tests/managers.py | 15 +
{model_utils/tests => tests}/models.py | 107 +-
tests/test_choices.py | 261 +++
.../tests => tests/test_fields}/__init__.py | 0
tests/test_fields/test_field_tracker.py | 728 ++++++++
tests/test_fields/test_monitor_field.py | 120 ++
tests/test_fields/test_split_field.py | 78 +
tests/test_fields/test_status_field.py | 32 +
.../tests => tests/test_managers}/__init__.py | 0
tests/test_managers/test_inheritance_manager.py | 456 +++++
tests/test_managers/test_query_manager.py | 29 +
tests/test_managers/test_softdelete_manager.py | 28 +
tests/test_managers/test_status_manager.py | 23 +
tests/test_miscellaneous.py | 29 +
.../tests => tests/test_models}/__init__.py | 0
tests/test_models/test_softdeletable_model.py | 52 +
tests/test_models/test_status_model.py | 70 +
tests/test_models/test_timeframed_model.py | 47 +
tests/test_models/test_timestamped_model.py | 25 +
tox.ini | 21 +-
translations.py | 12 +-
41 files changed, 2394 insertions(+), 2109 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..f384dde
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,14 @@
+## Problem
+
+Explain the problem you encountered.
+
+## Environment
+
+- Django Model Utils version:
+- Django version:
+- Python version:
+- Other libraries used, if any:
+
+## Code examples
+
+Give code example that demonstrates the issue, or even better, write new tests that fails because of that issue.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..63db703
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,15 @@
+## Problem
+
+Explain the problem you are fixing (add the link to the related issue(s), if any).
+
+## Solution
+
+Explain the solution that has been implemented, and what has been changed.
+
+## Commandments
+
+- [ ] Write PEP8 compliant code.
+- [ ] Cover it with tests.
+- [ ] Update `CHANGES.rst` file to describe the changes, and quote according issue with `GH-<issue_number>`.
+- [ ] Pay attention to backward compatibility, or if it breaks it, explain why.
+- [ ] Update documentation (if relevant).
diff --git a/.travis.yml b/.travis.yml
index 6ea0c92..5e01979 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,31 +3,22 @@ language: python
python: 2.7
env:
- - TOXENV=py26-django14
- - TOXENV=py26-django15
- - TOXENV=py26-django16
- - TOXENV=py27-django110
- - TOXENV=py27-django14
- - TOXENV=py27-django15
- - TOXENV=py27-django15_nosouth
- - TOXENV=py27-django16
- - TOXENV=py27-django17
- TOXENV=py27-django18
- TOXENV=py27-django19
- - TOXENV=py27-django_trunk
- - TOXENV=py33-django15
- - TOXENV=py33-django16
- - TOXENV=py33-django17
+ - TOXENV=py27-django110
+ - TOXENV=py27-django111
- TOXENV=py33-django18
- - TOXENV=py34-django110
- - TOXENV=py34-django17
- TOXENV=py34-django18
- TOXENV=py34-django19
- - TOXENV=py34-django_trunk
- - TOXENV=py35-django110
+ - TOXENV=py34-django110
+ - TOXENV=py34-django111
- TOXENV=py35-django18
- TOXENV=py35-django19
+ - TOXENV=py35-django110
+ - TOXENV=py35-django111
- TOXENV=py35-django_trunk
+ - TOXENV=py36-django111
+ - TOXENV=py36-django_trunk
install:
- pip install --upgrade pip setuptools tox virtualenv coveralls
@@ -37,11 +28,8 @@ script:
matrix:
allow_failures:
- - env: TOXENV=py27-django110
- - env: TOXENV=py34-django110
- - env: TOXENV=py35-django110
- - env: TOXENV=py27-django_trunk
- - env: TOXENV=py34-django_trunk
- env: TOXENV=py35-django_trunk
+ - env: TOXENV=py36-django111
+ - env: TOXENV=py36-django_trunk
after_success: coveralls
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 668453e..4dd3044 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -1,6 +1,7 @@
ad-m <github.com/ad-m>
Alejandro Varas <alej0varas at gmail.com>
Alex Orange <crazycasta at gmail.com>
+Alexey Evseev <myhappydo at gmail.com>
Andy Freeland <andy at andyfreeland.net>
Artis Avotins <artis.avotins at gmail.com>
Bram Boogaard <b.boogaard at auto-interactive.nl>
@@ -30,6 +31,7 @@ Paul McLanahan <paul at mclanahan.net>
Philipp Steinhardt <steinhardt at myvision.de>
Rinat Shigapov <rinatshigapov at gmail.com>
Rodney Folz <rodney at rodneyfolz.com>
+Romain Garrigues <github.com/romgar>
rsenkbeil <github.com/rsenkbeil>
Ryan Kaskel <dev at ryankaskel.com>
Simon Meers <simon at simonmeers.com>
@@ -39,3 +41,4 @@ Travis Swicegood <travis at domain51.com>
Trey Hunner <trey at treyhunner.com>
Karl Wan Nan Wo <karl.wnw at gmail.com>
zyegfryed
+Radosław Jan Ganczarek <radoslaw at ganczarek.in>
diff --git a/CHANGES.rst b/CHANGES.rst
index fa0bd0e..efa85a9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,48 @@
CHANGES
=======
+3.0.0 (2017.04.13)
+------------------
+
+* Drop support for Python 2.6.
+* Drop support for Django 1.4, 1.5, 1.6, 1.7.
+* Exclude tests from the distribution, fixes GH-258.
+* Add support for Django 1.11 GH-269
+
+
+2.6.1 (2017.01.11)
+------------------
+
+* Fix infinite recursion with multiple `MonitorField` and `defer()` or `only()`
+ on Django 1.10+. Thanks Romain Garrigues. Merge of GH-242, fixes GH-241.
+
+* Fix `InheritanceManager` and `SoftDeletableManager` to respect
+ `self._queryset_class` instead of hardcoding the queryset class. Merge of
+ GH-250, fixes GH-249.
+
+* Add mixins for `SoftDeletableQuerySet` and `SoftDeletableManager`, as stated
+ in the the documentation.
+
+* Fix `SoftDeletableModel.delete()` to use the correct database connection.
+ Merge of GH-239.
+
+* Added boolean keyword argument `soft` to `SoftDeletableModel.delete()` that
+ revert to default behavior when set to `False`. Merge of GH-240.
+
+* Enforced default manager in `StatusModel` to avoid manager order issues when
+ using abstract models that redefine `objects` manager. Merge of GH-253, fixes
+ GH-251.
+
+
+2.6 (2016.09.19)
+----------------
+
+* Added `SoftDeletableModel` abstract class, its manageer
+ `SoftDeletableManager` and queryset `SoftDeletableQuerySet`.
+
+* Fix issue with field tracker and deferred FileField for Django 1.10.
+
+
2.5.2 (2016.08.09)
------------------
diff --git a/README.rst b/README.rst
index 001712f..9ce6788 100644
--- a/README.rst
+++ b/README.rst
@@ -11,9 +11,8 @@ django-model-utils
Django model mixins and utilities.
-``django-model-utils`` supports `Django`_ 1.4 through 1.9 (latest bugfix
-release in each series only) on Python 2.6 (through Django 1.6 only), 2.7, 3.3
-(through Django 1.8 only), 3.4 and 3.5.
+``django-model-utils`` supports `Django`_ 1.8 through 1.10 (latest bugfix
+release in each series only) on Python 2.7, 3.3 (Django 1.8 only), 3.4 and 3.5.
.. _Django: http://www.djangoproject.com/
diff --git a/docs/managers.rst b/docs/managers.rst
index 2669a1c..43aa030 100644
--- a/docs/managers.rst
+++ b/docs/managers.rst
@@ -84,14 +84,7 @@ If you don't explicitly call ``select_subclasses()`` or ``get_subclass()``,
an ``InheritanceManager`` behaves identically to a normal ``Manager``; so
it's safe to use as your default manager for the model.
-.. note::
-
- Due to `Django bug #16572`_, on Django versions prior to 1.6
- ``InheritanceManager`` only supports a single level of model inheritance;
- it won't work for grandchild models.
-
.. _contributed by Jeff Elmore: http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
-.. _Django bug #16572: https://code.djangoproject.com/ticket/16572
.. _QueryManager:
@@ -124,57 +117,19 @@ set the ordering of the ``QuerySet`` returned by the ``QueryManager``
by chaining a call to ``.order_by()`` on the ``QueryManager`` (this is
not required).
+SoftDeletableManager
+--------------------
-PassThroughManager
-------------------
-
-`PassThroughManager` was removed in django-model-utils 2.4. Use Django's
-built-in `QuerySet.as_manager()` and/or `Manager.from_queryset()` utilities
-instead.
+Returns only model instances that have the ``is_removed`` field set
+to False. Uses ``SoftDeletableQuerySet``, which ensures model instances
+won't be removed in bulk, but they will be marked as removed instead.
Mixins
------
Each of the above manager classes has a corresponding mixin that can be used to
-add functionality to any manager. For example, to create a GeoDjango
-``GeoManager`` that includes "pass through" functionality, you can write the
-following code:
-
-.. code-block:: python
-
- from django.contrib.gis.db import models
- from django.contrib.gis.db.models.query import GeoQuerySet
-
- from model_utils.managers import PassThroughManagerMixin
-
- class PassThroughGeoManager(PassThroughManagerMixin, models.GeoManager):
- pass
-
- class LocationQuerySet(GeoQuerySet):
- def within_boundary(self, geom):
- return self.filter(point__within=geom)
-
- def public(self):
- return self.filter(public=True)
-
- class Location(models.Model):
- point = models.PointField()
- public = models.BooleanField(default=True)
- objects = PassThroughGeoManager.for_queryset_class(LocationQuerySet)()
-
- Location.objects.public()
- Location.objects.within_boundary(geom=geom)
- Location.objects.within_boundary(geom=geom).public()
-
-
-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``.
+add functionality to any manager.
-(Note that any manager class using ``InheritanceManagerMixin`` must return a
+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``.)
+method.
diff --git a/docs/models.rst b/docs/models.rst
index 7a05c79..51bde8f 100644
--- a/docs/models.rst
+++ b/docs/models.rst
@@ -47,3 +47,11 @@ returns objects with that status only:
# this query will only return published articles:
Article.published.all()
+
+
+SoftDeletableModel
+------------------
+
+This abstract base class just provides field ``is_removed`` which is
+set to True instead of removing the instance. Entities returned in
+default manager are limited to not-deleted instances.
diff --git a/docs/setup.rst b/docs/setup.rst
index 5621649..db2a34e 100644
--- a/docs/setup.rst
+++ b/docs/setup.rst
@@ -17,7 +17,7 @@ modify your ``INSTALLED_APPS`` setting.
Dependencies
============
-``django-model-utils`` supports `Django`_ 1.4.2 and later on Python 2.6, 2.7,
-3.2, and 3.3.
+``django-model-utils`` supports `Django`_ 1.8 through 1.10 (latest bugfix
+release in each series only) on Python 2.7, 3.3 (Django 1.8 only), 3.4 and 3.5.
.. _Django: http://www.djangoproject.com/
diff --git a/model_utils/__init__.py b/model_utils/__init__.py
index e42aa57..54c239c 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.5.2'
+__version__ = '3.0.0'
diff --git a/model_utils/fields.py b/model_utils/fields.py
index 805c707..f308706 100644
--- a/model_utils/fields.py
+++ b/model_utils/fields.py
@@ -110,6 +110,9 @@ class MonitorField(models.DateTimeField):
return getattr(instance, self.monitor)
def _save_initial(self, sender, instance, **kwargs):
+ if django.VERSION >= (1, 10) and self.monitor in instance.get_deferred_fields():
+ # Fix related to issue #241 to avoid recursive error on double monitor fields
+ return
setattr(instance, self.monitor_attname,
self.get_monitored_value(instance))
@@ -225,7 +228,7 @@ class SplitField(models.TextField):
return value.content
def value_to_string(self, obj):
- value = self._get_val_from_obj(obj)
+ value = self.value_from_object(obj)
return value.content
def get_prep_value(self, value):
@@ -238,30 +241,3 @@ class SplitField(models.TextField):
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:
- from south.modelsinspector import add_introspection_rules
- # For a normal MarkupField, the add_excerpt_field attribute is
- # always True, which means no_excerpt_field arg will always be
- # True in a frozen MarkupField, which is what we want.
- add_introspection_rules(rules=[
- (
- (SplitField,),
- [],
- {'no_excerpt_field': ('add_excerpt_field', {})}
- ),
- (
- (MonitorField,),
- [],
- {'monitor': ('monitor', {})}
- ),
- (
- (StatusField,),
- [],
- {'no_check_for_status': ('check_for_status', {})}
- ),
- ], patterns=['model_utils\.fields\.'])
-except ImportError:
- pass
-
diff --git a/model_utils/managers.py b/model_utils/managers.py
index 4f06d2e..9dc68a2 100644
--- a/model_utils/managers.py
+++ b/model_utils/managers.py
@@ -3,17 +3,54 @@ import django
from django.db import models
from django.db.models.fields.related import OneToOneField, OneToOneRel
from django.db.models.query import QuerySet
+try:
+ from django.db.models.query import BaseIterable, ModelIterable
+except ImportError:
+ # Django 1.8 does not have iterable classes
+ BaseIterable = object
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
- from django.db.models.sql.constants import LOOKUP_SEP
- string_types = (basestring,)
+from django.db.models.constants import LOOKUP_SEP
+from django.utils.six import string_types
+
+
+class InheritanceIterable(BaseIterable):
+ def __iter__(self):
+ queryset = self.queryset
+ iter = ModelIterable(queryset)
+ if getattr(queryset, 'subclasses', False):
+ extras = tuple(queryset.query.extra.keys())
+ # sort the subclass names longest first,
+ # so with 'a' and 'a__b' it goes as deep as possible
+ subclasses = sorted(queryset.subclasses, key=len, reverse=True)
+ for obj in iter:
+ sub_obj = None
+ for s in subclasses:
+ sub_obj = queryset._get_sub_obj_recurse(obj, s)
+ if sub_obj:
+ break
+ if not sub_obj:
+ sub_obj = obj
+
+ if getattr(queryset, '_annotated', False):
+ for k in queryset._annotated:
+ setattr(sub_obj, k, getattr(obj, k))
+
+ for k in extras:
+ setattr(sub_obj, k, getattr(obj, k))
+
+ yield sub_obj
+ else:
+ for obj in iter:
+ yield obj
class InheritanceQuerySetMixin(object):
+ def __init__(self, *args, **kwargs):
+ super(InheritanceQuerySetMixin, self).__init__(*args, **kwargs)
+ if django.VERSION > (1, 8):
+ self._iterable_class = InheritanceIterable
+
def select_subclasses(self, *subclasses):
levels = self._get_maximum_depth()
calculated_subclasses = self._get_subclasses_recurse(
@@ -68,6 +105,7 @@ class InheritanceQuerySetMixin(object):
return qset
def iterator(self):
+ # Maintained for Django 1.8 compatability
iter = super(InheritanceQuerySetMixin, self).iterator()
if getattr(self, 'subclasses', False):
extras = tuple(self.query.extra.keys())
@@ -143,10 +181,10 @@ class InheritanceQuerySetMixin(object):
if levels:
levels -= 1
while parent_link is not None:
- if django.VERSION < (1, 8):
- related = parent_link.related
- else:
+ if django.VERSION < (1, 9):
related = parent_link.rel
+ else:
+ related = parent_link.remote_field
ancestry.insert(0, related.get_accessor_name())
if levels or levels is None:
if django.VERSION < (1, 8):
@@ -192,13 +230,16 @@ class InheritanceQuerySetMixin(object):
return levels
+class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
+ pass
+
+
class InheritanceManagerMixin(object):
use_for_related_fields = True
+ _queryset_class = InheritanceQuerySet
def get_queryset(self):
- return InheritanceQuerySet(self.model)
-
- get_query_set = get_queryset
+ return self._queryset_class(self.model)
def select_subclasses(self, *subclasses):
return self.get_queryset().select_subclasses(*subclasses)
@@ -207,10 +248,6 @@ class InheritanceManagerMixin(object):
return self.get_queryset().get_subclass(*args, **kwargs)
-class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
- pass
-
-
class InheritanceManager(InheritanceManagerMixin, models.Manager):
pass
@@ -231,16 +268,51 @@ class QueryManagerMixin(object):
return self
def get_queryset(self):
- try:
- qs = super(QueryManagerMixin, self).get_queryset().filter(self._q)
- except AttributeError:
- qs = super(QueryManagerMixin, self).get_query_set().filter(self._q)
+ qs = super(QueryManagerMixin, self).get_queryset().filter(self._q)
if self._order_by is not None:
return qs.order_by(*self._order_by)
return qs
- get_query_set = get_queryset
-
class QueryManager(QueryManagerMixin, models.Manager):
pass
+
+
+class SoftDeletableQuerySetMixin(object):
+ """
+ QuerySet for SoftDeletableModel. Instead of removing instance sets
+ its ``is_removed`` field to True.
+ """
+
+ def delete(self):
+ """
+ Soft delete objects from queryset (set their ``is_removed``
+ field to True)
+ """
+ self.update(is_removed=True)
+
+
+class SoftDeletableQuerySet(SoftDeletableQuerySetMixin, QuerySet):
+ pass
+
+
+class SoftDeletableManagerMixin(object):
+ """
+ Manager that limits the queryset by default to show only not removed
+ instances of model.
+ """
+ _queryset_class = SoftDeletableQuerySet
+
+ def get_queryset(self):
+ """
+ Return queryset limited to not removed entries.
+ """
+ kwargs = {'model': self.model, 'using': self._db}
+ if hasattr(self, '_hints'):
+ kwargs['hints'] = self._hints
+
+ return self._queryset_class(**kwargs).filter(is_removed=False)
+
+
+class SoftDeletableManager(SoftDeletableManagerMixin, models.Manager):
+ pass
diff --git a/model_utils/models.py b/model_utils/models.py
index 3db4073..c679fc6 100644
--- a/model_utils/models.py
+++ b/model_utils/models.py
@@ -1,16 +1,16 @@
from __future__ import unicode_literals
import django
+from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.translation import ugettext_lazy as _
-from django.core.exceptions import ImproperlyConfigured
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, SoftDeletableManager
from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
StatusField, MonitorField
@@ -64,6 +64,11 @@ def add_status_query_managers(sender, **kwargs):
"""
if not issubclass(sender, StatusModel):
return
+
+ if django.VERSION >= (1, 10):
+ # First, get current manager name...
+ default_manager = sender._meta.default_manager
+
for value, display in getattr(sender, 'STATUS', ()):
if _field_exists(sender, value):
raise ImproperlyConfigured(
@@ -73,6 +78,10 @@ def add_status_query_managers(sender, **kwargs):
)
sender.add_to_class(value, QueryManager(status=value))
+ if django.VERSION >= (1, 10):
+ # ...then, put it back, as add_to_class is modifying the default manager!
+ sender._meta.default_manager_name = default_manager.name
+
def add_timeframed_query_manager(sender, **kwargs):
"""
@@ -99,3 +108,29 @@ 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]
+
+
+class SoftDeletableModel(models.Model):
+ """
+ An abstract base class model with a ``is_removed`` field that
+ marks entries that are not going to be used anymore, but are
+ kept in db for any reason.
+ Default manager returns only not-removed entries.
+ """
+ is_removed = models.BooleanField(default=False)
+
+ class Meta:
+ abstract = True
+
+ objects = SoftDeletableManager()
+
+ def delete(self, using=None, soft=True, *args, **kwargs):
+ """
+ Soft delete object (set its ``is_removed`` field to True).
+ Actually delete object if setting ``soft`` to False.
+ """
+ if soft:
+ self.is_removed = True
+ self.save(using=using)
+ else:
+ return super(SoftDeletableModel, self).delete(using=using, *args, **kwargs)
diff --git a/model_utils/tests/tests.py b/model_utils/tests/tests.py
deleted file mode 100644
index 14b6329..0000000
--- a/model_utils/tests/tests.py
+++ /dev/null
@@ -1,1873 +0,0 @@
-from __future__ import unicode_literals
-
-from datetime import datetime, timedelta
-try:
- from unittest import skipUnless
-except ImportError: # Python 2.6
- from django.utils.unittest import skipUnless
-
-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.core.management import call_command
-from django.test import TestCase
-
-from model_utils import Choices, FieldTracker
-from model_utils.fields import get_excerpt, MonitorField, StatusField
-from model_utils.managers import QueryManager
-from model_utils.models import StatusModel, TimeFramedModel
-from model_utils.tests.models import (
- InheritanceManagerTestRelated, InheritanceManagerTestGrandChild1,
- InheritanceManagerTestGrandChild1_2,
- InheritanceManagerTestParent, InheritanceManagerTestChild1,
- InheritanceManagerTestChild2, TimeStamp, Post, Article, Status,
- StatusPlainTuple, TimeFrame, Monitored, MonitorWhen, MonitorWhenEmpty, StatusManagerAdded,
- TimeFrameManagerAdded, SplitFieldAbstractParent,
- ModelTracked, ModelTrackedFK, ModelTrackedNotDefault, ModelTrackedMultiple, InheritedModelTracked,
- Tracked, TrackedFK, InheritedTrackedFK, TrackedNotDefault, TrackedNonFieldAttr, TrackedMultiple,
- InheritedTracked, StatusFieldDefaultFilled, StatusFieldDefaultNotFilled,
- 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")
- self.assertEqual(e, 'some content\n')
-
-
- def test_auto_split(self):
- e = get_excerpt("para one\n\npara two\n\npara three")
- self.assertEqual(e, 'para one\n\npara two')
-
-
- def test_middle_of_para(self):
- e = get_excerpt("some text\n<!-- split -->\nmore text")
- self.assertEqual(e, 'some text')
-
-
- def test_middle_of_line(self):
- e = get_excerpt("some text <!-- split --> more text")
- self.assertEqual(e, "some text <!-- split --> more text")
-
-
-
-class SplitFieldTests(TestCase):
- full_text = 'summary\n\n<!-- split -->\n\nmore'
- excerpt = 'summary\n'
-
-
- def setUp(self):
- self.post = Article.objects.create(
- title='example post', body=self.full_text)
-
-
- def test_unicode_content(self):
- self.assertEqual(text_type(self.post.body), self.full_text)
-
-
- def test_excerpt(self):
- self.assertEqual(self.post.body.excerpt, self.excerpt)
-
-
- def test_content(self):
- self.assertEqual(self.post.body.content, self.full_text)
-
-
- def test_has_more(self):
- 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.assertFalse(post.body.has_more)
-
-
- def test_load_back(self):
- post = Article.objects.get(pk=self.post.pk)
- 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 = 'different\n\n<!-- split -->\n\nother'
- self.post.body = new_text
- self.post.save()
- self.assertEqual(text_type(self.post.body), new_text)
-
-
- def test_assign_to_content(self):
- new_text = 'different\n\n<!-- split -->\n\nother'
- self.post.body.content = new_text
- self.post.save()
- self.assertEqual(text_type(self.post.body), new_text)
-
-
- def test_assign_to_excerpt(self):
- with self.assertRaises(AttributeError):
- self.post.body.excerpt = 'this should fail'
-
-
- def test_access_via_class(self):
- with self.assertRaises(AttributeError):
- Article.body
-
-
- def test_none(self):
- a = Article(title='Some Title', body=None)
- self.assertEqual(a.body, None)
-
-
- def test_assign_splittext(self):
- a = Article(title='Some Title')
- a.body = self.post.body
- self.assertEqual(a.body.excerpt, 'summary\n')
-
-
- def test_value_to_string(self):
- f = self.post._meta.get_field('body')
- self.assertEqual(f.value_to_string(self.post), self.full_text)
-
-
- def test_abstract_inheritance(self):
- class Child(SplitFieldAbstractParent):
- pass
-
- self.assertEqual(
- [f.name for f in Child._meta.fields],
- ["id", "content", "_content_excerpt"])
-
-
-
-class MonitorFieldTests(TestCase):
- def setUp(self):
- self.instance = Monitored(name='Charlie')
- self.created = self.instance.name_changed
-
-
- def test_save_no_change(self):
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
- def test_save_changed(self):
- self.instance.name = 'Maria'
- self.instance.save()
- self.assertTrue(self.instance.name_changed > self.created)
-
-
- def test_double_save(self):
- self.instance.name = 'Jose'
- self.instance.save()
- changed = self.instance.name_changed
- self.instance.save()
- self.assertEqual(self.instance.name_changed, changed)
-
-
- def test_no_monitor_arg(self):
- with self.assertRaises(TypeError):
- MonitorField()
-
-
-
-class MonitorWhenFieldTests(TestCase):
- """
- Will record changes only when name is 'Jose' or 'Maria'
- """
- def setUp(self):
- self.instance = MonitorWhen(name='Charlie')
- self.created = self.instance.name_changed
-
-
- def test_save_no_change(self):
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
- def test_save_changed_to_Jose(self):
- self.instance.name = 'Jose'
- self.instance.save()
- self.assertTrue(self.instance.name_changed > self.created)
-
-
- def test_save_changed_to_Maria(self):
- self.instance.name = 'Maria'
- self.instance.save()
- self.assertTrue(self.instance.name_changed > self.created)
-
-
- def test_save_changed_to_Pedro(self):
- self.instance.name = 'Pedro'
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
- def test_double_save(self):
- self.instance.name = 'Jose'
- self.instance.save()
- changed = self.instance.name_changed
- self.instance.save()
- self.assertEqual(self.instance.name_changed, changed)
-
-
-
-class MonitorWhenEmptyFieldTests(TestCase):
- """
- Monitor should never be updated id when is an empty list.
- """
- def setUp(self):
- self.instance = MonitorWhenEmpty(name='Charlie')
- self.created = self.instance.name_changed
-
-
- def test_save_no_change(self):
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
- def test_save_changed_to_Jose(self):
- self.instance.name = 'Jose'
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
- def test_save_changed_to_Maria(self):
- self.instance.name = 'Maria'
- self.instance.save()
- self.assertEqual(self.instance.name_changed, self.created)
-
-
-
-class StatusFieldTests(TestCase):
-
- def test_status_with_default_filled(self):
- instance = StatusFieldDefaultFilled()
- self.assertEqual(instance.status, instance.STATUS.yes)
-
- def test_status_with_default_not_filled(self):
- instance = StatusFieldDefaultNotFilled()
- self.assertEqual(instance.status, instance.STATUS.no)
-
- def test_no_check_for_status(self):
- field = StatusField(no_check_for_status=True)
- # this model has no STATUS attribute, so checking for it would error
- field.prepare_class(Article)
-
- def test_get_status_display(self):
- instance = StatusFieldDefaultFilled()
- self.assertEqual(instance.get_status_display(), "Yes")
-
- def test_choices_name(self):
- StatusFieldChoicesName()
-
-
-class ChoicesTests(TestCase):
- def setUp(self):
- self.STATUS = Choices('DRAFT', 'PUBLISHED')
-
-
- def test_getattr(self):
- self.assertEqual(self.STATUS.DRAFT, 'DRAFT')
-
-
- def test_indexing(self):
- self.assertEqual(self.STATUS['PUBLISHED'], 'PUBLISHED')
-
-
- def test_iteration(self):
- self.assertEqual(tuple(self.STATUS), (('DRAFT', 'DRAFT'), ('PUBLISHED', 'PUBLISHED')))
-
-
- def test_len(self):
- self.assertEqual(len(self.STATUS), 2)
-
-
- def test_repr(self):
- self.assertEqual(repr(self.STATUS), "Choices" + repr((
- ('DRAFT', 'DRAFT', 'DRAFT'),
- ('PUBLISHED', 'PUBLISHED', 'PUBLISHED'),
- )))
-
-
- def test_wrong_length_tuple(self):
- with self.assertRaises(ValueError):
- Choices(('a',))
-
-
- def test_contains_value(self):
- self.assertTrue('PUBLISHED' in self.STATUS)
- self.assertTrue('DRAFT' in self.STATUS)
-
-
- def test_doesnt_contain_value(self):
- self.assertFalse('UNPUBLISHED' in self.STATUS)
-
- def test_deepcopy(self):
- import copy
- self.assertEqual(list(self.STATUS),
- list(copy.deepcopy(self.STATUS)))
-
-
- def test_equality(self):
- self.assertEqual(self.STATUS, Choices('DRAFT', 'PUBLISHED'))
-
... 4246 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