From 91073ab4ee7f2aa817cec9b8157e1db8e80f3c7a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 10 Sep 2019 11:15:54 +0200 Subject: [PATCH] Drop Python 2 support --- .travis.yml | 4 +--- README.rst | 2 +- docs/admin.rst | 2 +- reversion/admin.py | 17 +++++++------ reversion/management/commands/__init__.py | 3 +-- .../commands/createinitialrevisions.py | 4 +--- .../management/commands/deleterevisions.py | 3 +-- reversion/middleware.py | 2 +- .../0001_squashed_0004_auto_20160611_1202.py | 3 --- reversion/models.py | 10 +++----- reversion/revisions.py | 5 ++-- reversion/views.py | 2 +- setup.py | 1 - tests/manage.py | 19 +++++---------- tests/test_app/migrations/0001_initial.py | 3 --- tests/test_app/tests/base.py | 24 +++++++++---------- tests/test_app/tests/test_admin.py | 16 ++++++------- tests/test_app/tests/test_api.py | 7 ++---- tests/test_app/tests/test_models.py | 5 ++-- tests/test_app/views.py | 2 +- 20 files changed, 51 insertions(+), 83 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2987bbeb..67cd61a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,6 @@ env: matrix: fast_finish: true include: - - { python: "2.7", env: DJANGO="==1.11.*" } - - { python: "3.7", env: DJANGO="==2.0.*", sudo: true } - { python: "3.7", env: DJANGO="==2.1.*", sudo: true } - { python: "3.7", env: DJANGO="==2.2.*", sudo: true } @@ -32,7 +30,7 @@ services: - mysql install: # mysqlclient 1.4 is not compatible with Django 1.11 - - pip install flake8 coverage mock sphinx django$DJANGO psycopg2 mysqlclient==1.3.14 -e . + - pip install flake8 coverage sphinx django$DJANGO psycopg2 mysqlclient==1.3.14 -e . before_script: - mysql -e 'create database test_project' - psql -c 'create database test_project;' -U postgres; diff --git a/README.rst b/README.rst index c5d37ad0..64653fa1 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ version control for model instances. Requirements ============ -- Python 2.7 or later +- Python 3.5 or later - Django 1.11 or later Features diff --git a/docs/admin.rst b/docs/admin.rst index 15d2c1f7..e0f72f1e 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -100,7 +100,7 @@ A subclass of ``django.contrib.ModelAdmin`` providing rollback and recovery. def reversion_register(self, model, **options): options["exclude"] = ("some_field",) - super(YourModelAdmin, self).reversion_register(model, **options) + super().reversion_register(model, **options) ``model`` The model that will be registered with django-reversion. diff --git a/reversion/admin.py b/reversion/admin.py index 4c536422..ba0b3bbf 100644 --- a/reversion/admin.py +++ b/reversion/admin.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import json from contextlib import contextmanager from django.db import models, transaction, connection @@ -74,7 +73,7 @@ def log_addition(self, request, object, change_message=None): set_comment(json.dumps(change_message)) else: set_comment(change_message) - super(VersionAdmin, self).log_addition(request, object, change_message) + super().log_addition(request, object, change_message) def log_change(self, request, object, message): if is_active(): @@ -82,7 +81,7 @@ def log_change(self, request, object, message): set_comment(json.dumps(message)) else: set_comment(message) - super(VersionAdmin, self).log_change(request, object, message) + super().log_change(request, object, message) # Auto-registration. @@ -128,7 +127,7 @@ def _reversion_introspect_inline_admin(self, inline): return inline_model, follow_field def __init__(self, *args, **kwargs): - super(VersionAdmin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Automatically register models if required. if not is_registered(self.model): inline_fields = () @@ -141,7 +140,7 @@ def __init__(self, *args, **kwargs): self._reversion_autoregister(self.model, inline_fields) def get_urls(self): - urls = super(VersionAdmin, self).get_urls() + urls = super().get_urls() admin_site = self.admin_site opts = self.model._meta info = opts.app_label, opts.model_name, @@ -156,11 +155,11 @@ def get_urls(self): def add_view(self, request, form_url='', extra_context=None): with self.create_revision(request): - return super(VersionAdmin, self).add_view(request, form_url, extra_context) + return super().add_view(request, form_url, extra_context) def change_view(self, request, object_id, form_url='', extra_context=None): with self.create_revision(request): - return super(VersionAdmin, self).change_view(request, object_id, form_url, extra_context) + return super().change_view(request, object_id, form_url, extra_context) def _reversion_revisionform_view(self, request, version, template_name, extra_context=None): # Check that database transactions are supported. @@ -233,7 +232,7 @@ def changelist_view(self, request, extra_context=None): "has_change_permission": self.has_change_permission(request), } context.update(extra_context or {}) - return super(VersionAdmin, self).changelist_view(request, context) + return super().changelist_view(request, context) def recoverlist_view(self, request, extra_context=None): """Displays a deleted model to allow recovery.""" @@ -289,4 +288,4 @@ def history_view(self, request, object_id, extra_context=None): # Compile the context. context = {"action_list": action_list} context.update(extra_context or {}) - return super(VersionAdmin, self).history_view(request, object_id, context) + return super().history_view(request, object_id, context) diff --git a/reversion/management/commands/__init__.py b/reversion/management/commands/__init__.py index 21993803..87253a41 100644 --- a/reversion/management/commands/__init__.py +++ b/reversion/management/commands/__init__.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import apps from django.conf import settings from django.contrib import admin @@ -9,7 +8,7 @@ class BaseRevisionCommand(BaseCommand): def add_arguments(self, parser): - super(BaseRevisionCommand, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument( "app_label", metavar="app_label", diff --git a/reversion/management/commands/createinitialrevisions.py b/reversion/management/commands/createinitialrevisions.py index 6d17569b..628273a8 100644 --- a/reversion/management/commands/createinitialrevisions.py +++ b/reversion/management/commands/createinitialrevisions.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import json from django.apps import apps @@ -15,7 +13,7 @@ class Command(BaseRevisionCommand): help = "Creates initial revisions for a given app [and model]." def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument( "--comment", action="store", diff --git a/reversion/management/commands/deleterevisions.py b/reversion/management/commands/deleterevisions.py index bfe25a13..cbece29d 100644 --- a/reversion/management/commands/deleterevisions.py +++ b/reversion/management/commands/deleterevisions.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from datetime import timedelta from django.db import transaction, models, router from django.utils import timezone @@ -11,7 +10,7 @@ class Command(BaseRevisionCommand): help = "Deletes revisions for a given app [and model]." def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument( "--days", default=0, diff --git a/reversion/middleware.py b/reversion/middleware.py index a0e6df6b..241cac39 100644 --- a/reversion/middleware.py +++ b/reversion/middleware.py @@ -14,7 +14,7 @@ class RevisionMiddleware(object): atomic = True def __init__(self, get_response=None): - super(RevisionMiddleware, self).__init__() + super().__init__() # Support Django 1.10 middleware. if get_response is not None: self.get_response = create_revision( diff --git a/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py b/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py index 0a8275f5..f35f5a33 100644 --- a/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py +++ b/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-06 13:22 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/reversion/models.py b/reversion/models.py index 10413556..aa6d7bd5 100644 --- a/reversion/models.py +++ b/reversion/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import defaultdict from itertools import chain, groupby @@ -13,7 +11,7 @@ from django.db import IntegrityError, connections, models, router, transaction from django.db.models.deletion import Collector from django.db.models.functions import Cast -from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils.encoding import force_text from django.utils.functional import cached_property from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ @@ -39,7 +37,6 @@ def _safe_revert(versions): _safe_revert(unreverted_versions) -@python_2_unicode_compatible class Revision(models.Model): """A group of related serialized versions.""" @@ -171,7 +168,6 @@ def get_unique(self): last_key = key -@python_2_unicode_compatible class Version(models.Model): """A saved version of a database model.""" @@ -309,11 +305,11 @@ class _Str(models.Func): template = "%(function)s(%(expressions)s as %(db_type)s)" def __init__(self, expression): - super(_Str, self).__init__(expression, output_field=models.TextField()) + super().__init__(expression, output_field=models.TextField()) def as_sql(self, compiler, connection): self.extra["db_type"] = self.output_field.db_type(connection) - return super(_Str, self).as_sql(compiler, connection) + return super().as_sql(compiler, connection) def _safe_subquery(method, left_query, left_field_name, right_subquery, right_field_name): diff --git a/reversion/revisions.py b/reversion/revisions.py index cc830df7..0137a80f 100644 --- a/reversion/revisions.py +++ b/reversion/revisions.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from collections import namedtuple, defaultdict from contextlib import contextmanager from functools import wraps @@ -10,7 +9,7 @@ from django.db.models.query import QuerySet from django.db.models.signals import post_save, m2m_changed from django.utils.encoding import force_text -from django.utils import timezone, six +from django.utils import timezone from reversion.errors import RevisionManagementError, RegistrationError from reversion.signals import pre_revision_commit, post_revision_commit @@ -355,7 +354,7 @@ def _get_senders_and_signals(model): opts = model._meta.concrete_model._meta for field in opts.local_many_to_many: m2m_model = field.remote_field.through - if isinstance(m2m_model, six.string_types): + if isinstance(m2m_model, str): if "." not in m2m_model: m2m_model = "{app_label}.{m2m_model}".format( app_label=opts.app_label, diff --git a/reversion/views.py b/reversion/views.py index 6e225872..7b7e8334 100644 --- a/reversion/views.py +++ b/reversion/views.py @@ -61,7 +61,7 @@ class RevisionMixin(object): revision_atomic = True def __init__(self, *args, **kwargs): - super(RevisionMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.dispatch = create_revision( manage_manually=self.revision_manage_manually, using=self.revision_using, diff --git a/setup.py b/setup.py index 8193bc60..612d4e13 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/tests/manage.py b/tests/manage.py index 29ba849e..374f3b4e 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -6,17 +6,10 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") try: from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django # noqa - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) diff --git a/tests/test_app/migrations/0001_initial.py b/tests/test_app/migrations/0001_initial.py index d9bb9fe7..ad058157 100644 --- a/tests/test_app/migrations/0001_initial.py +++ b/tests/test_app/migrations/0001_initial.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-14 10:27 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/tests/test_app/tests/base.py b/tests/test_app/tests/base.py index e59dc55d..a477fbad 100644 --- a/tests/test_app/tests/base.py +++ b/tests/test_app/tests/base.py @@ -1,4 +1,7 @@ from datetime import timedelta +from importlib import import_module, reload +from io import StringIO + from django.conf import settings from django.contrib.auth.models import User from django.core.management import call_command @@ -6,15 +9,10 @@ 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 + import reversion from reversion.models import Revision, Version from test_app.models import TestModel, TestModelParent -from importlib import import_module -try: - from importlib import reload -except ImportError: # Python 2.7 - pass # Test helpers. @@ -28,12 +26,12 @@ def reloadUrls(self): clear_url_caches() def setUp(self): - super(TestBaseMixin, self).setUp() + super().setUp() for model in list(reversion.get_registered_models()): reversion.unregister(model) def tearDown(self): - super(TestBaseMixin, self).tearDown() + super().tearDown() for model in list(reversion.get_registered_models()): reversion.unregister(model) @@ -48,7 +46,7 @@ def assertSingleRevision(self, objects, user=None, comment="", meta_names=(), da revision = Version.objects.using(using).get_for_object(objects[0], model_db=model_db).get().revision self.assertEqual(revision.user, user) if hasattr(comment, 'pattern'): - assertRegex(self, revision.get_comment(), comment) + self.assertRegex(revision.get_comment(), comment) elif comment is not None: # Allow a wildcard comment. self.assertEqual(revision.get_comment(), comment) self.assertAlmostEqual(revision.date_created, date_created or timezone.now(), delta=timedelta(seconds=1)) @@ -81,14 +79,14 @@ class TestBaseTransaction(TestBaseMixin, TransactionTestCase): class TestModelMixin(object): def setUp(self): - super(TestModelMixin, self).setUp() + super().setUp() reversion.register(TestModel) class TestModelParentMixin(TestModelMixin): def setUp(self): - super(TestModelParentMixin, self).setUp() + super().setUp() reversion.register(TestModelParent, follow=("testmodel_ptr",)) @@ -96,7 +94,7 @@ def setUp(self): class UserMixin(TestBase): def setUp(self): - super(UserMixin, self).setUp() + super().setUp() self.user = User(username="test", is_staff=True, is_superuser=True) self.user.set_password("password") self.user.save() @@ -105,5 +103,5 @@ def setUp(self): class LoginMixin(UserMixin): def setUp(self): - super(LoginMixin, self).setUp() + super().setUp() self.client.login(username="test", password="password") diff --git a/tests/test_app/tests/test_admin.py b/tests/test_app/tests/test_admin.py index 092eafbc..a73728df 100644 --- a/tests/test_app/tests/test_admin.py +++ b/tests/test_app/tests/test_admin.py @@ -12,12 +12,12 @@ class AdminMixin(TestBase): def setUp(self): - super(AdminMixin, self).setUp() + super().setUp() admin.site.register(TestModelParent, VersionAdmin) self.reloadUrls() def tearDown(self): - super(AdminMixin, self).tearDown() + super().tearDown() admin.site.unregister(TestModelParent) self.reloadUrls() @@ -71,7 +71,7 @@ def testChangelistView(self): class AdminRevisionViewTest(LoginMixin, AdminMixin, TestBase): def setUp(self): - super(AdminRevisionViewTest, self).setUp() + super().setUp() with reversion.create_revision(): self.obj = TestModelParent.objects.create() with reversion.create_revision(): @@ -132,7 +132,7 @@ def testRevisionViewRevert(self): class AdminRecoverViewTest(LoginMixin, AdminMixin, TestBase): def setUp(self): - super(AdminRecoverViewTest, self).setUp() + super().setUp() with reversion.create_revision(): obj = TestModelParent.objects.create() obj.delete() @@ -189,12 +189,12 @@ def testHistorylistView(self): class AdminQuotingTest(LoginMixin, AdminMixin, TestBase): def setUp(self): - super(AdminQuotingTest, self).setUp() + super().setUp() admin.site.register(TestModelEscapePK, VersionAdmin) self.reloadUrls() def tearDown(self): - super(AdminQuotingTest, self).tearDown() + super().tearDown() admin.site.unregister(TestModelEscapePK) self.reloadUrls() @@ -240,12 +240,12 @@ class TestModelParentAdmin(VersionAdmin): class AdminRegisterInlineTest(TestBase): def setUp(self): - super(AdminRegisterInlineTest, self).setUp() + super().setUp() admin.site.register(TestModelParent, TestModelParentAdmin) self.reloadUrls() def tearDown(self): - super(AdminRegisterInlineTest, self).tearDown() + super().tearDown() admin.site.unregister(TestModelParent) self.reloadUrls() diff --git a/tests/test_app/tests/test_api.py b/tests/test_app/tests/test_api.py index c7301c17..a9c1ff6a 100644 --- a/tests/test_app/tests/test_api.py +++ b/tests/test_app/tests/test_api.py @@ -1,4 +1,6 @@ from datetime import timedelta +from unittest.mock import MagicMock + from django.contrib.auth.models import User from django.db import models from django.db.transaction import get_connection @@ -7,11 +9,6 @@ from test_app.models import TestModel, TestModelRelated, TestModelThrough, TestModelParent, TestMeta from test_app.tests.base import TestBase, TestBaseTransaction, TestModelMixin, UserMixin -try: - from unittest.mock import MagicMock -except ImportError: - from mock import MagicMock - class SaveTest(TestModelMixin, TestBase): diff --git a/tests/test_app/tests/test_models.py b/tests/test_app/tests/test_models.py index d426fae7..f46c0335 100644 --- a/tests/test_app/tests/test_models.py +++ b/tests/test_app/tests/test_models.py @@ -1,4 +1,3 @@ -from django.utils.encoding import force_text import reversion from reversion.models import Version from test_app.models import ( @@ -177,8 +176,8 @@ def testGetDeletedOrdering(self): obj_1.delete() pk_2 = obj_2.pk obj_2.delete() - self.assertEqual(Version.objects.get_deleted(TestModel)[0].object_id, force_text(pk_2)) - self.assertEqual(Version.objects.get_deleted(TestModel)[1].object_id, force_text(pk_1)) + self.assertEqual(Version.objects.get_deleted(TestModel)[0].object_id, str(pk_2)) + self.assertEqual(Version.objects.get_deleted(TestModel)[1].object_id, str(pk_1)) def testGetDeletedPostgres(self): with reversion.create_revision(using="postgres"): diff --git a/tests/test_app/views.py b/tests/test_app/views.py index 9e36e937..d645ad06 100644 --- a/tests/test_app/views.py +++ b/tests/test_app/views.py @@ -22,7 +22,7 @@ class RevisionMixinView(RevisionMixin, View): def revision_request_creates_revision(self, request): silent = request.META.get("HTTP_X_NOREVISION", "false") == "true" - return super(RevisionMixinView, self).revision_request_creates_revision(request) and not silent + return super().revision_request_creates_revision(request) and not silent def dispatch(self, request): return save_obj_view(request)