[Python-modules-commits] [django-reversion] 01/09: Import django-reversion_2.0.9.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Thu Jun 22 08:20:01 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository django-reversion.

commit 181bad9c2b1870ecb6d5cffdb213051ff145bc19
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Thu Jun 22 08:53:24 2017 +0200

    Import django-reversion_2.0.9.orig.tar.gz
---
 CHANGELOG.rst                                   | 13 +++++++++
 docs/_include/create-revision-args.rst          |  3 +++
 docs/_include/create-revision-atomic.rst        |  1 +
 docs/admin.rst                                  |  3 +++
 docs/api.rst                                    |  4 +--
 docs/commands.rst                               |  5 ++++
 docs/common-problems.rst                        |  2 +-
 docs/django-versions.rst                        |  6 ++---
 docs/middleware.rst                             |  5 ++++
 docs/views.rst                                  |  2 +-
 reversion/__init__.py                           |  2 +-
 reversion/middleware.py                         | 14 ++++++++--
 reversion/migrations/0003_auto_20160601_1600.py | 27 +++++++++++--------
 reversion/revisions.py                          | 36 ++++++++++++++++++++-----
 reversion/views.py                              |  7 +++--
 tests/test_app/tests/base.py                    | 14 +++++++---
 tests/test_app/tests/test_api.py                | 15 ++++++++++-
 tests/test_app/tests/test_models.py             | 15 ++++++++++-
 tox.ini                                         | 10 ++++---
 19 files changed, 147 insertions(+), 37 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 66029e4..e4ea90a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,6 +3,17 @@
 django-reversion changelog
 ==========================
 
+2.0.9 - 19/06/2017
+------------------
+
+- Bugfix: Deleted inline admin instances no longer added to revision.
+- Bugfix: M2M relations correctly added to revision (@etianen, @claudep).
+- Improved performance of 0003 migration (@mkurek).
+- Documentation improvements (@orlra, @guettli, @meilinger).
+- Django 1.11 support (@claudep).
+- Added ``atomic=True`` parameter to ``create_revision`` (Ernesto Ferro).
+
+
 2.0.8 - 28/11/2016
 ------------------
 
@@ -163,6 +174,8 @@ Low-level API
 
 * **Breaking:** ``reversion.get_deleted()`` has been moved to ``Version.objects.get_deleted()`` (@etianen).
 
+* **Breaking:** ``Version.object_version`` has been renamed to ``Version._object_version`` (@etianen).
+
 * **Breaking:** Refactored multi-db support (@etianen).
 
     django-reversion now supports restoring model instances to their original database automatically. Several parameter names have also be updated to match Django coding conventions.
diff --git a/docs/_include/create-revision-args.rst b/docs/_include/create-revision-args.rst
index ed13406..b035887 100644
--- a/docs/_include/create-revision-args.rst
+++ b/docs/_include/create-revision-args.rst
@@ -3,3 +3,6 @@
 
 ``using``
     .. include:: /_include/create-revision-using.rst
+
+``atomic``
+    .. include:: /_include/create-revision-atomic.rst
diff --git a/docs/_include/create-revision-atomic.rst b/docs/_include/create-revision-atomic.rst
new file mode 100644
index 0000000..d111a30
--- /dev/null
+++ b/docs/_include/create-revision-atomic.rst
@@ -0,0 +1 @@
+If ``True``, the revision block will be wrapped in a ``transaction.atomic()``.
diff --git a/docs/admin.rst b/docs/admin.rst
index cda0da8..8ba6285 100644
--- a/docs/admin.rst
+++ b/docs/admin.rst
@@ -5,6 +5,9 @@ Admin integration
 
 django-reversion can be used to add rollback and recovery to your admin site.
 
+.. Warning::
+    The admin integration requires that your database engine supports transactions. This is the case for PostgreSQL, SQLite and MySQL InnoDB. If you are using MySQL MyISSAM, upgrade your database tables to InnoDB!
+
 
 Overview
 --------
diff --git a/docs/api.rst b/docs/api.rst
index 637a818..8bc961b 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -216,9 +216,9 @@ Registration API
 Revision API
 ------------
 
-``reversion.create_revision(manage_manually=False, using=None)``
+``reversion.create_revision(manage_manually=False, using=None, atomic=True)``
 
