Skip to content

Commit b335ef3

Browse files
committed
Imported a newer version of makefixture from github. It handles reverse relationships in a less hackish way than my hack.
- Legacy-Id: 6314
1 parent 4b568f8 commit b335ef3

1 file changed

Lines changed: 90 additions & 40 deletions

File tree

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,42 @@
1-
# From http://djangosnippets.org/snippets/918/
1+
# From https://github.com/ericholscher/django-test-utils/blob/master/test_utils/management/commands/makefixture.py
2+
"""
3+
"Make fixture" command.
4+
5+
Highly useful for making test fixtures. Use it to pick only few items
6+
from your data to serialize, restricted by primary keys. By default
7+
command also serializes foreign keys and m2m relations. You can turn
8+
off related items serialization with --skip-related option.
9+
10+
How to use:
11+
python manage.py makefixture
12+
13+
will display what models are installed
14+
15+
python manage.py makefixture User[:3]
16+
or
17+
python manage.py makefixture auth.User[:3]
18+
or
19+
python manage.py makefixture django.contrib.auth.User[:3]
20+
21+
will serialize users with ids 1 and 2, with assigned groups, permissions
22+
and content types.
23+
24+
python manage.py makefixture YourModel[3] YourModel[6:10]
25+
26+
will serialize YourModel with key 3 and keys 6 to 9 inclusively.
27+
28+
Of course, you can serialize whole tables, and also different tables at
29+
once, and use options of dumpdata:
30+
31+
python manage.py makefixture --format=xml --indent=4 YourModel[3] AnotherModel auth.User[:5] auth.Group
32+
"""
33+
# From http://www.djangosnippets.org/snippets/918/
234

335
#save into anyapp/management/commands/makefixture.py
436
#or back into django/core/management/commands/makefixture.py
537
#v0.1 -- current version
638
#known issues:
7-
#no support for generic relations
39+
#no support for generic relations
840
#no support for one-to-one relations
941
from optparse import make_option
1042
from django.core import serializers
@@ -15,8 +47,6 @@
1547
from django.db.models.fields.related import ManyToManyField
1648
from django.db.models.loading import get_models
1749

18-
import debug
19-
2050
DEBUG = False
2151

2252
def model_name(m):
@@ -29,20 +59,36 @@ class Command(LabelCommand):
2959
option_list = BaseCommand.option_list + (
3060
make_option('--skip-related', default=True, action='store_false', dest='propagate',
3161
help='Specifies if we shall not add related objects.'),
62+
make_option('--reverse', default=[], action='append', dest='reverse',
63+
help="Reverse relations to follow (e.g. 'Job.task_set')."),
3264
make_option('--format', default='json', dest='format',
3365
help='Specifies the output serialization format for fixtures.'),
3466
make_option('--indent', default=None, dest='indent', type='int',
3567
help='Specifies the indent level to use when pretty-printing output'),
36-
make_option('--include-reverse', default=False, action='store_true', dest='reverse',
37-
help='Add reverse related objects too'),
3868
)
39-
69+
def handle_reverse(self, **options):
70+
follow_reverse = options.get('reverse', [])
71+
to_reverse = {}
72+
for arg in follow_reverse:
73+
try:
74+
model_name, related_set_name = arg.rsplit(".", 1)
75+
except:
76+
raise CommandError("Bad fieldname on '--reverse %s'" % arg)
77+
model = self.get_model_from_name(model_name)
78+
try:
79+
getattr(model, related_set_name)
80+
except AttributeError:
81+
raise CommandError("Field '%s' does not exist on model '%s'" % (
82+
related_set_name, model_name))
83+
to_reverse.setdefault(model, []).append(related_set_name)
84+
return to_reverse
85+
4086
def handle_models(self, models, **options):
4187
format = options.get('format','json')
4288
indent = options.get('indent',None)
4389
show_traceback = options.get('traceback', False)
4490
propagate = options.get('propagate', True)
45-
opt_reverse = options.get('reverse', False)
91+
follow_reverse = self.handle_reverse(**options)
4692

4793
# Check that the serialization format exists; this is a shortcut to
4894
# avoid collating all the objects and _then_ failing.
@@ -56,7 +102,7 @@ def handle_models(self, models, **options):
56102

