diff --git a/.travis.yml b/.travis.yml index dcfbbcd77..6fa9e05f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - DJANGO="Django>=1.4,<1.5" - DJANGO="Django>=1.6,<1.7" - DJANGO="Django>=1.7,<1.8" + - DJANGO="Django>=1.8,<1.9" install: - pip install -U coverage coveralls $DJANGO @@ -23,6 +24,8 @@ matrix: env: DJANGO="Django>=1.6,<1.7" - python: 2.6 env: DJANGO="Django>=1.7,<1.8" + - python: 2.6 + env: DJANGO="Django>=1.8,<1.9" - python: 3.2 env: DJANGO="Django>=1.4,<1.5" - python: 3.3 @@ -31,5 +34,7 @@ matrix: include: - python: 3.4 env: DJANGO="Django>=1.7,<1.8" + - python: 3.4 + env: DJANGO="Django>=1.8,<1.9" after_success: coveralls diff --git a/AUTHORS.rst b/AUTHORS.rst index dd057fc75..7c75443df 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -26,6 +26,7 @@ Authors - Trey Hunner - Ulysses Vilela - vnagendra +- Rod Xavier Bondoc Background ========== diff --git a/CHANGES.rst b/CHANGES.rst index fbcbc18f3..901514b0d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ Changes ======= +1.5.x(not yet released) +- Add support for Django 1.8+ + 1.5.4 (2015-01-03) ------------------ - Fix a bug when models have a ``ForeignKey`` with ``primary_key=True`` diff --git a/simple_history/models.py b/simple_history/models.py index 19f2e805f..905f2b332 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -2,15 +2,12 @@ import threading import copy -try: - from django.apps import apps -except ImportError: # Django < 1.7 - apps = None +import warnings + from django.db import models, router from django.db.models import loading from django.db.models.fields.proxy import OrderWrt from django.db.models.fields.related import RelatedField -from django.db.models.related import RelatedObject from django.conf import settings from django.contrib import admin from django.utils import importlib, six @@ -18,6 +15,17 @@ from django.utils.encoding import smart_text from django.utils.timezone import now from django.utils.translation import string_concat + +from .manager import HistoryDescriptor + +try: + from django.apps import apps +except ImportError: # Django < 1.7 + apps = None +try: + from django.db.models.fields.related import RelatedObject +except ImportError: + RelatedObject = None try: from south.modelsinspector import add_introspection_rules except ImportError: # south not present @@ -25,7 +33,6 @@ else: # south configuration for CustomForeignKeyField add_introspection_rules( [], ["^simple_history.models.CustomForeignKeyField"]) -from .manager import HistoryDescriptor registered_models = {} @@ -121,18 +128,25 @@ def copy_fields(self, model): for field in model._meta.fields: field = copy.copy(field) field.rel = copy.copy(field.rel) - if isinstance(field, models.ForeignKey): - # Don't allow reverse relations. - # ForeignKey knows best what datatype to use for the column - # we'll used that as soon as it's finalized by copying rel.to - field.__class__ = CustomForeignKeyField - field.rel.related_name = '+' - field.null = True - field.blank = True if isinstance(field, OrderWrt): # OrderWrt is a proxy field, switch to a plain IntegerField field.__class__ = models.IntegerField - transform_field(field) + if isinstance(field, models.ForeignKey): + old_field = field + field = type(field)( + field.rel.to, + related_name='+', + null=True, + blank=True, + primary_key=False, + db_index=True, + serialize=True, + ) + field._unique = False + field.name = old_field.name + field.db_constraint = False + else: + transform_field(field) fields[field.name] = field return fields @@ -227,6 +241,8 @@ def get_history_user(self, instance): class CustomForeignKeyField(models.ForeignKey): def __init__(self, *args, **kwargs): + warnings.warn("CustomForeignKeyField is deprecated.", + DeprecationWarning) super(CustomForeignKeyField, self).__init__(*args, **kwargs) self.db_constraint = False self.generate_reverse_relation = False diff --git a/simple_history/tests/migration_test_app/migrations/0001_initial.py b/simple_history/tests/migration_test_app/migrations/0001_initial.py index a2007a12a..ecc4ae277 100644 --- a/simple_history/tests/migration_test_app/migrations/0001_initial.py +++ b/simple_history/tests/migration_test_app/migrations/0001_initial.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import simple_history.models +import django.db.models.deletion from django.conf import settings @@ -30,12 +30,9 @@ class Migration(migrations.Migration): ('history_id', models.AutoField(serialize=False, primary_key=True)), ('history_date', models.DateTimeField()), ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('history_user', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL)), + ('history_user', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)), ], - options={ - 'verbose_name': 'historical yar', - 'ordering': ('-history_date', '-history_id'), - }, + options={'ordering': ('-history_date', '-history_id'), 'get_latest_by': 'history_date', 'verbose_name': 'historical yar'}, bases=(models.Model,), ), migrations.CreateModel( @@ -59,8 +56,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='historicalyar', - name='what_id', - field=simple_history.models.CustomForeignKeyField(to='migration_test_app.WhatIMean', blank=True, null=True, related_name='+'), + name='what', + field=models.ForeignKey(related_name='+', db_constraint=False, blank=True, to='migration_test_app.WhatIMean', null=True), preserve_default=True, ), ] diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py index a023597b8..b8889d1ac 100644 --- a/simple_history/tests/tests/test_admin.py +++ b/simple_history/tests/tests/test_admin.py @@ -234,4 +234,3 @@ def test_deleteting_user(self): historical_poll = poll.history.all()[0] self.assertEqual(historical_poll.history_user, None) - diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index eab1d2fed..ecf4f1019 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -1,17 +1,9 @@ from __future__ import unicode_literals from datetime import datetime, timedelta -try: - from unittest import skipUnless -except ImportError: - from unittest2 import skipUnless +import unittest import django -try: - from django.contrib.auth import get_user_model - User = get_user_model() -except ImportError: # django 1.4 compatibility - from django.contrib.auth.models import User from django.db import models from django.db.models.loading import get_model from django.db.models.fields.proxy import OrderWrt @@ -29,6 +21,16 @@ ) from ..external.models import ExternalModel2, ExternalModel4 +try: + from unittest import skipUnless +except ImportError: + from unittest2 import skipUnless +try: + from django.contrib.auth import get_user_model + User = get_user_model() +except ImportError: # django 1.4 compatibility + from django.contrib.auth.models import User + today = datetime(2021, 1, 1, 10, 0) tomorrow = today + timedelta(days=1) yesterday = today - timedelta(days=1) @@ -324,7 +326,7 @@ def test_register_custome_records(self): self.assertEqual(expected, str(voter.history.all()[0])[:len(expected)]) -class CreateHistoryModelTests(TestCase): +class CreateHistoryModelTests(unittest.TestCase): def test_create_history_model_with_one_to_one_field_to_integer_field(self): records = HistoricalRecords() @@ -487,12 +489,20 @@ def test_invalid_bases(self): self.assertRaises(TypeError, HistoricalRecords, bases=bases) def test_import_related(self): - field_object = HistoricalChoice._meta.get_field_by_name('poll_id')[0] - self.assertEqual(field_object.related.model, Choice) + field_object = HistoricalChoice._meta.get_field_by_name('poll')[0] + 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_by_name('library_id')[0] - self.assertEqual(field_object.related.model, State) + field_object = HistoricalState._meta.get_field_by_name('library')[0] + try: + related_model = field_object.rel.related_model + except AttributeError: # Django<1.8 + related_model = field_object.related.model + self.assertEqual(related_model, HistoricalState) @skipUnless(django.get_version() >= "1.7", "Requires 1.7 migrations") def test_state_serialization_of_customfk(self): @@ -652,7 +662,8 @@ class TestLatest(TestCase): """"Test behavior of `latest()` without any field parameters""" def setUp(self): - poll = Poll.objects.create(question="Does `latest()` work?", pub_date=yesterday) + poll = Poll.objects.create( + question="Does `latest()` work?", pub_date=yesterday) poll.pub_date = today poll.save() diff --git a/tox.ini b/tox.ini index 2a0b534a4..30e2de6c8 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = py{26,27}-django14, py{26,27,32,33}-django16, py{27,32,33,34}-django17, + py{27,32,33,34}-django18, py{27,32,33,34}-djangotrunk, docs, flake8 @@ -32,5 +33,6 @@ deps = django14: Django>=1.4,<1.5 django16: Django>=1.6,<1.7 django17: Django>=1.7,<1.8 + django18: Django>=1.8,<1.9 djangotrunk: https://github.com/django/django/tarball/master commands = coverage run -a --branch setup.py test