-    Marks a block of code as a *revision block*. Can also be used as a decorator. The revision block will be wrapped in a ``transaction.atomic()``.
+    Marks a block of code as a *revision block*. Can also be used as a decorator.
 
     .. include:: /_include/create-revision-args.rst
 
diff --git a/docs/commands.rst b/docs/commands.rst
index 2bcf670..839d935 100644
--- a/docs/commands.rst
+++ b/docs/commands.rst
@@ -32,7 +32,12 @@ Deletes old revisions. It can be run regularly to keep revision history manageab
 .. code:: bash
 
     ./manage.py deleterevisions
+    # keep any changes from last 30 days
     ./manage.py deleterevisions your_app.YourModel --days=30
+    # keep 30 most recent changes for each item.
+    ./manage.py deleterevisions your_app.YourModel --keep=30
+    # Keep anything from last 30 days and at least 3 from older changes.
+    ./manage.py deleterevisions your_app.YourModel --keep=3 --days=30
 
 Run ``./manage.py deleterevisions --help`` for more information.
 
diff --git a/docs/common-problems.rst b/docs/common-problems.rst
index 3ea3130..0d3148e 100644
--- a/docs/common-problems.rst
+++ b/docs/common-problems.rst
@@ -9,4 +9,4 @@ RegistrationError: class 'myapp.MyModel' has already been registered with Revers
 
 This is caused by your ``models.py`` file being imported twice, resulting in ``reversion.register()`` being called twice for the same model.
 
-This problem is almost certainly due to relative import statements in your codebase. Try converting all your absolute imports into relative imports.
+This problem is almost certainly due to relative import statements in your codebase. Try converting all your relative imports into absolute imports.
diff --git a/docs/django-versions.rst b/docs/django-versions.rst
index 66a0954..4933b04 100644
--- a/docs/django-versions.rst
+++ b/docs/django-versions.rst
@@ -10,9 +10,9 @@ Older versions of Django require an older version of django-reversion to be inst
 ==============  =================
 Django version  Reversion release
 ==============  =================
-1.8+            2.0.0
-1.7+            1.10.0
-1.6+            1.8.5
+1.8 - current   2.0.0
+1.7             1.10.x
+1.6             1.8.x
 ==============  =================
 
 .. Warning::
diff --git a/docs/middleware.rst b/docs/middleware.rst
index 10697e8..38b2289 100644
--- a/docs/middleware.rst
+++ b/docs/middleware.rst
@@ -27,3 +27,8 @@ To enable ``RevisionMiddleware``, add ``'reversion.middleware.RevisionMiddleware
 ``RevisionMiddleware.using = None``
 
     .. include:: /_include/create-revision-using.rst
+
+
+``RevisionMiddleware.atomic = True``
+
+    .. include:: /_include/create-revision-atomic.rst
diff --git a/docs/views.rst b/docs/views.rst
index 0872e61..3582436 100644
--- a/docs/views.rst
+++ b/docs/views.rst
@@ -9,7 +9,7 @@ Shortcuts when using django-reversion in views.
 Decorators
 ----------
 
-``reversion.views.create_revision(manage_manually=False, using=None)``
+``reversion.views.create_revision(manage_manually=False, using=None, atomic=True)``
 
     Decorates a view to wrap every request that isn't ``GET``, ``HEAD`` or ``OPTIONS`` in a revision block.
 
diff --git a/reversion/__init__.py b/reversion/__init__.py
index 56128a2..1196d01 100644
--- a/reversion/__init__.py
+++ b/reversion/__init__.py
@@ -36,4 +36,4 @@ else:
         get_registered_models,
     )
 
-__version__ = VERSION = (2, 0, 8)
+__version__ = VERSION = (2, 0, 9)
diff --git a/reversion/middleware.py b/reversion/middleware.py
index 7a57437..7f0e89c 100644
--- a/reversion/middleware.py
+++ b/reversion/middleware.py
@@ -11,15 +11,25 @@ class RevisionMiddleware(object):
 
     using = None
 
+    atomic = True
+
     def __init__(self, get_response=None):
         super(RevisionMiddleware, self).__init__()
         # Support Django 1.10 middleware.
         if get_response is not None:
