diff --git a/reversion/models.py b/reversion/models.py index a7535a8f..0c8a0a97 100644 --- a/reversion/models.py +++ b/reversion/models.py @@ -225,10 +225,12 @@ def _model(self): @cached_property def _object_version(self): + version_options = _get_options(self._model) data = self.serialized_data data = force_str(data.encode("utf8")) try: - return list(serializers.deserialize(self.format, data, ignorenonexistent=True))[0] + return list(serializers.deserialize(self.format, data, ignorenonexistent=True, + use_natural_foreign_keys=version_options.use_natural_foreign_keys))[0] except DeserializationError: raise RevertError(ugettext("Could not load %(object_repr)s version - incompatible version data.") % { "object_repr": self.object_repr, diff --git a/reversion/revisions.py b/reversion/revisions.py index 0b5e75cb..6a8dafd2 100644 --- a/reversion/revisions.py +++ b/reversion/revisions.py @@ -20,6 +20,7 @@ "format", "for_concrete_model", "ignore_duplicates", + "use_natural_foreign_keys", )) @@ -190,6 +191,7 @@ def _add_to_revision(obj, using, model_db, explicit): version_options.format, (obj,), fields=version_options.fields, + use_natural_foreign_keys=version_options.use_natural_foreign_keys, ), object_repr=force_str(obj), ) @@ -364,7 +366,7 @@ def _get_senders_and_signals(model): def register(model=None, fields=None, exclude=(), follow=(), format="json", - for_concrete_model=True, ignore_duplicates=False): + for_concrete_model=True, ignore_duplicates=False, use_natural_foreign_keys=False): def register(model): # Prevent multiple registration. if is_registered(model): @@ -388,6 +390,7 @@ def register(model): format=format, for_concrete_model=for_concrete_model, ignore_duplicates=ignore_duplicates, + use_natural_foreign_keys=use_natural_foreign_keys, ) # Register the model. _registered_models[_get_registration_key(model)] = version_options diff --git a/tests/test_app/migrations/0002_naturalkey.py b/tests/test_app/migrations/0002_naturalkey.py new file mode 100644 index 00000000..0b192faa --- /dev/null +++ b/tests/test_app/migrations/0002_naturalkey.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.12 on 2020-08-15 11:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='TestModelWithNaturalKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='v1', max_length=191)), + ], + ), + migrations.AlterField( + model_name='testmodelnestedinline', + name='nested_inline_name', + field=models.CharField(default='v1', max_length=191), + ), + migrations.CreateModel( + name='TestModelInlineByNaturalKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('test_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_app.TestModelWithNaturalKey')), + ], + ), + ] diff --git a/tests/test_app/models.py b/tests/test_app/models.py index 3f66e4a7..a21e94f8 100644 --- a/tests/test_app/models.py +++ b/tests/test_app/models.py @@ -118,3 +118,27 @@ class TestMeta(models.Model): name = models.CharField( max_length=191, ) + + +class TestModelWithNaturalKeyManager(models.Manager): + def get_by_natural_key(self, name): + return self.get(name=name) + + +class TestModelWithNaturalKey(models.Model): + name = models.CharField( + max_length=191, + default="v1", + ) + + objects = TestModelWithNaturalKeyManager() + + def natural_key(self): + return (self.name,) + + +class TestModelInlineByNaturalKey(models.Model): + test_model = models.ForeignKey( + TestModelWithNaturalKey, + on_delete=models.CASCADE, + ) diff --git a/tests/test_app/tests/test_models.py b/tests/test_app/tests/test_models.py index f46c0335..fbb20f35 100644 --- a/tests/test_app/tests/test_models.py +++ b/tests/test_app/tests/test_models.py @@ -3,8 +3,10 @@ from test_app.models import ( TestModel, TestModelRelated, TestModelParent, TestModelInline, TestModelNestedInline, + TestModelInlineByNaturalKey, TestModelWithNaturalKey, ) from test_app.tests.base import TestBase, TestModelMixin, TestModelParentMixin +import json class GetForModelTest(TestModelMixin, TestBase): @@ -412,3 +414,24 @@ def testRevertDeleteNestedInline(self): self.assertEqual( list(child_a.testmodelnestedinline_set.all()), [grandchild_a] ) + + +class NaturalKeyTest(TestBase): + + def setUp(self): + reversion.register(TestModelInlineByNaturalKey, use_natural_foreign_keys=True) + reversion.register(TestModelWithNaturalKey) + + def testNaturalKeyInline(self): + with reversion.create_revision(): + inline = TestModelWithNaturalKey.objects.create() + obj = TestModelInlineByNaturalKey.objects.create(test_model=inline) + self.assertEqual(json.loads(Version.objects.get_for_object(obj).get().serialized_data), [{ + 'fields': {'test_model': ['v1']}, + 'model': 'test_app.testmodelinlinebynaturalkey', + 'pk': 1 + }]) + self.assertEqual(Version.objects.get_for_object(obj).get().field_dict, { + 'test_model_id': 1, + 'id': 1, + })