From 0c37f8d81f483b6435cb591aa6b4759df763e9b9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 16:48:07 +0000 Subject: [PATCH] Fixed #12775 -- Modified the --exclude argument to dumpdata to allow exclusion of individual models. Thanks to emulbreh for the suggestion, and Joshua Ginsberg for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@13511 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/core/management/commands/dumpdata.py | 27 +++++++++-- docs/ref/django-admin.txt | 5 ++ tests/modeltests/fixtures/tests.py | 51 ++++++++++++++++++++- 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index b16225741bb..31433adbdac 100644 --- a/AUTHORS +++ b/AUTHORS @@ -184,6 +184,7 @@ answer newbie questions, and generally made Django that much better: Idan Gazit geber@datacollect.com Baishampayan Ghose + Joshua Ginsberg Dimitris Glezos glin@seznam.cz martin.glueck@gmail.com diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 23c03e7b17f..d2fe6cdd07b 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -16,7 +16,7 @@ class Command(BaseCommand): default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load ' 'fixtures into. Defaults to the "default" database.'), make_option('-e', '--exclude', dest='exclude',action='append', default=[], - help='App to exclude (use multiple --exclude to exclude multiple apps).'), + help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).'), make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False, help='Use natural keys if they are available.'), ) @@ -31,11 +31,25 @@ class Command(BaseCommand): indent = options.get('indent',None) using = options.get('database', DEFAULT_DB_ALIAS) connection = connections[using] - exclude = options.get('exclude',[]) + excludes = options.get('exclude',[]) show_traceback = options.get('traceback', False) use_natural_keys = options.get('use_natural_keys', False) - excluded_apps = set(get_app(app_label) for app_label in exclude) + excluded_apps = set() + excluded_models = set() + for exclude in excludes: + if '.' in exclude: + app_label, model_name = exclude.split('.', 1) + model_obj = get_model(app_label, model_name) + if not model_obj: + raise CommandError('Unknown model in excludes: %s' % exclude) + excluded_models.add(model_obj) + else: + try: + app_obj = get_app(exclude) + excluded_apps.add(app_obj) + except ImproperlyConfigured: + raise CommandError('Unknown app in excludes: %s' % exclude) if len(app_labels) == 0: app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps) @@ -48,7 +62,8 @@ class Command(BaseCommand): app = get_app(app_label) except ImproperlyConfigured: raise CommandError("Unknown application: %s" % app_label) - + if app in excluded_apps: + continue model = get_model(app_label, model_label) if model is None: raise CommandError("Unknown model: %s.%s" % (app_label, model_label)) @@ -65,6 +80,8 @@ class Command(BaseCommand): app = get_app(app_label) except ImproperlyConfigured: raise CommandError("Unknown application: %s" % app_label) + if app in excluded_apps: + continue app_list[app] = None # Check that the serialization format exists; this is a shortcut to @@ -80,6 +97,8 @@ class Command(BaseCommand): # Now collate the objects to be serialized. objects = [] for model in sort_dependencies(app_list.items()): + if model in excluded_models: + continue if not model._meta.proxy and router.allow_syncdb(using, model): objects.extend(model._default_manager.using(using).all()) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 542a71582dd..e3ccda22a54 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -227,6 +227,11 @@ pretty-print the output with a number of indentation spaces. The :djadminopt:`--exclude` option may be provided to prevent specific applications from being dumped. +.. versionadded:: 1.3 + +The :djadminopt:`--exclude` option may also be provided to prevent specific +models (specified as in the form of ``appname.ModelName``) from being dumped. + .. versionadded:: 1.1 In addition to specifying application names, you can provide a list of diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index 4facc6dee96..e818423b482 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -23,9 +23,14 @@ class TestCaseFixtureLoadingTests(TestCase): class FixtureLoadingTests(TestCase): - def _dumpdata_assert(self, args, output, format='json', natural_keys=False): + def _dumpdata_assert(self, args, output, format='json', natural_keys=False, + exclude_list=[]): new_io = StringIO.StringIO() - management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io, 'use_natural_keys':natural_keys}) + management.call_command('dumpdata', *args, **{'format':format, + 'stdout':new_io, + 'stderr':new_io, + 'use_natural_keys':natural_keys, + 'exclude': exclude_list}) command_output = new_io.getvalue().strip() self.assertEqual(command_output, output) @@ -150,6 +155,48 @@ class FixtureLoadingTests(TestCase): self._dumpdata_assert(['fixtures'], """ News StoriesLatest news storiesXML identified as leading cause of cancer2006-06-16 16:00:00Django conquers world!2006-06-16 15:00:00Copyright is fine the way it is2006-06-16 14:00:00Poker on TV is great!2006-06-16 11:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3legalfixturesarticle3djangofixturesarticle4world dominationfixturesarticle4Artist formerly known as "Prince"Django ReinhardtStephane GrappelliDjango Reinhardtadd_userauthuserchange_userauthuserdelete_userauthuserStephane Grappelliadd_userauthuserdelete_userauthuserArtist formerly known as "Prince"change_userauthuserMusic for all agesArtist formerly known as "Prince"Django Reinhardt""", format='xml', natural_keys=True) + def test_dumpdata_with_excludes(self): + # Load fixture1 which has a site, two articles, and a category + management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False) + + # Excluding fixtures app should only leave sites + self._dumpdata_assert( + ['sites', 'fixtures'], + '[{"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]', + exclude_list=['fixtures']) + + # Excluding fixtures.Article should leave fixtures.Category + self._dumpdata_assert( + ['sites', 'fixtures'], + '[{"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}, {"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]', + exclude_list=['fixtures.Article']) + + # Excluding fixtures and fixtures.Article should be a no-op + self._dumpdata_assert( + ['sites', 'fixtures'], + '[{"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}, {"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]', + exclude_list=['fixtures.Article']) + + # Excluding sites and fixtures.Article should only leave fixtures.Category + self._dumpdata_assert( + ['sites', 'fixtures'], + '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]', + exclude_list=['fixtures.Article', 'sites']) + + # Excluding a bogus app should throw an error + self.assertRaises(SystemExit, + self._dumpdata_assert, + ['fixtures', 'sites'], + '', + exclude_list=['foo_app']) + + # Excluding a bogus model should throw an error + self.assertRaises(SystemExit, + self._dumpdata_assert, + ['fixtures', 'sites'], + '', + exclude_list=['fixtures.FooModel']) + def test_compress_format_loading(self): # Load fixture 4 (compressed), using format specification management.call_command('loaddata', 'fixture4.json', verbosity=0, commit=False)