-            self.get_response = create_revision(manage_manually=self.manage_manually, using=self.using)(get_response)
+            self.get_response = create_revision(
+                manage_manually=self.manage_manually,
+                using=self.using,
+                atomic=self.atomic
+            )(get_response)
 
     def process_request(self, request):
         if _request_creates_revision(request):
-            context = create_revision_base(manage_manually=self.manage_manually, using=self.using)
+            context = create_revision_base(
+                manage_manually=self.manage_manually,
+                using=self.using,
+                atomic=self.atomic
+            )
             context.__enter__()
             if not hasattr(request, "_revision_middleware"):
                 setattr(request, "_revision_middleware", {})
diff --git a/reversion/migrations/0003_auto_20160601_1600.py b/reversion/migrations/0003_auto_20160601_1600.py
index 7468344..7aa2cdb 100644
--- a/reversion/migrations/0003_auto_20160601_1600.py
+++ b/reversion/migrations/0003_auto_20160601_1600.py
@@ -3,7 +3,7 @@
 from __future__ import unicode_literals
 
 from collections import defaultdict
-from django.db import migrations, models, router
+from django.db import DEFAULT_DB_ALIAS, migrations, models, router
 from django.apps import apps as live_apps
 
 
@@ -43,9 +43,13 @@ def set_version_db(apps, schema_editor):
     """
     db_alias = schema_editor.connection.alias
     Version = apps.get_model("reversion", "Version")
-    content_types = Version.objects.using(db_alias).order_by().values_list("content_type__app_label", "content_type__model").distinct()
+    content_types = Version.objects.using(db_alias).order_by().values_list(
+        "content_type_id",
+        "content_type__app_label",
+        "content_type__model"
+    ).distinct()
     model_dbs = defaultdict(list)
-    for app_label, model_name in content_types:
+    for content_type_id, app_label, model_name in content_types:
         # We need to be able to access all models in the project, and we can't
         # specify them up-front in the migration dependencies. So we have to
         # just get the live model. This should be fine, since we don't actually
@@ -57,15 +61,16 @@ def set_version_db(apps, schema_editor):
             db = "default"
         else:
             db = router.db_for_write(model)
-        model_dbs[db].append((app_label, model_name))
+        model_dbs[db].append(content_type_id)
     # Update db field.
-    for db, model_keys in model_dbs.items():
-        db_query = models.Q()
-        for app_label, model_name in model_keys:
-            db_query |= models.Q(
-                content_type__app_label=app_label, content_type__model=model_name
-            )
-        Version.objects.using(db_alias).filter(db_query).update(db=db)
+    # speedup for case when there is only default db
+    if DEFAULT_DB_ALIAS in model_dbs and len(model_dbs) == 1:
+        Version.objects.using(db_alias).update(db=DEFAULT_DB_ALIAS)
+    else:
+        for db, content_type_ids in model_dbs.items():
+            Version.objects.using(db_alias).filter(
+                content_type__in=content_type_ids
+            ).update(db=db)
 
 
 class Migration(migrations.Migration):
diff --git a/reversion/revisions.py b/reversion/revisions.py
index f9bf5da..121ec73 100644
--- a/reversion/revisions.py
+++ b/reversion/revisions.py
@@ -1,5 +1,5 @@
 from __future__ import unicode_literals
-from collections import namedtuple
+from collections import namedtuple, defaultdict
 from contextlib import contextmanager
 from functools import wraps
 from threading import local
@@ -214,6 +214,24 @@ def add_to_revision(obj, model_db=None):
 
 def _save_revision(versions, user=None, comment="", meta=(), date_created=None, using=None):
     from reversion.models import Revision
+    # Only save versions that exist in the database.
+    model_db_pks = defaultdict(lambda: defaultdict(set))
+    for version in versions:
+        model_db_pks[version._model][version.db].add(version.object_id)
+    model_db_existing_pks = {
+        model: {
+            db: frozenset(map(
+                force_text,
+                model._default_manager.using(db).filter(pk__in=pks).values_list("pk", flat=True),
+            ))
+            for db, pks in db_pks.items()
+        }
+        for model, db_pks in model_db_pks.items()
+    }
+    versions = [
+        version for version in versions
+        if version.object_id in model_db_existing_pks[version._model][version.db]
+    ]
     # Bail early if there are no objects to save.
     if not versions:
         return
@@ -250,10 +268,16 @@ def _save_revision(versions, user=None, comment="", meta=(), date_created=None,
 
 
 @contextmanager
-def _create_revision_context(manage_manually, using):
+def _dummy_context():
+    yield
+
+
+ at contextmanager
+def _create_revision_context(manage_manually, using, atomic):
     _push_frame(manage_manually, using)
     try:
-        with transaction.atomic(using=using):
+        context = transaction.atomic(using=using) if atomic else _dummy_context()
+        with context:
             yield
             # Only save for a db if that's the last stack frame for that db.
             if not any(using in frame.db_versions for frame in _local.stack[:-1]):
@@ -270,10 +294,10 @@ def _create_revision_context(manage_manually, using):
         _pop_frame()
 
 
-def create_revision(manage_manually=False, using=None):
+def create_revision(manage_manually=False, using=None, atomic=True):
     from reversion.models import Revision
     using = using or router.db_for_write(Revision)
-    return _ContextWrapper(_create_revision_context, (manage_manually, using))
+    return _ContextWrapper(_create_revision_context, (manage_manually, using, atomic))
 
 
 class _ContextWrapper(object):
@@ -304,7 +328,7 @@ def _post_save_receiver(sender, instance, using, **kwargs):
 
 def _m2m_changed_receiver(instance, using, action, model, reverse, **kwargs):
     if action.startswith("post_") and not reverse:
-        if is_registered(model) and is_active() and not is_manage_manually():
+        if is_registered(instance) and is_active() and not is_manage_manually():
             add_to_revision(instance, model_db=using)
 
 
diff --git a/reversion/views.py b/reversion/views.py
index f91e883..2057d17 100644
--- a/reversion/views.py
+++ b/reversion/views.py
@@ -19,7 +19,7 @@ def _set_user_from_request(request):
         set_user(request.user)
 
 
-def create_revision(manage_manually=False, using=None):
+def create_revision(manage_manually=False, using=None, atomic=True):
     """
     View decorator that wraps the request in a revision.
 
