Skip to content

Commit 0e8f639

Browse files
committed
Added two new management commands to make it easier to load back selected objects that have been removed by mistake (providing they are available in a full database dump or backup that can be loaded and worked with): dumprelated and loadrelated.
- Legacy-Id: 15790
1 parent d2b86bc commit 0e8f639

3 files changed

Lines changed: 346 additions & 174 deletions

File tree

ietf/utils/management/commands/delete_data_lacking_consent.py

Lines changed: 0 additions & 174 deletions
This file was deleted.
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import warnings
2+
from collections import OrderedDict
3+
4+
from django.apps import apps
5+
from django.contrib.admin.utils import NestedObjects
6+
from django.core import serializers
7+
from django.core.management.base import BaseCommand, CommandError
8+
from django.core.management.utils import parse_apps_and_model_labels
9+
from django.db import DEFAULT_DB_ALIAS, router
10+
11+
import debug # pyflakes:ignore
12+
debug.debug = True
13+
14+
class ProxyModelWarning(Warning):
15+
pass
16+
17+
18+
class Command(BaseCommand):
19+
help = (
20+
"Output a database object and its related objects as a fixture of the given format "
21+
)
22+
23+
def add_arguments(self, parser):
24+
parser.add_argument(
25+
'args', metavar='app_label.ModelName', nargs=1,
26+
help='Specifies the app_label.ModelName for which to dump objects given by --pks',
27+
)
28+
parser.add_argument(
29+
'--format', default='json', dest='format',
30+
help='Specifies the output serialization format for fixtures.',
31+
)
32+
parser.add_argument(
33+
'--indent', default=None, dest='indent', type=int,
34+
help='Specifies the indent level to use when pretty-printing output.',
35+
)
36+
parser.add_argument(
37+
'--database', action='store', dest='database',
38+
default=DEFAULT_DB_ALIAS,
39+
help='Nominates a specific database to dump fixtures from. '
40+
'Defaults to the "default" database.',
41+
)
42+
parser.add_argument(
43+
'-e', '--exclude', dest='exclude', action='append', default=[],
44+
help='An app_label or app_label.ModelName to exclude '
45+
'(use multiple --exclude to exclude multiple apps/models).',
46+
)
47+
parser.add_argument(
48+
'--natural-foreign', action='store_true', dest='use_natural_foreign_keys', default=False,
49+
help='Use natural foreign keys if they are available.',
50+
)
51+
parser.add_argument(
52+
'--natural-primary', action='store_true', dest='use_natural_primary_keys', default=False,
53+
help='Use natural primary keys if they are available.',
54+
)
55+
parser.add_argument(
56+
'-o', '--output', default=None, dest='output',
57+
help='Specifies file to which the output is written.'
58+
)
59+
parser.add_argument(
60+
'--pks', dest='primary_keys', required=True,
61+
help="Only dump objects with given primary keys. Accepts a comma-separated "
62+
"list of keys. This option only works when you specify one model.",
63+
)
64+
65+
def handle(self, *app_labels, **options):
66+
format = options['format']
67+
indent = options['indent']
68+
using = options['database']
69+
excludes = options['exclude']
70+
output = options['output']
71+
show_traceback = options['traceback']
72+
use_natural_foreign_keys = options['use_natural_foreign_keys']
73+
use_natural_primary_keys = options['use_natural_primary_keys']
74+
pks = options['primary_keys']
75+
76+
if pks:
77+
primary_keys = [pk.strip() for pk in pks.split(',')]
78+
else:
79+
primary_keys = []
80+
81+
excluded_models, excluded_apps = parse_apps_and_model_labels(excludes)
82+
83+
if len(app_labels) == 0:
84+
if primary_keys:
85+
raise CommandError("You can only use --pks option with one model")
86+
app_list = OrderedDict(
87+
(app_config, None) for app_config in apps.get_app_configs()
88+
if app_config.models_module is not None and app_config not in excluded_apps
89+
)
90+
else:
91+
if len(app_labels) > 1 and primary_keys:
92+
raise CommandError("You can only use --pks option with one model")
93+
app_list = OrderedDict()
94+
for label in app_labels:
95+
try:
96+
app_label, model_label = label.split('.')
97+
try:
98+
app_config = apps.get_app_config(app_label)
99+
except LookupError as e:
100+
raise CommandError(str(e))
101+
if app_config.models_module is None or app_config in excluded_apps:
102+
continue
103+
try:
104+
model = app_config.get_model(model_label)
105+
except LookupError:
106+
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
107+
108+
app_list_value = app_list.setdefault(app_config, [])
109+
110+
# We may have previously seen a "all-models" request for
111+
# this app (no model qualifier was given). In this case
112+
# there is no need adding specific models to the list.
113+
if app_list_value is not None:
114+
if model not in app_list_value:
115+
app_list_value.append(model)
116+
except ValueError:
117+
if primary_keys:
118+
raise CommandError("You can only use --pks option with one model")
119+
# This is just an app - no model qualifier
120+
app_label = label
121+
try:
122+
app_config = apps.get_app_config(app_label)
123+
except LookupError as e:
124+
raise CommandError(str(e))
125+
if app_config.models_module is None or app_config in excluded_apps:
126+
continue
127+
app_list[app_config] = None
128+
129+
# Check that the serialization format exists; this is a shortcut to
130+
# avoid collating all the objects and _then_ failing.
131+
if format not in serializers.get_public_serializer_formats():
132+
try:
133+
serializers.get_serializer(format)
134+
except serializers.SerializerDoesNotExist:
135+
pass
136+
137+
raise CommandError("Unknown serialization format: %s" % format)
138+
139+
def flatten(l):
140+
if isinstance(l, list):
141+
for el in l:
142+
if isinstance(el, list):
143+
for sub in flatten(el):
144+
yield sub
145+
else:
146+
yield el
147+
else:
148+
yield l
149+
150+
def get_objects(count_only=False):
151+
"""
152+
Collate the objects to be serialized. If count_only is True, just
153+
count the number of objects to be serialized.
154+
"""
155+
models = serializers.sort_dependencies(app_list.items())
156+
for model in models:
157+
if model in excluded_models:
158+
continue
159+
if model._meta.proxy and model._meta.proxy_for_model not in models:
160+
warnings.warn(
161+
"%s is a proxy model and won't be serialized." % model._meta.label,
162+
category=ProxyModelWarning,
163+
)
164+
if not model._meta.proxy and router.allow_migrate_model(using, model):
165+
objects = model._default_manager
166+
167+
queryset = objects.using(using).order_by(model._meta.pk.name)
168+
if primary_keys:
169+
queryset = queryset.filter(pk__in=primary_keys)
170+
if count_only:
171+
yield queryset.order_by().count()
172+
else:
173+
for obj in queryset.iterator():
174+
collector = NestedObjects(using=using)
175+
collector.collect([obj,])
176+
object_list = list(flatten(collector.nested()))
177+
object_list.reverse()
178+
for o in object_list:
179+
yield o
180+
181+
try:
182+
self.stdout.ending = None
183+
progress_output = None
184+
object_count = 0
185+
# If dumpdata is outputting to stdout, there is no way to display progress
186+
if (output and self.stdout.isatty() and options['verbosity'] > 0):
187+
progress_output = self.stdout
188+
object_count = sum(get_objects(count_only=True))
189+
stream = open(output, 'w') if output else None
190+
try:
191+
serializers.serialize(
192+
format, get_objects(), indent=indent,
193+
use_natural_foreign_keys=use_natural_foreign_keys,
194+
use_natural_primary_keys=use_natural_primary_keys,
195+
stream=stream or self.stdout, progress_output=progress_output,
196+
object_count=object_count,
197+
)
198+
finally:
199+
if stream:
200+
stream.close()
201+
except Exception as e:
202+
if show_traceback:
203+
raise
204+
raise CommandError("Unable to serialize database: %s" % e)

0 commit comments

Comments
 (0)