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:
parent
c930c241f8
commit
af1fa5e7da
1
AUTHORS
1
AUTHORS
|
@ -617,6 +617,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Paulo Poiati <paulogpoiati@gmail.com>
|
Paulo Poiati <paulogpoiati@gmail.com>
|
||||||
Paulo Scardine <paulo@scardine.com.br>
|
Paulo Scardine <paulo@scardine.com.br>
|
||||||
Paul Smith <blinkylights23@gmail.com>
|
Paul Smith <blinkylights23@gmail.com>
|
||||||
|
Pavel Kulikov <kulikovpavel@gmail.com>
|
||||||
pavithran s <pavithran.s@gmail.com>
|
pavithran s <pavithran.s@gmail.com>
|
||||||
Pavlo Kapyshin <i@93z.org>
|
Pavlo Kapyshin <i@93z.org>
|
||||||
permonik@mesias.brnonet.cz
|
permonik@mesias.brnonet.cz
|
||||||
|
|
|
@ -2,6 +2,7 @@ import functools
|
||||||
import glob
|
import glob
|
||||||
import gzip
|
import gzip
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
import zipfile
|
import zipfile
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
@ -25,6 +26,8 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
has_bz2 = False
|
has_bz2 = False
|
||||||
|
|
||||||
|
READ_STDIN = '-'
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Installs the named fixture(s) in the database.'
|
help = 'Installs the named fixture(s) in the database.'
|
||||||
|
@ -52,6 +55,10 @@ class Command(BaseCommand):
|
||||||
'-e', '--exclude', dest='exclude', action='append', default=[],
|
'-e', '--exclude', dest='exclude', action='append', default=[],
|
||||||
help='An app_label or app_label.ModelName to exclude. Can be used multiple times.',
|
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):
|
def handle(self, *fixture_labels, **options):
|
||||||
self.ignore = options['ignore']
|
self.ignore = options['ignore']
|
||||||
|
@ -59,6 +66,7 @@ class Command(BaseCommand):
|
||||||
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'])
|
self.excluded_models, self.excluded_apps = parse_apps_and_model_labels(options['exclude'])
|
||||||
|
self.format = options['format']
|
||||||
|
|
||||||
with transaction.atomic(using=self.using):
|
with transaction.atomic(using=self.using):
|
||||||
self.loaddata(fixture_labels)
|
self.loaddata(fixture_labels)
|
||||||
|
@ -85,6 +93,7 @@ class Command(BaseCommand):
|
||||||
None: (open, 'rb'),
|
None: (open, 'rb'),
|
||||||
'gz': (gzip.GzipFile, 'rb'),
|
'gz': (gzip.GzipFile, 'rb'),
|
||||||
'zip': (SingleZipReader, 'r'),
|
'zip': (SingleZipReader, 'r'),
|
||||||
|
'stdin': (lambda *args: sys.stdin, None),
|
||||||
}
|
}
|
||||||
if has_bz2:
|
if has_bz2:
|
||||||
self.compression_formats['bz2'] = (bz2.BZ2File, 'r')
|
self.compression_formats['bz2'] = (bz2.BZ2File, 'r')
|
||||||
|
@ -201,6 +210,9 @@ class Command(BaseCommand):
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
def find_fixtures(self, fixture_label):
|
def find_fixtures(self, fixture_label):
|
||||||
"""Find fixture files for a given 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)
|
fixture_name, ser_fmt, cmp_fmt = self.parse_name(fixture_label)
|
||||||
databases = [self.using, None]
|
databases = [self.using, None]
|
||||||
cmp_fmts = list(self.compression_formats.keys()) if cmp_fmt is None else [cmp_fmt]
|
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.
|
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)
|
parts = fixture_name.rsplit('.', 2)
|
||||||
|
|
||||||
if len(parts) > 1 and parts[-1] in self.compression_formats:
|
if len(parts) > 1 and parts[-1] in self.compression_formats:
|
||||||
|
|
|
@ -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:: --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
|
.. django-admin-option:: --exclude EXCLUDE, -e EXCLUDE
|
||||||
|
|
||||||
.. versionadded:: 1.11
|
.. 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
|
``mydata.master.json.gz`` and the fixture will only be loaded when you
|
||||||
specify you want to load data into the ``master`` database.
|
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``
|
``makemessages``
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,8 @@ Management Commands
|
||||||
* The new :option:`makemessages --add-location` option controls the comment
|
* The new :option:`makemessages --add-location` option controls the comment
|
||||||
format in PO files.
|
format in PO files.
|
||||||
|
|
||||||
|
* :djadmin:`loaddata` can now :ref:`read from stdin <loading-fixtures-stdin>`.
|
||||||
|
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -680,6 +680,35 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
|
||||||
with self.assertRaisesMessage(management.CommandError, msg):
|
with self.assertRaisesMessage(management.CommandError, msg):
|
||||||
management.call_command('loaddata', 'fixture1', exclude=['fixtures.FooModel'], verbosity=0)
|
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):
|
class NonexistentFixtureTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue