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/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) ------------------ 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/admin.py b/simple_history/admin.py index a861333e7..bea0c7839 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.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 diff --git a/simple_history/models.py b/simple_history/models.py index ccba2b83e..99a523f2c 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -176,10 +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. - if isinstance(old_field.rel.to, str) and old_field.rel.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: - object_to = old_field.rel.to + try: + object_to = old_remote_field.model + except AttributeError: # Django < 1.8 + object_to = old_remote_field.to field = FieldType( object_to, @@ -284,11 +291,17 @@ def get_history_user(self, instance): return instance._history_user except AttributeError: try: - if self.thread.request.user.is_authenticated(): - return self.thread.request.user - return None + user = self.thread.request.user except AttributeError: return None + if user.is_authenticated is True: + return user + try: # Django < 2.0 + if user.is_authenticated(): + return user + except TypeError: + pass + return None def transform_field(field): 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 eed6e4a0a..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): @@ -287,7 +290,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() diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py index 2d71b6319..4ea3e5fa4 100644 --- a/simple_history/tests/tests/test_admin.py +++ b/simple_history/tests/tests/test_admin.py @@ -6,7 +6,6 @@ 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 from django.conf import settings from django.contrib.auth import get_user_model from django.utils.encoding import force_text @@ -19,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) @@ -201,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): @@ -219,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): @@ -234,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): @@ -256,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/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), ] 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