From 7cc283e7286972ea90bea465950ccf497de12142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20E=C3=9Fer?= Date: Fri, 20 Oct 2017 08:30:40 +0200 Subject: [PATCH 01/12] Update models.py for Django 2.0 `field.rel.to` was deprecated since 1.9 and removed in 2.0 --- simple_history/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simple_history/models.py b/simple_history/models.py index 22f16c75c..d92c85232 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -173,7 +173,8 @@ def copy_fields(self, model): if getattr(old_field, 'db_column', None): field_arguments['db_column'] = old_field.db_column field = FieldType( - old_field.rel.to, + # required for Django <= 1.8 # required for Django >= 2.0 + old_field.rel.to if hasattr(old_field, 'rel') else old_field.remote_field.model, related_name='+', null=True, blank=True, From 32f2dc22ab1d96a8b5bcf6dfa444127cf282e7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20E=C3=9Fer?= Date: Fri, 20 Oct 2017 08:32:32 +0200 Subject: [PATCH 02/12] Update admin.py for Django 2.0 `django.core.urlresolvers` was deprecated since 1.10 and removed in 2.0 see https://docs.djangoproject.com/en/1.11/ref/urlresolvers/ --- simple_history/admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index 70c6ac873..ac4699a9c 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -6,7 +6,6 @@ from django.contrib import admin from django.contrib.admin import helpers from django.contrib.contenttypes.models import ContentType -from django.core.urlresolvers import reverse from django.shortcuts import get_object_or_404, render from django.utils.text import capfirst from django.utils.html import mark_safe @@ -14,6 +13,10 @@ from django.utils.encoding import force_text from django.conf import settings +try: + from django.core.urlresolvers import reverse +except ImportError: # Django < 1.10 + from django.urls import reverse try: from django.contrib.admin.utils import unquote except ImportError: # Django < 1.7 From 0d73d8486b19ea36ea10f6fe49390804a072f2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20E=C3=9Fer?= Date: Fri, 20 Oct 2017 08:38:45 +0200 Subject: [PATCH 03/12] Swap imports --- simple_history/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index ac4699a9c..ec83ceb6a 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -14,9 +14,9 @@ from django.conf import settings try: - from django.core.urlresolvers import reverse -except ImportError: # Django < 1.10 from django.urls import reverse +except ImportError: # Django < 1.10 + from django.core.urlresolvers import reverse try: from django.contrib.admin.utils import unquote except ImportError: # Django < 1.7 From 48147a526cb9c120ea658af2b709bf02123f0ad5 Mon Sep 17 00:00:00 2001 From: dgilge <33256939+dgilge@users.noreply.github.com> Date: Tue, 28 Nov 2017 16:27:14 +0100 Subject: [PATCH 04/12] Updated `is_authenticated` to be a property. `is_authenticated` has been a method until Django 1.9. Further information: https://docs.djangoproject.com/en/1.11/releases/1.10/#using-user-is-authenticated-and-user-is-anonymous-as-methods --- simple_history/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/simple_history/models.py b/simple_history/models.py index d92c85232..e27dfd91f 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -276,7 +276,13 @@ def get_history_user(self, instance): return instance._history_user except AttributeError: try: - if self.thread.request.user.is_authenticated(): + try: + # Django < 1.10 + is_authenticated = self.thread.request.user.is_authenticated() + except TypeError: + # Django >= 2.0 + is_authenticated = self.thread.request.user.is_authenticated + if is_authenticated: return self.thread.request.user return None except AttributeError: From 38c72cab01471ca3a2f137a471efe84be330030e Mon Sep 17 00:00:00 2001 From: dgilge <33256939+dgilge@users.noreply.github.com> Date: Tue, 28 Nov 2017 22:27:55 +0100 Subject: [PATCH 05/12] Improved implementation for is_authenticated --- simple_history/models.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index e27dfd91f..6a2219949 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -276,12 +276,11 @@ def get_history_user(self, instance): return instance._history_user except AttributeError: try: + is_authenticated = self.thread.request.user.is_authenticated try: - # Django < 1.10 - is_authenticated = self.thread.request.user.is_authenticated() + is_authenticated = is_authenticated() # Django < 1.10 except TypeError: - # Django >= 2.0 - is_authenticated = self.thread.request.user.is_authenticated + pass if is_authenticated: return self.thread.request.user return None From 41c5965f69b9faba858c34911b41b6e6d0c611dc Mon Sep 17 00:00:00 2001 From: Daniel <33256939+dgilge@users.noreply.github.com> Date: Mon, 4 Dec 2017 09:02:36 +0100 Subject: [PATCH 06/12] is_authenticated shouldn't emit a warning in Django 1.10+ anymore. Note: In Django 2.0 the AttributeError is raised in all passing tests right now. --- simple_history/models.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 6a2219949..c713a0d92 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -277,15 +277,13 @@ def get_history_user(self, instance): except AttributeError: try: is_authenticated = self.thread.request.user.is_authenticated - try: - is_authenticated = is_authenticated() # Django < 1.10 - except TypeError: - pass - if is_authenticated: - return self.thread.request.user - return None except AttributeError: return None + if not is_authenticated in (True, False): + is_authenticated = is_authenticated() # Django < 1.10 + if is_authenticated: + return self.thread.request.user + return None def transform_field(field): From 212dd35e3a78d192f3f7bb5235dcd97293886e44 Mon Sep 17 00:00:00 2001 From: Daniel <33256939+dgilge@users.noreply.github.com> Date: Mon, 4 Dec 2017 09:04:53 +0100 Subject: [PATCH 07/12] Added on_delete to some test model fields --- simple_history/registry_tests/migration_test_app/models.py | 2 +- simple_history/tests/models.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/simple_history/registry_tests/migration_test_app/models.py b/simple_history/registry_tests/migration_test_app/models.py index c79d7b55b..6b9ea9a56 100644 --- a/simple_history/registry_tests/migration_test_app/models.py +++ b/simple_history/registry_tests/migration_test_app/models.py @@ -11,5 +11,5 @@ class WhatIMean(DoYouKnow): class Yar(models.Model): - what = models.ForeignKey(WhatIMean) + what = models.ForeignKey(WhatIMean, on_delete=models.CASCADE) history = HistoricalRecords() diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py index 7acae715c..fb3fedffa 100644 --- a/simple_history/tests/models.py +++ b/simple_history/tests/models.py @@ -268,7 +268,11 @@ class UserAccessorOverride(models.Model): class Employee(models.Model): - manager = models.OneToOneField('Employee', null=True) + manager = models.OneToOneField( + 'Employee', + on_delete=models.CASCADE, + null=True, + ) history = HistoricalRecords() From 49d53d465fb483013a39d55d918239f170b0b21a Mon Sep 17 00:00:00 2001 From: Daniel <33256939+dgilge@users.noreply.github.com> Date: Mon, 4 Dec 2017 09:05:52 +0100 Subject: [PATCH 08/12] Swap import in test --- simple_history/tests/tests/test_admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py index 04b18e5a4..168e63c2e 100644 --- a/simple_history/tests/tests/test_admin.py +++ b/simple_history/tests/tests/test_admin.py @@ -6,7 +6,10 @@ from django.contrib.messages.storage.fallback import FallbackStorage from django.test.utils import override_settings from django.test.client import RequestFactory -from django.core.urlresolvers import reverse +try: + from django.urls import reverse +except ImportError: # Django <1.10 + from django.core.urlresolvers import reverse from django.conf import settings from django.contrib.auth import get_user_model from django.utils.encoding import force_text From 07b206ccb60a255e70eeb24b78e847f4e5c20c6a Mon Sep 17 00:00:00 2001 From: Daniel <33256939+dgilge@users.noreply.github.com> Date: Mon, 4 Dec 2017 09:25:24 +0100 Subject: [PATCH 09/12] Updated urls in tests --- simple_history/tests/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simple_history/tests/urls.py b/simple_history/tests/urls.py index 1909d01ee..9c32fab14 100644 --- a/simple_history/tests/urls.py +++ b/simple_history/tests/urls.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals -from django.conf.urls import include, url +from django.conf.urls import url from django.contrib import admin from . import other_admin admin.autodiscover() urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), - url(r'^other-admin/', include(other_admin.site.urls)), + url(r'^admin/', admin.site.urls), + url(r'^other-admin/', other_admin.site.urls), ] From bd58b70852f295191a4c1c7dee9038520f664271 Mon Sep 17 00:00:00 2001 From: Ross Rogers Date: Thu, 21 Dec 2017 10:12:32 -0800 Subject: [PATCH 10/12] Correct @flesser's merge to remove new uses of `old_field.rel.to`. Reorder field check per @merwok feedback --- simple_history/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 2333db3f5..928235b3d 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -176,11 +176,11 @@ def copy_fields(self, model): # If old_field.rel.to is 'self' then we have a case where object has a foreign key # to itself. In this case we update need to set the `to` value of the field # to be set to a model. We can use the old_field.model value. - if isinstance(old_field.rel.to, str) and old_field.rel.to == 'self': + + # required for Django > 2.0 required for Django <= 1.8 + object_to = old_field.remote_field.model if hasattr(old_field, 'remote_field') else old_field.rel.to + if isinstance(object_to, str) and object_to == 'self': object_to = old_field.model - else: - # required for Django <= 1.8 # required for Django >= 2.0 - object_to = old_field.rel.to if hasattr(old_field, 'rel') else old_field.remote_field.model field = FieldType( object_to, From 2fe403889f0fd61f7443397070372c24ac824978 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Sat, 17 Mar 2018 17:39:16 -0700 Subject: [PATCH 11/12] Clean up changes for Django 2.0, update tests --- .travis.yml | 5 ++++ runtests.py | 19 ++++++++---- simple_history/models.py | 27 +++++++++++------ simple_history/tests/models.py | 3 ++ simple_history/tests/tests/test_admin.py | 30 +++++++++++-------- simple_history/tests/tests/test_models.py | 35 ++++++++++++++++++----- tox.ini | 2 ++ 7 files changed, 88 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 56adf4e57..edaecf95d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: - DJANGO="Django>=1.9,<1.10" - DJANGO="Django>=1.10,<1.11" - DJANGO="Django>=1.11,<1.12" + - DJANGO="Django>=2.0,<2.1" install: - pip install -U coverage codecov @@ -42,5 +43,9 @@ matrix: env: DJANGO="Django>=1.10,<1.11" - python: 3.3 env: DJANGO="Django>=1.11,<1.12" + - python: 2.7 + env: DJANGO="Django>=2.0,<2.1" + - python: 3.3 + env: DJANGO="Django>=2.0,<2.1" after_success: codecov diff --git a/runtests.py b/runtests.py index 111616691..896e7f49a 100755 --- a/runtests.py +++ b/runtests.py @@ -6,6 +6,10 @@ import django from django.conf import settings +try: + from django.utils.version import get_complete_version +except ImportError: + get_complete_version = lambda: django.VERSION sys.path.insert(0, abspath(dirname(__file__))) @@ -38,11 +42,6 @@ 'ENGINE': 'django.db.backends.sqlite3', } }, - MIDDLEWARE_CLASSES=[ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - ], TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, @@ -54,6 +53,16 @@ }], ) +MIDDLEWARE = [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +] +if get_complete_version() >= (1, 10): + DEFAULT_SETTINGS['MIDDLEWARE'] = MIDDLEWARE +else: + DEFAULT_SETTINGS['MIDDLEWARE_CLASSES'] = MIDDLEWARE + def main(): diff --git a/simple_history/models.py b/simple_history/models.py index 928235b3d..99a523f2c 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -176,11 +176,17 @@ def copy_fields(self, model): # If old_field.rel.to is 'self' then we have a case where object has a foreign key # to itself. In this case we update need to set the `to` value of the field # to be set to a model. We can use the old_field.model value. - - # required for Django > 2.0 required for Django <= 1.8 - object_to = old_field.remote_field.model if hasattr(old_field, 'remote_field') else old_field.rel.to - if isinstance(object_to, str) and object_to == 'self': + try: + old_remote_field = old_field.remote_field + except AttributeError: # Django < 1.9 + old_remote_field = old_field.rel + if isinstance(old_remote_field, str) and old_remote_field == 'self': object_to = old_field.model + else: + try: + object_to = old_remote_field.model + except AttributeError: # Django < 1.8 + object_to = old_remote_field.to field = FieldType( object_to, @@ -285,13 +291,16 @@ def get_history_user(self, instance): return instance._history_user except AttributeError: try: - is_authenticated = self.thread.request.user.is_authenticated + user = self.thread.request.user except AttributeError: return None - if not is_authenticated in (True, False): - is_authenticated = is_authenticated() # Django < 1.10 - if is_authenticated: - return self.thread.request.user + if user.is_authenticated is True: + return user + try: # Django < 2.0 + if user.is_authenticated(): + return user + except TypeError: + pass return None diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py index d6911818a..11d8957e7 100644 --- a/simple_history/tests/models.py +++ b/simple_history/tests/models.py @@ -85,6 +85,9 @@ class Voter(models.Model): related_name='voters', ) + def __str__(self): + return 'Voter object' + class HistoricalRecordsVerbose(HistoricalRecords): def get_extra_fields(self, model, fields): diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py index 269563425..4ea3e5fa4 100644 --- a/simple_history/tests/tests/test_admin.py +++ b/simple_history/tests/tests/test_admin.py @@ -6,10 +6,6 @@ from django.contrib.messages.storage.fallback import FallbackStorage from django.test.utils import override_settings from django.test.client import RequestFactory -try: - from django.urls import reverse -except ImportError: # Django <1.10 - from django.core.urlresolvers import reverse from django.conf import settings from django.contrib.auth import get_user_model from django.utils.encoding import force_text @@ -22,7 +18,17 @@ from django.contrib.admin.utils import quote except ImportError: # Django < 1.7 from django.contrib.admin.util import quote +try: + from django.urls import reverse +except ImportError: # Django < 1.10 + from django.core.urlresolvers import reverse +try: + settings.MIDDLEWARE +except AttributeError: # Django < 1.10 + MIDDLEWARE_SETTING = 'MIDDLEWARE_CLASSES' +else: + MIDDLEWARE_SETTING = 'MIDDLEWARE' User = get_user_model() today = datetime(2021, 1, 1, 10, 0) tomorrow = today + timedelta(days=1) @@ -204,8 +210,8 @@ def test_history_user_not_saved(self): def test_middleware_saves_user(self): overridden_settings = { - 'MIDDLEWARE_CLASSES': - settings.MIDDLEWARE_CLASSES + + MIDDLEWARE_SETTING: + getattr(settings, MIDDLEWARE_SETTING) + ['simple_history.middleware.HistoryRequestMiddleware'], } with override_settings(**overridden_settings): @@ -222,8 +228,8 @@ def test_middleware_saves_user(self): def test_middleware_unsets_request(self): overridden_settings = { - 'MIDDLEWARE_CLASSES': - settings.MIDDLEWARE_CLASSES + + MIDDLEWARE_SETTING: + getattr(settings, MIDDLEWARE_SETTING) + ['simple_history.middleware.HistoryRequestMiddleware'], } with override_settings(**overridden_settings): @@ -237,8 +243,8 @@ def test_rolled_back_user_does_not_lead_to_foreign_key_error(self): # creating a new entry does not fail with a foreign key error. overridden_settings = { - 'MIDDLEWARE_CLASSES': - settings.MIDDLEWARE_CLASSES + + MIDDLEWARE_SETTING: + getattr(settings, MIDDLEWARE_SETTING) + ['simple_history.middleware.HistoryRequestMiddleware'], } with override_settings(**overridden_settings): @@ -259,8 +265,8 @@ def test_rolled_back_user_does_not_lead_to_foreign_key_error(self): def test_middleware_anonymous_user(self): overridden_settings = { - 'MIDDLEWARE_CLASSES': - settings.MIDDLEWARE_CLASSES + + MIDDLEWARE_SETTING: + getattr(settings, MIDDLEWARE_SETTING) + ['simple_history.middleware.HistoryRequestMiddleware'], } with override_settings(**overridden_settings): diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 3b4e03f04..29f99b102 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import mock import unittest import warnings from datetime import datetime, timedelta @@ -190,7 +191,11 @@ def test_foreignkey_still_allows_reverse_lookup_via_set_attribute(self): lib = Library.objects.create() state = State.objects.create(library=lib) self.assertTrue(hasattr(lib, 'state_set')) - self.assertIsNone(state._meta.get_field('library').rel.related_name, + try: + remote_related_name = state._meta.get_field('library').remote_field.related_name + except AttributeError: # Django < 1.9 + remote_related_name = state._meta.get_field('library').rel.related_name + self.assertIsNone(remote_related_name, "the '+' shouldn't leak through to the original " "model's field related_name") @@ -343,6 +348,16 @@ def test_model_with_excluded_fields(self): self.assertIn('question', all_fields_names) self.assertNotIn('pub_date', all_fields_names) + def test_get_history_user(self): + poll = Poll() + historical_records = HistoricalRecords() + historical_records.thread = mock.Mock() + test_user = User() + historical_records.thread.request.user = test_user + + self.assertRaises(AttributeError, lambda: poll._history_user) + self.assertEquals(historical_records.get_history_user(poll), test_user) + class CreateHistoryModelTests(unittest.TestCase): @@ -515,17 +530,23 @@ def test_invalid_bases(self): def test_import_related(self): field_object = HistoricalChoice._meta.get_field('poll') try: - related_model = field_object.rel.related_model - except AttributeError: # Django<1.8 - related_model = field_object.related.model + related_model = field_object.remote_field.related_model + except AttributeError: # Django < 1.9 + try: + related_model = field_object.rel.related_model + except AttributeError: # Django < 1.8 + related_model = field_object.related.model self.assertEqual(related_model, HistoricalChoice) def test_string_related(self): field_object = HistoricalState._meta.get_field('library') try: - related_model = field_object.rel.related_model - except AttributeError: # Django<1.8 - related_model = field_object.related.model + related_model = field_object.remote_field.related_model + except AttributeError: # Django < 1.9 + try: + related_model = field_object.rel.related_model + except AttributeError: # Django < 1.8 + related_model = field_object.related.model self.assertEqual(related_model, HistoricalState) @unittest.skipUnless(django.get_version() >= "1.7", diff --git a/tox.ini b/tox.ini index cc7858dfa..594defff7 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ envlist = py{27,34,35}-django19, py{27,34,35,36}-django110, py{27,34,35,36}-django111, + py{34,35,36}-django20, py{35,36}-djangotrunk, docs, flake8 @@ -37,6 +38,7 @@ deps = django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 django111: Django>=1.11,<1.12 + django20: Django>=2.0,<2.1 djangotrunk: https://github.com/django/django/tarball/master commands = coverage run -a --branch setup.py test From c8e09e847102afc29f77cd53019ef1b1e593c20c Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Sat, 17 Mar 2018 18:10:24 -0700 Subject: [PATCH 12/12] Update AUTHORS and CHANGES --- AUTHORS.rst | 3 +++ CHANGES.rst | 1 + 2 files changed, 4 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 90b1df83b..855994b24 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -7,10 +7,12 @@ Authors - Brian Dixon - Corey Bertram - Damien Nozay +- Daniel Gilge - Daniel Levy - Daniel Roschka - David Hite - Eduardo Cuducos +- Florian Eßer - George Vilches - Grzegorz Bialy - Hamish Downer @@ -33,6 +35,7 @@ Authors - Rod Xavier Bondoc - Ross Lote - Ross Mechanic +- Ross Rogers - Steven Klass - Steeve Chailloux - Trey Hunner diff --git a/CHANGES.rst b/CHANGES.rst index 8c4be8d4b..97a2b32c4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Unreleased - Use get_queryset rather than model.objects in history_view. (gh-303) - Change ugettext calls in models.py to ugettext_lazy - Resolve issue where model references itself (gh-278) +- Add Django 2.0 support 1.9.0 (2017-06-11) ------------------