diff --git a/django/core/management/base.py b/django/core/management/base.py index a07fc7d56ff..6761cb69bc7 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -213,6 +213,8 @@ class BaseCommand(object): sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) sys.exit(1) try: + self.stdout = options.get('stdout', sys.stdout) + self.stderr = options.get('stderr', sys.stderr) if self.requires_model_validation: self.validate() output = self.handle(*args, **options) @@ -223,12 +225,12 @@ class BaseCommand(object): from django.db import connections, DEFAULT_DB_ALIAS connection = connections[options.get('database', DEFAULT_DB_ALIAS)] if connection.ops.start_transaction_sql(): - print self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) - print output + self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())) + self.stdout.write(output) if self.output_transaction: - print self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()) + self.stdout.write(self.style.SQL_KEYWORD("COMMIT;") + '\n') except CommandError, e: - sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) + self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) sys.exit(1) def validate(self, app=None, display_num_errors=False): @@ -250,7 +252,7 @@ class BaseCommand(object): error_text = s.read() raise CommandError("One or more models did not validate:\n%s" % error_text) if display_num_errors: - print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '') + self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or '')) def handle(self, *args, **options): """ diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 6212d6151dc..caf3b11b85a 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -112,10 +112,10 @@ class Command(BaseCommand): if formats: if verbosity > 1: - print "Loading '%s' fixtures..." % fixture_name + self.stdout.write("Loading '%s' fixtures...\n" % fixture_name) else: sys.stderr.write( - self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format." % + self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % (fixture_name, format))) transaction.rollback(using=using) transaction.leave_transaction_management(using=using) @@ -128,7 +128,7 @@ class Command(BaseCommand): for fixture_dir in fixture_dirs: if verbosity > 1: - print "Checking %s for fixtures..." % humanize(fixture_dir) + self.stdout.write("Checking %s for fixtures...\n" % humanize(fixture_dir)) label_found = False for combo in product([using, None], formats, compression_formats): @@ -141,16 +141,16 @@ class Command(BaseCommand): ) if verbosity > 1: - print "Trying %s for %s fixture '%s'..." % \ - (humanize(fixture_dir), file_name, fixture_name) + self.stdout.write("Trying %s for %s fixture '%s'...\n" % \ + (humanize(fixture_dir), file_name, fixture_name)) full_path = os.path.join(fixture_dir, file_name) open_method = compression_types[compression_format] try: fixture = open_method(full_path, 'r') if label_found: fixture.close() - print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." % - (fixture_name, humanize(fixture_dir))) + self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" % + (fixture_name, humanize(fixture_dir)))) transaction.rollback(using=using) transaction.leave_transaction_management(using=using) return @@ -158,8 +158,8 @@ class Command(BaseCommand): fixture_count += 1 objects_in_fixture = 0 if verbosity > 0: - print "Installing %s fixture '%s' from %s." % \ - (format, fixture_name, humanize(fixture_dir)) + self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ + (format, fixture_name, humanize(fixture_dir))) try: objects = serializers.deserialize(format, fixture, using=using) for obj in objects: @@ -190,7 +190,7 @@ class Command(BaseCommand): # error was encountered during fixture loading. if objects_in_fixture == 0: sys.stderr.write( - self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" % + self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % (fixture_name))) transaction.rollback(using=using) transaction.leave_transaction_management(using=using) @@ -198,8 +198,8 @@ class Command(BaseCommand): except Exception, e: if verbosity > 1: - print "No %s fixture '%s' in %s." % \ - (format, fixture_name, humanize(fixture_dir)) + self.stdout.write("No %s fixture '%s' in %s.\n" % \ + (format, fixture_name, humanize(fixture_dir))) # If we found even one object in a fixture, we need to reset the # database sequences. @@ -207,7 +207,7 @@ class Command(BaseCommand): sequence_sql = connection.ops.sequence_reset_sql(self.style, models) if sequence_sql: if verbosity > 1: - print "Resetting sequences" + self.stdout.write("Resetting sequences\n") for line in sequence_sql: cursor.execute(line) @@ -217,10 +217,10 @@ class Command(BaseCommand): if object_count == 0: if verbosity > 0: - print "No fixtures found." + self.stdout.write("No fixtures found.\n") else: if verbosity > 0: - print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count) + self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % (object_count, fixture_count)) # Close the DB connection. This is required as a workaround for an # edge case in MySQL: if the same connection is used to diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index f8b173cafa0..3f5feaa67ae 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -8,7 +8,7 @@ Writing custom django-admin commands Applications can register their own actions with ``manage.py``. For example, you might want to add a ``manage.py`` action for a Django app that you're -distributing. In this document, we will be building a custom ``closepoll`` +distributing. In this document, we will be building a custom ``closepoll`` command for the ``polls`` application from the :ref:`tutorial`. @@ -62,9 +62,16 @@ look like this: poll.opened = False poll.save() - print 'Successfully closed poll "%s"' % poll_id + self.stdout.write('Successfully closed poll "%s"\n' % poll_id) -The new custom command can be called using ``python manage.py closepoll +.. note:: + When you are using management commands and wish to provide console + output, you should write to ``self.stdout`` and ``self.stderr``, + instead of printing to ``stdout`` and ``stderr`` directly. By + using these proxies, it becomes much easier to test your custom + command. + +The new custom command can be called using ``python manage.py closepoll ``. The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened`` @@ -91,8 +98,8 @@ must be added to :attr:`~BaseCommand.option_list` like this: ) # ... -In addition to being able to add custom command line options, all -:ref:`management commands` can accept some +In addition to being able to add custom command line options, all +:ref:`management commands` can accept some default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`. Command objects @@ -113,7 +120,7 @@ Subclassing the :class:`BaseCommand` class requires that you implement the Attributes ---------- -All attributes can be set in your derived class and can be used in +All attributes can be set in your derived class and can be used in :class:`BaseCommand`'s :ref:`subclasses`. .. attribute:: BaseCommand.args @@ -133,7 +140,7 @@ All attributes can be set in your derived class and can be used in .. attribute:: BaseCommand.help A short description of the command, which will be printed in the - help message when the user runs the command + help message when the user runs the command ``python manage.py help ``. .. attribute:: BaseCommand.option_list @@ -230,7 +237,7 @@ Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement A command which takes no arguments on the command line. Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement -:meth:`~NoArgsCommand.handle_noargs`; :meth:`~BaseCommand.handle` itself is +:meth:`~NoArgsCommand.handle_noargs`; :meth:`~BaseCommand.handle` itself is overridden to ensure no arguments are passed to the command. .. method:: NoArgsCommand.handle_noargs(**options) diff --git a/tests/modeltests/fixtures/models.py b/tests/modeltests/fixtures/models.py index 46e07a5e6b5..216a8e2502a 100644 --- a/tests/modeltests/fixtures/models.py +++ b/tests/modeltests/fixtures/models.py @@ -90,230 +90,3 @@ class Book(models.Model): class Meta: ordering = ('name',) - -__test__ = {'API_TESTS': """ ->>> from django.core import management ->>> from django.db.models import get_app - -# Reset the database representation of this app. -# This will return the database to a clean initial state. ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Syncdb introduces 1 initial data object from initial_data.json. ->>> Article.objects.all() -[] - -# Load fixture 1. Single JSON file, with two objects. ->>> management.call_command('loaddata', 'fixture1.json', verbosity=0) ->>> Article.objects.all() -[, , ] - -# Dump the current contents of the database as a JSON fixture ->>> management.call_command('dumpdata', 'fixtures', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Try just dumping the contents of fixtures.Category ->>> management.call_command('dumpdata', 'fixtures.Category', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}] - -# ...and just fixtures.Article ->>> management.call_command('dumpdata', 'fixtures.Article', format='json') -[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# ...and both ->>> management.call_command('dumpdata', 'fixtures.Category', 'fixtures.Article', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Specify a specific model twice ->>> management.call_command('dumpdata', 'fixtures.Article', 'fixtures.Article', format='json') -[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Specify a dump that specifies Article both explicitly and implicitly ->>> management.call_command('dumpdata', 'fixtures.Article', 'fixtures', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Same again, but specify in the reverse order ->>> management.call_command('dumpdata', 'fixtures', 'fixtures.Article', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Specify one model from one application, and an entire other application. ->>> management.call_command('dumpdata', 'fixtures.Category', 'sites', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}] - -# Load fixture 2. JSON file imported by default. Overwrites some existing objects ->>> management.call_command('loaddata', 'fixture2.json', verbosity=0) ->>> Article.objects.all() -[, , , ] - -# Load fixture 3, XML format. ->>> management.call_command('loaddata', 'fixture3.xml', verbosity=0) ->>> Article.objects.all() -[, , , , ] - -# Load fixture 6, JSON file with dynamic ContentType fields. Testing ManyToOne. ->>> management.call_command('loaddata', 'fixture6.json', verbosity=0) ->>> Tag.objects.all() -[ tagged "copyright">, tagged "law">] - -# Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne. ->>> management.call_command('loaddata', 'fixture7.xml', verbosity=0) ->>> Tag.objects.all() -[ tagged "copyright">, tagged "legal">, tagged "django">, tagged "world domination">] - -# Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany. ->>> management.call_command('loaddata', 'fixture8.json', verbosity=0) ->>> Visa.objects.all() -[, , ] - -# Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany. ->>> management.call_command('loaddata', 'fixture9.xml', verbosity=0) ->>> Visa.objects.all() -[, , ] - ->>> Book.objects.all() -[] - -# Load a fixture that doesn't exist ->>> management.call_command('loaddata', 'unknown.json', verbosity=0) - -# object list is unaffected ->>> Article.objects.all() -[, , , , ] - -# By default, you get raw keys on dumpdata ->>> management.call_command('dumpdata', 'fixtures.book', format='json') -[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}] - -# But you can get natural keys if you ask for them and they are available ->>> management.call_command('dumpdata', 'fixtures.book', format='json', use_natural_keys=True) -[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}] - -# Dump the current contents of the database as a JSON fixture ->>> management.call_command('dumpdata', 'fixtures', format='json', use_natural_keys=True) -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}] - -# Dump the current contents of the database as an XML fixture ->>> management.call_command('dumpdata', 'fixtures', format='xml', use_natural_keys=True) - -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 - -"""} - -# Database flushing does not work on MySQL with the default storage engine -# because it requires transaction support. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': - __test__['API_TESTS'] += \ -""" -# Reset the database representation of this app. This will delete all data. ->>> management.call_command('flush', verbosity=0, interactive=False) ->>> Article.objects.all() -[] - -# Load fixture 1 again, using format discovery ->>> management.call_command('loaddata', 'fixture1', verbosity=0) ->>> Article.objects.all() -[, , ] - -# Try to load fixture 2 using format discovery; this will fail -# because there are two fixture2's in the fixtures directory ->>> management.call_command('loaddata', 'fixture2', verbosity=0) # doctest: +ELLIPSIS -Multiple fixtures named 'fixture2' in '...fixtures'. Aborting. - -# object list is unaffected ->>> Article.objects.all() -[, , ] - -# Dump the current contents of the database as a JSON fixture ->>> management.call_command('dumpdata', 'fixtures', format='json') -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] - -# Load fixture 4 (compressed), using format discovery ->>> management.call_command('loaddata', 'fixture4', verbosity=0) ->>> Article.objects.all() -[, , , ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load fixture 4 (compressed), using format specification ->>> management.call_command('loaddata', 'fixture4.json', verbosity=0) ->>> Article.objects.all() -[, ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load fixture 5 (compressed), using format *and* compression specification ->>> management.call_command('loaddata', 'fixture5.json.zip', verbosity=0) ->>> Article.objects.all() -[, ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load fixture 5 (compressed), only compression specification ->>> management.call_command('loaddata', 'fixture5.zip', verbosity=0) ->>> Article.objects.all() -[, ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Try to load fixture 5 using format and compression discovery; this will fail -# because there are two fixture5's in the fixtures directory ->>> management.call_command('loaddata', 'fixture5', verbosity=0) # doctest: +ELLIPSIS -Multiple fixtures named 'fixture5' in '...fixtures'. Aborting. - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load db fixtures 1 and 2. These will load using the 'default' database identifier implicitly ->>> management.call_command('loaddata', 'db_fixture_1', verbosity=0) ->>> management.call_command('loaddata', 'db_fixture_2', verbosity=0) ->>> Article.objects.all() -[, , ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load db fixtures 1 and 2. These will load using the 'default' database identifier explicitly ->>> management.call_command('loaddata', 'db_fixture_1', verbosity=0, using='default') ->>> management.call_command('loaddata', 'db_fixture_2', verbosity=0, using='default') ->>> Article.objects.all() -[, , ] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Try to load db fixture 3. This won't load because the database identifier doesn't match ->>> management.call_command('loaddata', 'db_fixture_3', verbosity=0) ->>> Article.objects.all() -[] - ->>> management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default') ->>> Article.objects.all() -[] - ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Load back in fixture 1, we need the articles from it ->>> management.call_command('loaddata', 'fixture1', verbosity=0) - -# Try to load fixture 6 using format discovery ->>> management.call_command('loaddata', 'fixture6', verbosity=0) ->>> Tag.objects.all() -[ tagged "copyright">, tagged "law">] - -# Dump the current contents of the database as a JSON fixture ->>> management.call_command('dumpdata', 'fixtures', format='json', use_natural_keys=True) -[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}] - -# Dump the current contents of the database as an XML fixture ->>> management.call_command('dumpdata', 'fixtures', format='xml', use_natural_keys=True) - -News StoriesLatest news storiesTime to reform copyright2006-06-16 13:00:00Poker has no place on ESPN2006-06-16 12:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtPrinceStephane Grappelli - -""" - -from django.test import TestCase - -class SampleTestCase(TestCase): - fixtures = ['fixture1.json', 'fixture2.json'] - - def testClassFixtures(self): - "Check that test case has installed 4 fixture objects" - self.assertEqual(Article.objects.count(), 4) - self.assertEquals(str(Article.objects.all()), "[, , , ]") diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py new file mode 100644 index 00000000000..ba7ab341410 --- /dev/null +++ b/tests/modeltests/fixtures/tests.py @@ -0,0 +1,277 @@ +import StringIO +import sys + +from django.test import TestCase, TransactionTestCase +from django.conf import settings +from django.core import management +from django.db import DEFAULT_DB_ALIAS + +from models import Article, Blog, Book, Category, Person, Tag, Visa + +class TestCaseFixtureLoadingTests(TestCase): + fixtures = ['fixture1.json', 'fixture2.json'] + + def testClassFixtures(self): + "Check that test case has installed 4 fixture objects" + self.assertEqual(Article.objects.count(), 4) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '', + '' + ]) + +class FixtureLoadingTests(TestCase): + + def _dumpdata_assert(self, args, output, format='json', natural_keys=False): + new_io = StringIO.StringIO() + management.call_command('dumpdata', *args, format=format, stdout=new_io, use_natural_keys=natural_keys) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, output) + + def test_initial_data(self): + # Syncdb introduces 1 initial data object from initial_data.json. + self.assertQuerysetEqual(Article.objects.all(), [ + '' + ]) + + def test_loading_and_dumping(self): + new_io = StringIO.StringIO() + + # Load fixture 1. Single JSON file, with two objects. + management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '' + ]) + + # Dump the current contents of the database as a JSON fixture + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Try just dumping the contents of fixtures.Category + self._dumpdata_assert(['fixtures.Category'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]') + + # ...and just fixtures.Article + self._dumpdata_assert(['fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # ...and both + self._dumpdata_assert(['fixtures.Category', 'fixtures.Article'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Specify a specific model twice + self._dumpdata_assert(['fixtures.Article', 'fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Specify a dump that specifies Article both explicitly and implicitly + self._dumpdata_assert(['fixtures.Article', 'fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Same again, but specify in the reverse order + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Specify one model from one application, and an entire other application. + self._dumpdata_assert(['fixtures.Category', 'sites'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]') + + # Load fixture 2. JSON file imported by default. Overwrites some existing objects + management.call_command('loaddata', 'fixture2.json', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '', + '' + ]) + + # Load fixture 3, XML format. + management.call_command('loaddata', 'fixture3.xml', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '', + '', + '' + ]) + + # Load fixture 6, JSON file with dynamic ContentType fields. Testing ManyToOne. + management.call_command('loaddata', 'fixture6.json', verbosity=0, commit=False) + self.assertQuerysetEqual(Tag.objects.all(), [ + ' tagged "copyright">', + ' tagged "law">' + ]) + + # Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne. + management.call_command('loaddata', 'fixture7.xml', verbosity=0, commit=False) + self.assertQuerysetEqual(Tag.objects.all(), [ + ' tagged "copyright">', + ' tagged "legal">', + ' tagged "django">', + ' tagged "world domination">' + ]) + + # Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany. + management.call_command('loaddata', 'fixture8.json', verbosity=0, commit=False) + self.assertQuerysetEqual(Visa.objects.all(), [ + '', + '', + '' + ]) + + # Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany. + management.call_command('loaddata', 'fixture9.xml', verbosity=0, commit=False) + self.assertQuerysetEqual(Visa.objects.all(), [ + '', + '', + '' + ]) + + self.assertQuerysetEqual(Book.objects.all(), [ + '' + ]) + + # Load a fixture that doesn't exist + management.call_command('loaddata', 'unknown.json', verbosity=0, commit=False) + + # object list is unaffected + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '', + '', + '' + ]) + + # By default, you get raw keys on dumpdata + self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}]') + + # But you can get natural keys if you ask for them and they are available + self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True) + + # Dump the current contents of the database as a JSON fixture + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True) + + # Dump the current contents of the database as an XML fixture + 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_compress_format_loading(self): + # Load fixture 4 (compressed), using format specification + management.call_command('loaddata', 'fixture4.json', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '' + ]) + + def test_compressed_specified_loading(self): + # Load fixture 5 (compressed), using format *and* compression specification + management.call_command('loaddata', 'fixture5.json.zip', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '' + ]) + + def test_compressed_loading(self): + # Load fixture 5 (compressed), only compression specification + management.call_command('loaddata', 'fixture5.zip', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '' + ]) + + def test_ambiguous_compressed_fixture(self): + # The name "fixture5" is ambigous, so loading it will raise an error + new_io = StringIO.StringIO() + management.call_command('loaddata', 'fixture5', verbosity=0, stderr=new_io, commit=False) + output = new_io.getvalue().strip().split('\n') + self.assertEqual(len(output), 1) + self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture5'")) + + def test_db_loading(self): + # Load db fixtures 1 and 2. These will load using the 'default' database identifier implicitly + management.call_command('loaddata', 'db_fixture_1', verbosity=0, commit=False) + management.call_command('loaddata', 'db_fixture_2', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '' + ]) + + def test_loading_using(self): + # Load db fixtures 1 and 2. These will load using the 'default' database identifier explicitly + management.call_command('loaddata', 'db_fixture_1', verbosity=0, using='default', commit=False) + management.call_command('loaddata', 'db_fixture_2', verbosity=0, using='default', commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '' + ]) + + def test_unmatched_identifier_loading(self): + # Try to load db fixture 3. This won't load because the database identifier doesn't match + management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '' + ]) + + management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '' + ]) + + def test_output_formats(self): + # Load back in fixture 1, we need the articles from it + management.call_command('loaddata', 'fixture1', verbosity=0, commit=False) + + # Try to load fixture 6 using format discovery + management.call_command('loaddata', 'fixture6', verbosity=0, commit=False) + self.assertQuerysetEqual(Tag.objects.all(), [ + ' tagged "copyright">', + ' tagged "law">' + ]) + + # Dump the current contents of the database as a JSON fixture + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]', natural_keys=True) + + # Dump the current contents of the database as an XML fixture + self._dumpdata_assert(['fixtures'], """ +News StoriesLatest news storiesTime to reform copyright2006-06-16 13:00:00Poker has no place on ESPN2006-06-16 12:00:00Python program becomes self aware2006-06-16 11:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtPrinceStephane Grappelli""", format='xml', natural_keys=True) + +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': + class FixtureTransactionTests(TransactionTestCase): + def _dumpdata_assert(self, args, output, format='json'): + new_io = StringIO.StringIO() + management.call_command('dumpdata', *args, format=format, stdout=new_io) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, output) + + def test_format_discovery(self): + # Load fixture 1 again, using format discovery + management.call_command('loaddata', 'fixture1', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '' + ]) + + # Try to load fixture 2 using format discovery; this will fail + # because there are two fixture2's in the fixtures directory + new_io = StringIO.StringIO() + management.call_command('loaddata', 'fixture2', verbosity=0, stderr=new_io) + output = new_io.getvalue().strip().split('\n') + self.assertEqual(len(output), 1) + self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture2'")) + + # object list is unaffected + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '' + ]) + + # Dump the current contents of the database as a JSON fixture + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]') + + # Load fixture 4 (compressed), using format discovery + management.call_command('loaddata', 'fixture4', verbosity=0, commit=False) + self.assertQuerysetEqual(Article.objects.all(), [ + '', + '', + '', + '' + ])