@@ -30,7 +30,7 @@ def create_revision(manage_manually=False, using=None):
         def do_revision_view(request, *args, **kwargs):
             if _request_creates_revision(request):
                 try:
-                    with create_revision_base(manage_manually=manage_manually, using=None):
+                    with create_revision_base(manage_manually=manage_manually, using=using, atomic=atomic):
                         response = func(request, *args, **kwargs)
                         # Check for an error response.
                         if response.status_code >= 400:
@@ -57,9 +57,12 @@ class RevisionMixin(object):
 
     revision_using = None
 
+    revision_atomic = True
+
     def __init__(self, *args, **kwargs):
         super(RevisionMixin, self).__init__(*args, **kwargs)
         self.dispatch = create_revision(
             manage_manually=self.revision_manage_manually,
             using=self.revision_using,
+            atomic=self.revision_atomic
         )(self.dispatch)
diff --git a/tests/test_app/tests/base.py b/tests/test_app/tests/base.py
index 01db278..5bf2fd7 100644
--- a/tests/test_app/tests/base.py
+++ b/tests/test_app/tests/base.py
@@ -6,7 +6,7 @@ try:
     from django.urls import clear_url_caches
 except ImportError:  # Django < 1.10 pragma: no cover
     from django.core.urlresolvers import clear_url_caches
-from django.test import TestCase
+from django.test import TestCase, TransactionTestCase
 from django.test.utils import override_settings
 from django.utils import timezone
 from django.utils.six import StringIO, assertRegex
@@ -22,7 +22,7 @@ except ImportError:  # Python 2.7
 
 # Test helpers.
 
-class TestBase(TestCase):
+class TestBaseMixin(object):
 
     multi_db = True
 
@@ -31,7 +31,7 @@ class TestBase(TestCase):
         clear_url_caches()
 
     def tearDown(self):
-        super(TestBase, self).tearDown()
+        super(TestBaseMixin, self).tearDown()
         for model in list(reversion.get_registered_models()):
             reversion.unregister(model)
 
