summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPreston Holmes <preston@ptone.com>2012-09-30 16:34:13 +0400
committerPreston Holmes <preston@ptone.com>2012-09-30 23:40:27 -0700
commite7723683dc652613df369d5ca412e8b1217012d3 (patch)
treeac531a7e8bdf7b363c9942951f9be8c5fefce85c
parent7cc4068c4470876c526830778cbdac2fdfd6dc26 (diff)
Fixed #9279 -- Added ignorenonexistent option to loaddata
Thanks to Roman Gladkov for the initial patch and Simon Charette for review.
-rw-r--r--django/core/management/commands/loaddata.py8
-rw-r--r--django/core/serializers/python.py12
-rw-r--r--docs/ref/django-admin.txt5
-rw-r--r--docs/releases/1.5.txt3
-rw-r--r--docs/topics/serialization.txt8
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json13
-rw-r--r--tests/regressiontests/fixtures_regress/tests.py29
7 files changed, 76 insertions, 2 deletions
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 30cf740cdf..a2e7f7d4c9 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -23,6 +23,7 @@ try:
except ImportError:
has_bz2 = False
+
class Command(BaseCommand):
help = 'Installs the named fixture(s) in the database.'
args = "fixture [fixture ...]"
@@ -31,9 +32,14 @@ class Command(BaseCommand):
make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load '
'fixtures into. Defaults to the "default" database.'),
+ make_option('--ignorenonexistent', '-i', action='store_true', dest='ignore',
+ default=False, help='Ignores entries in the serialised data for fields'
+ ' that have been removed from the database'),
)
def handle(self, *fixture_labels, **options):
+
+ ignore = options.get('ignore')
using = options.get('database')
connection = connections[using]
@@ -175,7 +181,7 @@ class Command(BaseCommand):
self.stdout.write("Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir)))
- objects = serializers.deserialize(format, fixture, using=using)
+ objects = serializers.deserialize(format, fixture, using=using, ignorenonexistent=ignore)
for obj in objects:
objects_in_fixture += 1
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index a1fff6f9bb..37fa906280 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -11,6 +11,7 @@ from django.db import models, DEFAULT_DB_ALIAS
from django.utils.encoding import smart_text, is_protected_type
from django.utils import six
+
class Serializer(base.Serializer):
"""
Serializes a QuerySet to basic Python objects.
@@ -72,6 +73,7 @@ class Serializer(base.Serializer):
def getvalue(self):
return self.objects
+
def Deserializer(object_list, **options):
"""
Deserialize simple Python objects back into Django ORM instances.
@@ -80,15 +82,23 @@ def Deserializer(object_list, **options):
stream or a string) to the constructor
"""
db = options.pop('using', DEFAULT_DB_ALIAS)
+ ignore = options.pop('ignorenonexistent', False)
+
models.get_apps()
for d in object_list:
# Look up the model and starting build a dict of data for it.
Model = _get_model(d["model"])
- data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
+ data = {Model._meta.pk.attname: Model._meta.pk.to_python(d["pk"])}
m2m_data = {}
+ model_fields = Model._meta.get_all_field_names()
# Handle each field
for (field_name, field_value) in six.iteritems(d["fields"]):
+
+ if ignore and field_name not in model_fields:
+ # skip fields no longer on model
+ continue
+
if isinstance(field_value, str):
field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True)
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 93e8fd9856..7fa7539985 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -289,6 +289,11 @@ Searches for and loads the contents of the named fixture into the database.
The :djadminopt:`--database` option can be used to specify the database
onto which the data will be loaded.
+.. versionadded:: 1.5
+
+The :djadminopt:`--ignorenonexistent` option can be used to ignore fields that
+may have been removed from models since the fixture was originally generated.
+
What's a "fixture"?
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index 546170b2a8..11b3488c11 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -195,6 +195,9 @@ Django 1.5 also includes several smaller improvements worth noting:
whenever a user fails to login successfully. See
:data:`~django.contrib.auth.signals.user_login_failed`
+* The loaddata management command now supports an `ignorenonexistent` option to
+ ignore data for fields that no longer exist.
+
Backwards incompatible changes in 1.5
=====================================
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index ac1a77ed98..9b44166e42 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -130,6 +130,14 @@ trust your data source you could just save the object and move on.
The Django object itself can be inspected as ``deserialized_object.object``.
+.. versionadded:: 1.5
+
+If fields in the serialized data do not exist on a model,
+a ``DeserializationError`` will be raised unless the ``ignorenonexistent``
+argument is passed in as True::
+
+ serializers.deserialize("xml", data, ignorenonexistent=True)
+
.. _serialization-formats:
Serialization formats
diff --git a/tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json b/tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json
new file mode 100644
index 0000000000..03c0f36696
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json
@@ -0,0 +1,13 @@
+[
+ {
+ "pk": "1",
+ "model": "fixtures_regress.animal",
+ "fields": {
+ "name": "Lion",
+ "extra_name": "Super Lion",
+ "latin_name": "Panthera leo",
+ "count": 3,
+ "weight": 1.2
+ }
+ }
+]
diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
index d675372c7a..c9b9058dff 100644
--- a/tests/regressiontests/fixtures_regress/tests.py
+++ b/tests/regressiontests/fixtures_regress/tests.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import, unicode_literals
import os
import re
+from django.core.serializers.base import DeserializationError
from django.core import management
from django.core.management.base import CommandError
from django.core.management.commands.dumpdata import sort_dependencies
@@ -22,6 +23,7 @@ from .models import (Animal, Stuff, Absolute, Parent, Child, Article, Widget,
class TestFixtures(TestCase):
+
def animal_pre_save_check(self, signal, sender, instance, **kwargs):
self.pre_save_checks.append(
(
@@ -54,6 +56,33 @@ class TestFixtures(TestCase):
animal.save()
self.assertGreater(animal.id, 1)
+ def test_loaddata_not_found_fields_not_ignore(self):
+ """
+ Test for ticket #9279 -- Error is raised for entries in
+ the serialised data for fields that have been removed
+ from the database when not ignored.
+ """
+ with self.assertRaises(DeserializationError):
+ management.call_command(
+ 'loaddata',
+ 'sequence_extra',
+ verbosity=0
+ )
+
+ def test_loaddata_not_found_fields_ignore(self):
+ """
+ Test for ticket #9279 -- Ignores entries in
+ the serialised data for fields that have been removed
+ from the database.
+ """
+ management.call_command(
+ 'loaddata',
+ 'sequence_extra',
+ ignore=True,
+ verbosity=0
+ )
+ self.assertEqual(Animal.specimens.all()[0].name, 'Lion')
+
@skipIfDBFeature('interprets_empty_strings_as_nulls')
def test_pretty_print_xml(self):
"""