Skip to content

Commit b53323c

Browse files
Merge pull request #2 from treyhunner/fix-additional-migration
Fix additional migration
2 parents 844e5f8 + d7d5830 commit b53323c

File tree

16 files changed

+201
-54
lines changed

16 files changed

+201
-54
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Authors
77
- Damien Nozay
88
- Daniel Levy
99
- Daniel Roschka
10+
- David Hite
1011
- George Vilches
1112
- Hamish Downer
1213
- jofusa

CHANGES.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
Changes
22
=======
33

4-
tip (unreleased)
5-
----------------
4+
1.5.1 (2014-10-13)
5+
------------------
66
- Removed some incompatibilities with non-default admin sites (gh-92)
77
- Fixed error caused by ``HistoryRequestMiddleware`` during anonymous requests (gh-115 fixes gh-114)
88
- Added workaround for clashing related historical accessors on User (gh-121)
9+
- Added support for MongoDB AutoField (gh-125)
10+
- Fixed CustomForeignKeyField errors with 1.7 migrations (gh-126 fixes gh-124)
911

1012
1.5.0 (2014-08-17)
1113
------------------

runtests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'simple_history',
2222
'simple_history.tests',
2323
'simple_history.tests.external',
24+
'simple_history.tests.migration_test_app',
2425
]
2526

2627
DEFAULT_SETTINGS = dict(

setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from setuptools import setup
22
import simple_history
33

4+
tests_require = ["Django>=1.4", "webtest==2.0.6", "django-webtest==1.7"]
5+
try:
6+
from unittest import skipUnless
7+
except ImportError: # Python 2.6 compatibility
8+
tests_require.append("unittest2")
9+
410
setup(
511
name='django-simple-history',
612
version=simple_history.__version__,
@@ -27,7 +33,7 @@
2733
'Programming Language :: Python :: 3.3',
2834
"License :: OSI Approved :: BSD License",
2935
],
30-
tests_require=["Django>=1.4", "webtest==2.0.6", "django-webtest==1.7"],
36+
tests_require=tests_require,
3137
include_package_data=True,
3238
test_suite='runtests.main',
3339
)

simple_history/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import unicode_literals
22

3-
__version__ = '1.5.0'
3+
__version__ = '1.5.1'
44

55

66
def register(model, app=None, manager_name='history', **records_config):

simple_history/manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def _as_of_set(self, date):
8989
queryset.order_by().values_list(pk_attr, flat=True)):
9090
changes = queryset.filter(**{pk_attr: original_pk})
9191
last_change = changes.latest('history_date')
92-
if changes.filter(history_date=last_change.history_date, history_type='-').exists():
92+
if changes.filter(history_date=last_change.history_date,
93+
history_type='-').exists():
9394
continue
9495
yield last_change.instance

simple_history/models.py

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,17 @@
66
from django.apps import apps # Django >= 1.7
77
except ImportError:
88
apps = None
9-
from django.db import models
9+
from django.db import models, router
1010
from django.db.models.fields.related import RelatedField
1111
from django.db.models.related import RelatedObject
1212
from django.conf import settings
1313
from django.contrib import admin
14-
from django.utils import importlib
14+
from django.utils import importlib, six
15+
from django.utils.encoding import python_2_unicode_compatible
1516
try:
1617
from django.utils.encoding import smart_text
1718
except ImportError:
18-
smart_text = unicode
19-
try:
20-
from django.utils.six import text_type
21-
except ImportError:
22-
text_type = unicode
19+
from django.utils.encoding import smart_unicode as smart_text
2320
try:
2421
from django.utils.timezone import now
2522
except ImportError:
@@ -28,29 +25,6 @@
2825
from django.utils.translation import string_concat
2926
from .manager import HistoryDescriptor
3027

31-
try:
32-
basestring
33-
except NameError:
34-
basestring = str # Python 3 has no basestring
35-
36-
try:
37-
from django.utils.encoding import python_2_unicode_compatible
38-
except ImportError: # django 1.3 compatibility
39-
import sys
40-
41-
# copy of django function without use of six
42-
def python_2_unicode_compatible(klass):
43-
"""
44-
Decorator defining __unicode__ and __str__ as appropriate for Py2/3
45-
46-
Usage: define __str__ method and apply this decorator to the class.
47-
"""
48-
if sys.version_info[0] != 3:
49-
klass.__unicode__ = klass.__str__
50-
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
51-
return klass
52-
53-
5428
registered_models = {}
5529

5630

