summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerker Peksag <berker.peksag@gmail.com>2016-05-17 09:52:01 +0300
committerTim Graham <timograham@gmail.com>2016-06-09 10:35:32 -0400
commitae2a7da86bf841d42be86dea3effb0261187c950 (patch)
tree563af5761dfe6f02b4f4ca14c9500a94e1a0f5ee
parent21130ce1a9c4fcbfce4c614be9e5408b43092bf0 (diff)
Fixed #20468 -- Added loaddata --exclude option.
Thanks Alex Morozov for the initial patch.
-rw-r--r--django/core/management/commands/dumpdata.py17
-rw-r--r--django/core/management/commands/loaddata.py10
-rw-r--r--django/core/management/utils.py28
-rw-r--r--docs/ref/django-admin.txt8
-rw-r--r--docs/releases/1.11.txt3
-rw-r--r--tests/fixtures/tests.py28
6 files changed, 75 insertions, 19 deletions
diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
index b8547d684f..e16c8f1f17 100644
--- a/django/core/management/commands/dumpdata.py
+++ b/django/core/management/commands/dumpdata.py
@@ -4,6 +4,7 @@ from collections import OrderedDict
from django.apps import apps
from django.core import serializers
from django.core.management.base import BaseCommand, CommandError
+from django.core.management.utils import parse_apps_and_model_labels
from django.db import DEFAULT_DB_ALIAS, router
@@ -81,21 +82,7 @@ class Command(BaseCommand):
else:
primary_keys = []
- excluded_apps = set()
- excluded_models = set()
- for exclude in excludes:
- if '.' in exclude:
- try:
- model = apps.get_model(exclude)
- except LookupError:
- raise CommandError('Unknown model in excludes: %s' % exclude)
- excluded_models.add(model)
- else:
- try:
- app_config = apps.get_app_config(exclude)
- except LookupError as e:
- raise CommandError(str(e))
- excluded_apps.add(app_config)
+ excluded_models, excluded_apps = parse_apps_and_model_labels(excludes)
if len(app_labels) == 0:
if primary_keys:
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 1b0cee0d2b..d824011a03 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -13,6 +13,7 @@ from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
+from django.core.management.utils import parse_apps_and_model_labels
from django.db import (
DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, connections, router,
transaction,
@@ -52,13 +53,17 @@ class Command(BaseCommand):
help='Ignores entries in the serialized data for fields that do not '
'currently exist on the model.',
)
+ parser.add_argument(
+ '-e', '--exclude', dest='exclude', action='append', default=[],
+ help='An app_label or app_label.ModelName to exclude. Can be used multiple times.',
+ )
def handle(self, *fixture_labels, **options):
-
self.ignore = options['ignore']
self.using = options['database']
self.app_label = options['app_label']
self.verbosity = options['verbosity']
+ self.excluded_models, self.excluded_apps = parse_apps_and_model_labels(options['exclude'])
with transaction.atomic(using=self.using):
self.loaddata(fixture_labels)
@@ -160,6 +165,9 @@ class Command(BaseCommand):
for obj in objects:
objects_in_fixture += 1
+ if (obj.object._meta.app_config in self.excluded_apps or
+ type(obj.object) in self.excluded_models):
+ continue
if router.allow_migrate_model(self.using, obj.object.__class__):
loaded_objects_in_fixture += 1
self.models.add(obj.object.__class__)
diff --git a/django/core/management/utils.py b/django/core/management/utils.py
index 637091e68a..7ea005a6f0 100644
--- a/django/core/management/utils.py
+++ b/django/core/management/utils.py
@@ -4,6 +4,7 @@ import os
import sys
from subprocess import PIPE, Popen
+from django.apps import apps as installed_apps
from django.utils import six
from django.utils.crypto import get_random_string
from django.utils.encoding import DEFAULT_LOCALE_ENCODING, force_text
@@ -84,3 +85,30 @@ def get_random_secret_key():
"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
return get_random_string(50, chars)
+
+
+def parse_apps_and_model_labels(labels):
+ """
+ Parse a list of "app_label.ModelName" or "app_label" strings into actual
+ objects and return a two-element tuple:
+ (set of model classes, set of app_configs).
+ Raise a CommandError if some specified models or apps don't exist.
+ """
+ apps = set()
+ models = set()
+
+ for label in labels:
+ if '.' in label:
+ try:
+ model = installed_apps.get_model(label)
+ except LookupError:
+ raise CommandError('Unknown model: %s' % label)
+ models.add(model)
+ else:
+ try:
+ app_config = installed_apps.get_app_config(label)
+ except LookupError as e:
+ raise CommandError(str(e))
+ apps.add(app_config)
+
+ return models, apps
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 12c43f3dab..2ecf082575 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -416,6 +416,14 @@ originally generated.
Specifies a single app to look for fixtures in rather than looking in all apps.
+.. django-admin-option:: --exclude EXCLUDE, -e EXCLUDE
+
+.. versionadded:: 1.11
+
+Excludes loading the fixtures from the given applications and/or models (in the
+form of ``app_label`` or ``app_label.ModelName``). Use the option multiple
+times to exclude more than one app or model.
+
What's a "fixture"?
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index e8cf5ae0f3..ed4620ec44 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -169,7 +169,8 @@ Internationalization
Management Commands
~~~~~~~~~~~~~~~~~~~
-* ...
+* The new :option:`loaddata --exclude` option allows excluding models and apps
+ while loading data from fixtures.
Migrations
~~~~~~~~~~
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index b021d927f0..95240652f3 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -20,7 +20,7 @@ from django.test import (
from django.utils import six
from django.utils.encoding import force_text
-from .models import Article, ProxySpy, Spy, Tag, Visa
+from .models import Article, Category, ProxySpy, Spy, Tag, Visa
class TestCaseFixtureLoadingTests(TestCase):
@@ -370,7 +370,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
# Excluding a bogus model should throw an error
- with self.assertRaisesMessage(management.CommandError, "Unknown model in excludes: fixtures.FooModel"):
+ with self.assertRaisesMessage(management.CommandError, "Unknown model: fixtures.FooModel"):
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
@unittest.skipIf(sys.platform.startswith('win'), "Windows doesn't support '?' in filenames.")
@@ -650,6 +650,30 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
format='xml', natural_foreign_keys=True
)
+ def test_loading_with_exclude_app(self):
+ Site.objects.all().delete()
+ management.call_command('loaddata', 'fixture1', exclude=['fixtures'], verbosity=0)
+ self.assertFalse(Article.objects.exists())
+ self.assertFalse(Category.objects.exists())
+ self.assertQuerysetEqual(Site.objects.all(), ['<Site: example.com>'])
+
+ def test_loading_with_exclude_model(self):
+ Site.objects.all().delete()
+ management.call_command('loaddata', 'fixture1', exclude=['fixtures.Article'], verbosity=0)
+ self.assertFalse(Article.objects.exists())
+ self.assertQuerysetEqual(Category.objects.all(), ['<Category: News Stories>'])
+ self.assertQuerysetEqual(Site.objects.all(), ['<Site: example.com>'])
+
+ def test_exclude_option_errors(self):
+ """Excluding a bogus app or model should raise an error."""
+ msg = "No installed app with label 'foo_app'."
+ with self.assertRaisesMessage(management.CommandError, msg):
+ management.call_command('loaddata', 'fixture1', exclude=['foo_app'], verbosity=0)
+
+ msg = "Unknown model: fixtures.FooModel"
+ with self.assertRaisesMessage(management.CommandError, msg):
+ management.call_command('loaddata', 'fixture1', exclude=['fixtures.FooModel'], verbosity=0)
+
class NonExistentFixtureTests(TestCase):
"""