summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavel Kulikov <kulikovpavel@gmail.com>2017-03-26 22:29:05 +0300
committerTim Graham <timograham@gmail.com>2017-05-26 19:54:21 -0400
commitaf1fa5e7da21c57a4037e67f93493af4e78d454a (patch)
tree459e2342f0431a83f9915dffee09cf89fa54bc16
parentc930c241f8f1ccf3a7848b843628eacdb983d70a (diff)
Fixed #27978 -- Allowed loaddata to read data from stdin.
Thanks Squareweave for the django-loaddata-stdin project from which this is adapted.
-rw-r--r--AUTHORS1
-rw-r--r--django/core/management/commands/loaddata.py17
-rw-r--r--docs/ref/django-admin.txt29
-rw-r--r--docs/releases/2.0.txt2
-rw-r--r--tests/fixtures/tests.py29
5 files changed, 78 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index 30a445b91c..fbf66bacbf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 395d459378..2c19350cc4 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -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:
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 2de0ab6a8b..68fb762623 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:: --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``
----------------
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt
index 186f64319d..de5b38a098 100644
--- a/docs/releases/2.0.txt
+++ b/docs/releases/2.0.txt
@@ -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
~~~~~~~~~~
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index fe047e9838..fb9ba91d3d 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -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):
"""