@@ -62,7 +36,7 @@ def __init__(self, verbose_name=None, bases=(models.Model,),
6236
self.user_set_verbose_name = verbose_name
6337
self.user_related_name = user_related_name
6438
try:
65-
if isinstance(bases, basestring):
39+
if isinstance(bases, six.string_types):
6640
raise TypeError
6741
self.bases = tuple(bases)
6842
except TypeError:
@@ -149,7 +123,7 @@ def copy_fields(self, model):
149123
# Don't allow reverse relations.
150124
# ForeignKey knows best what datatype to use for the column
151125
# we'll used that as soon as it's finalized by copying rel.to
152-
field.__class__ = get_custom_fk_class(type(field))
126+
field.__class__ = CustomForeignKeyField
153127
field.rel.related_name = '+'
154128
field.null = True
155129
field.blank = True
@@ -243,7 +217,13 @@ def get_history_user(self, instance):
243217
return None
244218

245219

246-
class ForeignKeyMixin(object):
220+
class CustomForeignKeyField(models.ForeignKey):
221+
222+
def __init__(self, *args, **kwargs):
223+
super(CustomForeignKeyField, self).__init__(*args, **kwargs)
224+
self.db_constraint = False
225+
self.generate_reverse_relation = False
226+
247227
def get_attname(self):
248228
return self.name
249229

@@ -253,7 +233,7 @@ def get_one_to_one_field(self, to_field, other):
253233
# recursive
254234
temp_field = self.__class__(to_field.rel.to._meta.object_name)
255235
for key, val in to_field.__dict__.items():
256-
if (isinstance(key, basestring)
236+
if (isinstance(key, six.string_types)
257237
and not key.startswith('_')):
258238
setattr(temp_field, key, val)
259239
field = self.__class__.get_field(
@@ -268,7 +248,7 @@ def get_field(self, other, cls):
268248
if isinstance(to_field, models.OneToOneField):
269249
field = self.get_one_to_one_field(to_field, other)
270250
elif isinstance(to_field, models.AutoField):
271-
field.__class__ = models.IntegerField
251+
field.__class__ = convert_auto_field(to_field)
272252
else:
273253
field.__class__ = to_field.__class__
274254
excluded_prefixes = ("_", "__")
@@ -294,7 +274,7 @@ def get_field(self, other, cls):
294274
"blank",
295275
)
296276
for key, val in to_field.__dict__.items():
297-
if (isinstance(key, basestring)
277+
if (isinstance(key, six.string_types)
298278
and not key.startswith(excluded_prefixes)
299279
and key not in excluded_attributes):
300280
setattr(field, key, val)
@@ -303,7 +283,13 @@ def get_field(self, other, cls):
303283
def do_related_class(self, other, cls):
304284
field = self.get_field(other, cls)
305285
if not hasattr(self, 'related'):
306-
self.related = RelatedObject(other, cls.instance_type, self)
286+
try:
287+
instance_type = cls.instance_type
288+
except AttributeError: # when model is reconstituted for migration
289+
if cls.__module__ != "__fake__": # not from migrations, error
290+
raise
291+
else:
292+
self.related = RelatedObject(other, instance_type, self)
307293
transform_field(field)
308294
field.rel = None
309295

@@ -312,17 +298,12 @@ def contribute_to_class(self, cls, name):
312298
RelatedField.contribute_to_class(self, cls, name)
313299

314300

315-
def get_custom_fk_class(parent_type):
316-
return type(str('CustomForeignKey'), (ForeignKeyMixin, parent_type), {})
317-
318-
319301
def transform_field(field):
320302
"""Customize field appropriately for use in historical model"""
321303
field.name = field.attname
322304
if isinstance(field, models.AutoField):
323-
# The historical model gets its own AutoField, so any
324-
# existing one must be replaced with an IntegerField.
325-
field.__class__ = models.IntegerField
305+
field.__class__ = convert_auto_field(field)
306+
326307
elif isinstance(field, models.FileField):
327308
# Don't copy file, just path.
328309
field.__class__ = models.TextField
@@ -340,6 +321,19 @@ def transform_field(field):
340321
field.serialize = True
341322

342323

324+
def convert_auto_field(field):
325+
"""Convert AutoField to a non-incrementing type
326+
327+
The historical model gets its own AutoField, so any existing one
328+
must be replaced with an IntegerField.
329+
"""
330+
connection = router.db_for_write(field.model)
331+
if settings.DATABASES[connection]['ENGINE'] in ('django_mongodb_engine',):
332+
# Check if AutoField is string for django-non-rel support
333+
return models.TextField
334+
return models.IntegerField
335+
336+
343337
class HistoricalObjectDescriptor(object):
344338
def __init__(self, model):
345339
self.model = model

simple_history/tests/migration_test_app/__init__.py

Whitespace-only changes.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models, migrations
5+
import simple_history.models
6+
from django.conf import settings
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='DoYouKnow',
18+
fields=[
19+
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
20+
],
21+
options={
22+
},
23+
bases=(models.Model,),
24+
),
25+
migrations.CreateModel(
26+
name='HistoricalYar',
27+
fields=[
28+
('id', models.IntegerField(verbose_name='ID', auto_created=True, db_index=True, blank=True)),
29+
('history_id', models.AutoField(serialize=False, primary_key=True)),
30+
('history_date', models.DateTimeField()),
31+
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
32+
('history_user', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL)),
33+
],
34+
options={
35+
'verbose_name': 'historical yar',
36+
'ordering': ('-history_date', '-history_id'),
37+
},
38+
bases=(models.Model,),
39+
),
40+
migrations.CreateModel(
41+
name='WhatIMean',
42+
fields=[
43+
('doyouknow_ptr', models.OneToOneField(primary_key=True, to='migration_test_app.DoYouKnow', auto_created=True, parent_link=True, serialize=False)),
44+
],
45+
options={
46+
},
47+
bases=('migration_test_app.doyouknow',),
48+
),
49+
migrations.CreateModel(
50+
name='Yar',
51+
fields=[
52+
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
53+
('what', models.ForeignKey(to='migration_test_app.WhatIMean')),
54+
],
55+
options={
56+
},
57+
bases=(models.Model,),
58+
),
59+
migrations.AddField(
60+
model_name='historicalyar',
61+
name='what_id',
62+
field=simple_history.models.CustomForeignKeyField(to='migration_test_app.WhatIMean', blank=True, null=True, related_name='+'),
63+
preserve_default=True,
64+
),
65+
]

simple_history/tests/migration_test_app/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)