@@ -68,6 +68,14 @@ class TestBase(TestCase):
         self.assertEqual(Revision.objects.using(using).all().count(), 0)
 
 
+class TestBase(TestBaseMixin, TestCase):
+    pass
+
+
+class TestBaseTransaction(TestBaseMixin, TransactionTestCase):
+    pass
+
+
 class TestModelMixin(object):
 
     def setUp(self):
diff --git a/tests/test_app/tests/test_api.py b/tests/test_app/tests/test_api.py
index 583bfd7..24201a2 100644
--- a/tests/test_app/tests/test_api.py
+++ b/tests/test_app/tests/test_api.py
@@ -1,10 +1,11 @@
 from datetime import timedelta
 from django.contrib.auth.models import User
 from django.db import models
+from django.db.transaction import get_connection
 from django.utils import timezone
 import reversion
 from test_app.models import TestModel, TestModelRelated, TestModelThrough, TestModelParent, TestMeta
-from test_app.tests.base import TestBase, TestModelMixin, UserMixin
+from test_app.tests.base import TestBase, TestBaseTransaction, TestModelMixin, UserMixin
 
 try:
     from unittest.mock import MagicMock
@@ -130,6 +131,18 @@ class CreateRevisionTest(TestModelMixin, TestBase):
         self.assertEqual(_callback.call_count, 1)
 
 
+class CreateRevisionAtomicTest(TestModelMixin, TestBaseTransaction):
+    def testCreateRevisionAtomic(self):
+        self.assertFalse(get_connection().in_atomic_block)
+        with reversion.create_revision():
+            self.assertTrue(get_connection().in_atomic_block)
+
+    def testCreateRevisionNonAtomic(self):
+        self.assertFalse(get_connection().in_atomic_block)
+        with reversion.create_revision(atomic=False):
+            self.assertFalse(get_connection().in_atomic_block)
+
+
 class CreateRevisionManageManuallyTest(TestModelMixin, TestBase):
 
     def testCreateRevisionManageManually(self):
diff --git a/tests/test_app/tests/test_models.py b/tests/test_app/tests/test_models.py
index b8a0bff..f3f13c7 100644
--- a/tests/test_app/tests/test_models.py
+++ b/tests/test_app/tests/test_models.py
@@ -224,7 +224,7 @@ class FieldDictTest(TestModelMixin, TestBase):
         self.assertEqual(Version.objects.get_for_object(obj).get().field_dict, {
             "id": obj.pk,
             "name": "v1",
-            "related": [],
+            "related": [obj_related.pk],
         })
 
 
@@ -279,6 +279,19 @@ class FieldDictInheritanceTest(TestModelParentMixin, TestBase):
         })
 
 
+class M2MTest(TestModelMixin, TestBase):
+
+    def testM2MSave(self):
+        v1 = TestModelRelated.objects.create(name="v1")
+        v2 = TestModelRelated.objects.create(name="v2")
+        with reversion.create_revision():
+            obj = TestModel.objects.create()
+            obj.related.add(v1)
+            obj.related.add(v2)
+        version = Version.objects.get_for_object(obj).first()
+        self.assertEqual(set(version.field_dict["related"]), set((v1.pk, v2.pk,)))
+
+
 class RevertTest(TestModelMixin, TestBase):
 
     def testRevert(self):
diff --git a/tox.ini b/tox.ini
index ba6872c..b5a0629 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,8 @@
 [tox]
 envlist =
-    {py27,py34,py35}-django{18,19,110}
+    coverage-erase
+    test-{py27,py34,py35}-django{18,19,110,111}
+    coverage-report
     flake8
     docs
 
@@ -11,12 +13,14 @@ deps =
     django18: Django>=1.8,<1.9
     django19: Django>=1.9,<1.10
     django110: Django>=1.10,<1.11
+    django111: Django>=1.11a,<2.0
     psycopg2>=2.6.1
     mysqlclient>=mysqlclient
     coverage>=4.1
 commands =
-    coverage run tests/manage.py test tests
-    coverage report
+    coverage-erase: coverage erase
+    test: coverage run tests/manage.py test tests
+    coverage-report: coverage report
 
 [testenv:flake8]
 basepython = python3.5

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-reversion.git



More information about the Python-modules-commits mailing list