From 2dd529b30e3148f541e961d7500d296460608d2b Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Tue, 3 Mar 2015 23:11:35 -0800 Subject: [PATCH 01/10] Removed CustomForeignKey for 1.8 compatability --- simple_history/models.py | 23 +++++++++++++++-------- simple_history/tests/tests/test_models.py | 12 ++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 19f2e805f..222f81154 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -10,7 +10,10 @@ 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 +try: + from django.db.models.fields.related import RelatedObject +except ImportError: + pass from django.conf import settings from django.contrib import admin from django.utils import importlib, six @@ -121,18 +124,22 @@ def copy_fields(self, model): for field in model._meta.fields: field = copy.copy(field) field.rel = copy.copy(field.rel) + if isinstance(field, OrderWrt): + # OrderWrt is a proxy field, switch to a plain IntegerField + field.__class__ = models.IntegerField 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 + old_field = field + field = type(field)(field.rel.to, related_name='+', null=True, blank=True) + field.rel = old_field.rel 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) + field.name = old_field.name + field.db_constraint = False + field._unique = False + else: + transform_field(field) fields[field.name] = field return fields diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index eab1d2fed..db96c0b87 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -488,11 +488,19 @@ def test_invalid_bases(self): def test_import_related(self): field_object = HistoricalChoice._meta.get_field_by_name('poll_id')[0] - self.assertEqual(field_object.related.model, Choice) + try: + related_model = field_object.rel.related_model + except AttributeError: # Django<1.8 + related_model = field_object.related.model + self.assertEqual(related_model, Choice) def test_string_related(self): field_object = HistoricalState._meta.get_field_by_name('library_id')[0] - self.assertEqual(field_object.related.model, State) + try: + related_model = field_object.rel.related_model + except AttributeError: # Django<1.8 + related_model = field_object.related.model + self.assertEqual(related_model, State) @skipUnless(django.get_version() >= "1.7", "Requires 1.7 migrations") def test_state_serialization_of_customfk(self): From 2f24d59dbb011cf6767f7f666d18645efabf71f1 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Thu, 19 Mar 2015 23:46:59 -0700 Subject: [PATCH 02/10] Add 1.8 to the test matrix --- .travis.yml | 9 ++++++--- tox.ini | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index dcfbbcd77..d2e4104ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,8 @@ python: env: - DJANGO="Django>=1.4,<1.5" - DJANGO="Django>=1.6,<1.7" - - DJANGO="Django>=1.7,<1.8" + - DJANGO="Django>=1.7,<1.8a" + - DJANGO="Django>=1.8a,<1.9" install: - pip install -U coverage coveralls $DJANGO @@ -22,7 +23,7 @@ matrix: - python: 2.6 env: DJANGO="Django>=1.6,<1.7" - python: 2.6 - env: DJANGO="Django>=1.7,<1.8" + env: DJANGO="Django>=1.7,<1.8a" - python: 3.2 env: DJANGO="Django>=1.4,<1.5" - python: 3.3 @@ -30,6 +31,8 @@ matrix: include: - python: 3.4 - env: DJANGO="Django>=1.7,<1.8" + env: DJANGO="Django>=1.7,<1.8a" + - python: 3.4 + env: DJANGO="Django>=1.8a,<1.9" after_success: coveralls diff --git a/tox.ini b/tox.ini index 2a0b534a4..e4a8a714c 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 @@ -31,6 +32,7 @@ deps = py26: unittest2 django14: Django>=1.4,<1.5 django16: Django>=1.6,<1.7 - django17: Django>=1.7,<1.8 + django17: Django>=1.7,<1.8a + django18: Django>=1.8a,<1.9a djangotrunk: https://github.com/django/django/tarball/master commands = coverage run -a --branch setup.py test From 87c11e691883d8d3dceebad70c750732b6829c57 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Fri, 20 Mar 2015 00:06:39 -0700 Subject: [PATCH 03/10] Allow development Django versions to be tested with Travis --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2e4104ca..f28559587 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ env: - DJANGO="Django>=1.8a,<1.9" install: - - pip install -U coverage coveralls $DJANGO + - pip install --pre -U coverage coveralls $DJANGO - python -c 'from __future__ import print_function; import django; print("Django " + django.get_version())' script: coverage run -a setup.py test @@ -24,6 +24,8 @@ matrix: env: DJANGO="Django>=1.6,<1.7" - python: 2.6 env: DJANGO="Django>=1.7,<1.8a" + - python: 2.6 + env: DJANGO="Django>=1.8a,<1.9" - python: 3.2 env: DJANGO="Django>=1.4,<1.5" - python: 3.3 From 411f946b7baf71e73302d2c1e2f9328fe156a76c Mon Sep 17 00:00:00 2001 From: Rod Xavier Bondoc Date: Fri, 20 Mar 2015 23:36:44 +1100 Subject: [PATCH 04/10] Update AUTHORS.rst and CHANGES.rst --- AUTHORS.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ CHANGES.rst | 3 +++ 2 files changed, 47 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index dd057fc75..5aed05243 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,6 +1,50 @@ Authors ======= +- Aleksey Kladov +- Brian Dixon +- Corey Bertram +- Damien Nozay +- Daniel Levy +- Daniel Roschka +- David Hite +- George Vilches +- Hamish Downer +- James Pulec +- Joao Pedro Francese +- jofusa +- John Whitlock +- Jonathan Sanchez +- Josh Fyne +- Klaas van Schelven +- Marty Alchin +- Mauricio de Abreu Antunes +- Micah Denbraver +- Rajesh Pappula +- Ross Lote +- Steven Klass +- Trey Hunner +- Ulysses Vilela +- vnagendra +- Rod Xavier Bondoc + +Background +========== + +This code originally comes from Pro Django, published by Apress, Inc. +in December 2008. The author of the book and primary author +of the code is Marty Alchin , who +may be found online at . + +As part of the technical review process, additional code +modifications were provided by the technical reviewer, +George Vilches . + +This code was originally extended, licensed, and improved by +Corey Bertram with the permission of Marty Alchin. +Authors +======= + - Aleksey Kladov - Brian Dixon - Corey Bertram 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`` From d6ef1d18b92d61784de33fcbe9c891d1a4f34ea4 Mon Sep 17 00:00:00 2001 From: Rod Xavier Bondoc Date: Sat, 21 Mar 2015 01:36:47 +1100 Subject: [PATCH 05/10] Add support for django < 1.8 --- simple_history/models.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 222f81154..96cb2c7a4 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -124,22 +124,29 @@ 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): + if not 'RelatedObject' in globals(): + old_field = field + field = type(field)(field.rel.to, related_name='+', null=True, blank=True) + field.rel = old_field.rel + field.rel.related_name = '+' + field.name = old_field.name + field.db_constraint = False + field._unique = False + setattr(field, 'attname', field.name) + else: + # 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 + # Django < 1.8 + 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 - 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 - old_field = field - field = type(field)(field.rel.to, related_name='+', null=True, blank=True) - field.rel = old_field.rel - field.rel.related_name = '+' - field.name = old_field.name - field.db_constraint = False - field._unique = False - else: - transform_field(field) + transform_field(field) fields[field.name] = field return fields From aff374ecab0e7210a3f216704092fc4e4f016847 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Sat, 28 Mar 2015 14:16:34 -0700 Subject: [PATCH 06/10] Fix AUTHORS --- AUTHORS.rst | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 5aed05243..7c75443df 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -42,46 +42,3 @@ George Vilches . This code was originally extended, licensed, and improved by Corey Bertram with the permission of Marty Alchin. -Authors -======= - -- Aleksey Kladov -- Brian Dixon -- Corey Bertram -- Damien Nozay -- Daniel Levy -- Daniel Roschka -- David Hite -- George Vilches -- Hamish Downer -- James Pulec -- Joao Pedro Francese -- jofusa -- John Whitlock -- Jonathan Sanchez -- Josh Fyne -- Klaas van Schelven -- Marty Alchin -- Mauricio de Abreu Antunes -- Micah Denbraver -- Rajesh Pappula -- Ross Lote -- Steven Klass -- Trey Hunner -- Ulysses Vilela -- vnagendra - -Background -========== - -This code originally comes from Pro Django, published by Apress, Inc. -in December 2008. The author of the book and primary author -of the code is Marty Alchin , who -may be found online at . - -As part of the technical review process, additional code -modifications were provided by the technical reviewer, -George Vilches . - -This code was originally extended, licensed, and improved by -Corey Bertram with the permission of Marty Alchin. From a2d8b61d6ad4949dd43560d3ab72ee863e51652f Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Sat, 28 Mar 2015 14:19:07 -0700 Subject: [PATCH 07/10] Seperate stable test dependencies from unstable --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f28559587..a0f858f3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,8 @@ env: - DJANGO="Django>=1.8a,<1.9" install: - - pip install --pre -U coverage coveralls $DJANGO + - pip install -U coverage coveralls + - pip install --pre -U $DJANGO - python -c 'from __future__ import print_function; import django; print("Django " + django.get_version())' script: coverage run -a setup.py test From 7e97aec05164b60afa6cadc5791e62ba49564ce9 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Sat, 28 Mar 2015 15:40:37 -0700 Subject: [PATCH 08/10] Completely remove use of `CustomForeignKeyField` --- simple_history/models.py | 60 ++++++++++--------- .../migrations/0001_initial.py | 13 ++-- simple_history/tests/tests/test_admin.py | 1 - simple_history/tests/tests/test_models.py | 11 ++-- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 96cb2c7a4..905f2b332 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -2,18 +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 -try: - from django.db.models.fields.related import RelatedObject -except ImportError: - pass from django.conf import settings from django.contrib import admin from django.utils import importlib, six @@ -21,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 @@ -28,7 +33,6 @@ else: # south configuration for CustomForeignKeyField add_introspection_rules( [], ["^simple_history.models.CustomForeignKeyField"]) -from .manager import HistoryDescriptor registered_models = {} @@ -124,29 +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): - if not 'RelatedObject' in globals(): - old_field = field - field = type(field)(field.rel.to, related_name='+', null=True, blank=True) - field.rel = old_field.rel - field.rel.related_name = '+' - field.name = old_field.name - field.db_constraint = False - field._unique = False - setattr(field, 'attname', field.name) - else: - # 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 - # Django < 1.8 - 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 @@ -241,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 db96c0b87..f0667d929 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -487,20 +487,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] + 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, Choice) + self.assertEqual(related_model, HistoricalChoice) def test_string_related(self): - field_object = HistoricalState._meta.get_field_by_name('library_id')[0] + 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, State) + self.assertEqual(related_model, HistoricalState) @skipUnless(django.get_version() >= "1.7", "Requires 1.7 migrations") def test_state_serialization_of_customfk(self): @@ -660,7 +660,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() From 71a76277961858a1e0d97fb5df5b3d2fbdedf659 Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Thu, 2 Apr 2015 21:26:27 -0700 Subject: [PATCH 09/10] Do not register new models inside a transaction test case --- simple_history/tests/tests/test_models.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index f0667d929..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() From 4d296c3e4f48c8d319a7e04dd4f7b5d986202bff Mon Sep 17 00:00:00 2001 From: Micah Denbraver Date: Thu, 2 Apr 2015 21:45:48 -0700 Subject: [PATCH 10/10] Removed prerelease builds from build matrix --- .travis.yml | 15 +++++++-------- tox.ini | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index a0f858f3f..6fa9e05f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,11 @@ python: env: - DJANGO="Django>=1.4,<1.5" - DJANGO="Django>=1.6,<1.7" - - DJANGO="Django>=1.7,<1.8a" - - DJANGO="Django>=1.8a,<1.9" + - DJANGO="Django>=1.7,<1.8" + - DJANGO="Django>=1.8,<1.9" install: - - pip install -U coverage coveralls - - pip install --pre -U $DJANGO + - pip install -U coverage coveralls $DJANGO - python -c 'from __future__ import print_function; import django; print("Django " + django.get_version())' script: coverage run -a setup.py test @@ -24,9 +23,9 @@ matrix: - python: 2.6 env: DJANGO="Django>=1.6,<1.7" - python: 2.6 - env: DJANGO="Django>=1.7,<1.8a" + env: DJANGO="Django>=1.7,<1.8" - python: 2.6 - env: DJANGO="Django>=1.8a,<1.9" + env: DJANGO="Django>=1.8,<1.9" - python: 3.2 env: DJANGO="Django>=1.4,<1.5" - python: 3.3 @@ -34,8 +33,8 @@ matrix: include: - python: 3.4 - env: DJANGO="Django>=1.7,<1.8a" + env: DJANGO="Django>=1.7,<1.8" - python: 3.4 - env: DJANGO="Django>=1.8a,<1.9" + env: DJANGO="Django>=1.8,<1.9" after_success: coveralls diff --git a/tox.ini b/tox.ini index e4a8a714c..30e2de6c8 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,7 @@ deps = py26: unittest2 django14: Django>=1.4,<1.5 django16: Django>=1.6,<1.7 - django17: Django>=1.7,<1.8a - django18: Django>=1.8a,<1.9a + 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