57103
objects = []
58104
for model, slice in models:
59-
if isinstance(slice, basestring):
105+
if isinstance(slice, basestring) and slice:
60106
objects.extend(model._default_manager.filter(pk__exact=slice))
61107
elif not slice or type(slice) is list:
62108
items = model._default_manager.all()
@@ -68,32 +114,16 @@ def handle_models(self, models, **options):
68114
objects.extend(items)
69115
else:
70116
raise CommandError("Wrong slice: %s" % slice)
71-
117+
72118
all = objects
73-
collected = set([(x.__class__, x.pk) for x in all])
74-
75-
if opt_reverse:
76-
related = []
77-
for x in objects:
78-
attribs = []
79-
for name in dir(x):
80-
try:
81-
attribs.append(getattr(x, name))
82-
except AttributeError:
83-
pass
84-
for o in attribs:
85-
if "django.db.models.fields.related.RelatedManager object" in repr(o):
86-
for new in o.all():
87-
collected.add((new.__class__, new.pk))
88-
related.append(new)
89-
all.extend(related)
90-
91119
if propagate:
120+
collected = set([(x.__class__, x.pk) for x in all])
92121
while objects:
93122
related = []
94123
for x in objects:
95124
if DEBUG:
96125
print "Adding %s[%s]" % (model_name(x), x.pk)
126+
# follow forward relation fields
97127
for f in x.__class__._meta.fields + x.__class__._meta.many_to_many:
98128
if isinstance(f, ForeignKey):
99129
new = getattr(x, f.name) # instantiate object
@@ -105,9 +135,16 @@ def handle_models(self, models, **options):
105135
if new and not (new.__class__, new.pk) in collected:
106136
collected.add((new.__class__, new.pk))
107137
related.append(new)
138+
# follow reverse relations as requested
139+
for reverse_field in follow_reverse.get(x.__class__, []):
140+
mgr = getattr(x, reverse_field)
141+
for new in mgr.all():
142+
if new and not (new.__class__, new.pk) in collected:
143+
collected.add((new.__class__, new.pk))
144+
related.append(new)
108145
objects = related
109-
all.extend(related)
110-
146+
all.extend(objects)
147+
111148
try:
112149
return serializers.serialize(format, all, indent=indent)
113150
except Exception, e:
@@ -118,24 +155,37 @@ def handle_models(self, models, **options):
118155
def get_models(self):
119156
return [(m, model_name(m)) for m in get_models()]
120157

158+
def get_model_from_name(self, search):
159+
"""Given a name of a model, return the model object associated with it
160+
161+
The name can be either fully specified or uniquely matching the
162+
end of the model name. e.g.
163+
django.contrib.auth.User
164+
or
165+
auth.User
166+
raises CommandError if model can't be found or uniquely determined
167+
"""
168+
models = [model for model, name in self.get_models()
169+
if name.endswith('.'+name) or name == search]
170+
if not models:
171+
raise CommandError("Unknown model: %s" % search)
172+
if len(models)>1:
173+
raise CommandError("Ambiguous model name: %s" % search)
174+
return models[0]
175+
121176
def handle_label(self, labels, **options):
122177
parsed = []
123178
for label in labels:
124179
search, pks = label, ''
125180
if '[' in label:
126181
search, pks = label.split('[', 1)
127182
slice = ''
128-
if ':' in pks:
183+
if ':' in pks:
129184
slice = pks.rstrip(']').split(':', 1)
130-
elif pks:
185+
elif pks:
131186
slice = pks.rstrip(']')
132-
models = [model for model, name in self.get_models()
133-
if name.endswith('.'+search) or name == search]
134-
if not models:
135-
raise CommandError("Wrong model: %s" % search)
136-
if len(models)>1:
137-
raise CommandError("Ambiguous model name: %s" % search)
138-
parsed.append((models[0], slice))
187+
model = self.get_model_from_name(search)
188+
parsed.append((model, slice))
139189
return self.handle_models(parsed, **options)
140190

141191
def list_models(self):
@@ -149,5 +199,5 @@ def handle(self, *labels, **options):
149199
output = []
150200
label_output = self.handle_label(labels, **options)
151201
if label_output:
152-
output.append(label_output)
202+
output.append(label_output)
153203
return '\n'.join(output)

0 commit comments

Comments
 (0)