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
941from optparse import make_option
1042from django .core import serializers
1547from django .db .models .fields .related import ManyToManyField
1648from django .db .models .loading import get_models
1749
18- import debug
19-
2050DEBUG = False
2151
2252def 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