Fixed #27978 -- Allowed loaddata to read data from stdin.

Thanks Squareweave for the django-loaddata-stdin project from which this
is adapted.
This commit is contained in:
Pavel Kulikov 2017-03-26 22:29:05 +03:00 committed by Tim Graham
parent c930c241f8
commit af1fa5e7da
5 changed files with 78 additions and 0 deletions

View File

@ -617,6 +617,7 @@ answer newbie questions, and generally made Django that much better:
Paulo Poiati <paulogpoiati@gmail.com>
Paulo Scardine <paulo@scardine.com.br>
Paul Smith <blinkylights23@gmail.com>
Pavel Kulikov <kulikovpavel@gmail.com>
pavithran s <pavithran.s@gmail.com>
Pavlo Kapyshin <i@93z.org>
permonik@mesias.brnonet.cz

View File

@ -2,6 +2,7 @@ import functools
import glob
import gzip
import os
import sys
import warnings
import zipfile
from itertools import product
@ -25,6 +26,8 @@ try:
except ImportError:
has_bz2 = False
READ_STDIN = '-'
class Command(BaseCommand):
help = 'Installs the named fixture(s) in the database.'
@ -52,6 +55,10 @@ class Command(BaseCommand):
'-e', '--exclude', dest='exclude', action='append', default=[],
help='An app_label or app_label.ModelName to exclude. Can be used multiple times.',
)
parser.add_argument(
'--format', action='store', dest='format', default=None,
help='Format of serialized data when reading from stdin.',
)
def handle(self, *fixture_labels, **options):
self.ignore = options['ignore']
@ -59,6 +66,7 @@ class Command(BaseCommand):
self.app_label = options['app_label']
self.verbosity = options['verbosity']
self.excluded_models, self.excluded_apps = parse_apps_and_model_labels(options['exclude'])
self.format = options['format']
with transaction.atomic(using=self.using):
self.loaddata(fixture_labels)
@ -85,6 +93,7 @@ class Command(BaseCommand):
None: (open, 'rb'),
'gz': (gzip.GzipFile, 'rb'),
'zip': (SingleZipReader, 'r'),
'stdin': (lambda *args: sys.stdin, None),
}
if has_bz2:
self.compression_formats['bz2'] = (bz2.BZ2File, 'r')
@ -201,6 +210,9 @@ class Command(BaseCommand):
@functools.lru_cache(maxsize=None)
def find_fixtures(self, fixture_label):
"""Find fixture files for a given label."""
if fixture_label == READ_STDIN:
return [(READ_STDIN, None, READ_STDIN)]
fixture_name, ser_fmt, cmp_fmt = self.parse_name(fixture_label)
databases = [self.using, None]
cmp_fmts = list(self.compression_formats.keys()) if cmp_fmt is None else [cmp_fmt]
@ -288,6 +300,11 @@ class Command(BaseCommand):
"""
Split fixture name in name, serialization format, compression format.
"""
if fixture_name == READ_STDIN:
if not self.format:
raise CommandError('--format must be specified when reading from stdin.')
return READ_STDIN, self.format, 'stdin'
parts = fixture_name.rsplit('.', 2)
if len(parts) > 1 and parts[-1] in self.compression_formats:

View File

@ -416,6 +416,14 @@ originally generated.
Specifies a single app to look for fixtures in rather than looking in all apps.
.. django-admin-option:: --format FORMAT
.. versionadded:: 2.0
Specifies the :ref:`serialization format <serialization-formats>` (e.g.,
``json`` or ``xml``) for fixtures :ref:`read from stdin
<loading-fixtures-stdin>`.
.. django-admin-option:: --exclude EXCLUDE, -e EXCLUDE
.. versionadded:: 1.11
@ -552,6 +560,27 @@ defined, name the fixture ``mydata.master.json`` or
``mydata.master.json.gz`` and the fixture will only be loaded when you
specify you want to load data into the ``master`` database.
.. _loading-fixtures-stdin:
Loading fixtures from ``stdin``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2.0
You can use a dash as the fixture name to load input from ``sys.stdin``. For
example::
django-admin loaddata --format=json -
When reading from ``stdin``, the :option:`--format <loaddata --format>` option
is required to specify the :ref:`serialization format <serialization-formats>`
of the input (e.g., ``json`` or ``xml``).
Loading from ``stdin`` is useful with standard input and output redirections.
For example::
django-admin dumpdata --format=json --database=test app_label.ModelName | django-admin loaddata --format=json --database=prod -
``makemessages``
----------------

View File

@ -185,6 +185,8 @@ Management Commands
* The new :option:`makemessages --add-location` option controls the comment
format in PO files.
* :djadmin:`loaddata` can now :ref:`read from stdin <loading-fixtures-stdin>`.
Migrations
~~~~~~~~~~

View File

@ -680,6 +680,35 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
with self.assertRaisesMessage(management.CommandError, msg):
management.call_command('loaddata', 'fixture1', exclude=['fixtures.FooModel'], verbosity=0)
def test_stdin_without_format(self):
"""Reading from stdin raises an error if format isn't specified."""
msg = '--format must be specified when reading from stdin.'
with self.assertRaisesMessage(management.CommandError, msg):
management.call_command('loaddata', '-', verbosity=0)
def test_loading_stdin(self):
"""Loading fixtures from stdin with json and xml."""
tests_dir = os.path.dirname(__file__)
fixture_json = os.path.join(tests_dir, 'fixtures', 'fixture1.json')
fixture_xml = os.path.join(tests_dir, 'fixtures', 'fixture3.xml')
with mock.patch('django.core.management.commands.loaddata.sys.stdin', open(fixture_json, 'r')):
management.call_command('loaddata', '--format=json', '-', verbosity=0)
self.assertEqual(Article.objects.count(), 2)
self.assertQuerysetEqual(Article.objects.all(), [
'<Article: Time to reform copyright>',
'<Article: Poker has no place on ESPN>',
])
with mock.patch('django.core.management.commands.loaddata.sys.stdin', open(fixture_xml, 'r')):
management.call_command('loaddata', '--format=xml', '-', verbosity=0)
self.assertEqual(Article.objects.count(), 3)
self.assertQuerysetEqual(Article.objects.all(), [
'<Article: XML identified as leading cause of cancer>',
'<Article: Time to reform copyright>',
'<Article: Poker on TV is great!>',
])
class NonexistentFixtureTests(TestCase):
"""