Fixed #20468 -- Added loaddata --exclude option.
Thanks Alex Morozov for the initial patch.
This commit is contained in:
parent
21130ce1a9
commit
ae2a7da86b
|
@ -4,6 +4,7 @@ from collections import OrderedDict
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
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
|
from django.db import DEFAULT_DB_ALIAS, router
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,21 +82,7 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
primary_keys = []
|
primary_keys = []
|
||||||
|
|
||||||
excluded_apps = set()
|
excluded_models, excluded_apps = parse_apps_and_model_labels(excludes)
|
||||||
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)
|
|
||||||
|
|
||||||
if len(app_labels) == 0:
|
if len(app_labels) == 0:
|
||||||
if primary_keys:
|
if primary_keys:
|
||||||
|
|
|
@ -13,6 +13,7 @@ from django.core import serializers
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
|
from django.core.management.utils import parse_apps_and_model_labels
|
||||||
from django.db import (
|
from django.db import (
|
||||||
DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, connections, router,
|
DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, connections, router,
|
||||||
transaction,
|
transaction,
|
||||||
|
@ -52,13 +53,17 @@ class Command(BaseCommand):
|
||||||
help='Ignores entries in the serialized data for fields that do not '
|
help='Ignores entries in the serialized data for fields that do not '
|
||||||
'currently exist on the model.',
|
'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):
|
def handle(self, *fixture_labels, **options):
|
||||||
|
|
||||||
self.ignore = options['ignore']
|
self.ignore = options['ignore']
|
||||||
self.using = options['database']
|
self.using = options['database']
|
||||||
self.app_label = options['app_label']
|
self.app_label = options['app_label']
|
||||||
self.verbosity = options['verbosity']
|
self.verbosity = options['verbosity']
|
||||||
|
self.excluded_models, self.excluded_apps = parse_apps_and_model_labels(options['exclude'])
|
||||||
|
|
||||||
with transaction.atomic(using=self.using):
|
with transaction.atomic(using=self.using):
|
||||||
self.loaddata(fixture_labels)
|
self.loaddata(fixture_labels)
|
||||||
|
@ -160,6 +165,9 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
objects_in_fixture += 1
|
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__):
|
if router.allow_migrate_model(self.using, obj.object.__class__):
|
||||||
loaded_objects_in_fixture += 1
|
loaded_objects_in_fixture += 1
|
||||||
self.models.add(obj.object.__class__)
|
self.models.add(obj.object.__class__)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from subprocess import PIPE, Popen
|
from subprocess import PIPE, Popen
|
||||||
|
|
||||||
|
from django.apps import apps as installed_apps
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.encoding import DEFAULT_LOCALE_ENCODING, force_text
|
from django.utils.encoding import DEFAULT_LOCALE_ENCODING, force_text
|
||||||
|
@ -84,3 +85,30 @@ def get_random_secret_key():
|
||||||
"""
|
"""
|
||||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
||||||
return get_random_string(50, chars)
|
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
|
||||||
|
|
|
@ -416,6 +416,14 @@ originally generated.
|
||||||
|
|
||||||
Specifies a single app to look for fixtures in rather than looking in all apps.
|
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"?
|
What's a "fixture"?
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,8 @@ Internationalization
|
||||||
Management Commands
|
Management Commands
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The new :option:`loaddata --exclude` option allows excluding models and apps
|
||||||
|
while loading data from fixtures.
|
||||||
|
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
|
@ -20,7 +20,7 @@ from django.test import (
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_text
|
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):
|
class TestCaseFixtureLoadingTests(TestCase):
|
||||||
|
@ -370,7 +370,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
|
||||||
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
|
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
|
||||||
|
|
||||||
# Excluding a bogus model should throw an error
|
# 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'])
|
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith('win'), "Windows doesn't support '?' in filenames.")
|
@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
|
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):
|
class NonExistentFixtureTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue