Ticket #11838: ticket11838.diff

File ticket11838.diff, 19.4 KB (added by Brandon Konkle, 14 years ago)
Line 
1Index: django/core/management/commands/loaddata.py
2===================================================================
3--- django/core/management/commands/loaddata.py (revision 11795)
4+++ django/core/management/commands/loaddata.py (working copy)
5@@ -%ld,%ld +%ld,%ld @@
6
7 from django.core.management.base import BaseCommand
8 from django.core.management.color import no_style
9+from django.core import serializers
10+from django.db import transaction
11
12 try:
13 set
14@@ -%ld,%ld +%ld,%ld @@
15
16 def handle(self, *fixture_labels, **options):
17 from django.db.models import get_apps
18- from django.core import serializers
19- from django.db import connection, transaction
20+ from django.db import connection
21 from django.conf import settings
22
23 self.style = no_style()
24
25- verbosity = int(options.get('verbosity', 1))
26- show_traceback = options.get('traceback', False)
27+ self.verbosity = int(options.get('verbosity', 1))
28+ self.show_traceback = options.get('traceback', False)
29
30 # commit is a stealth option - it isn't really useful as
31 # a command line option, but it can be useful when invoking
32@@ -%ld,%ld +%ld,%ld @@
33
34 # Keep a count of the installed objects and fixtures
35 fixture_count = 0
36- object_count = 0
37- models = set()
38+ self.object_count = 0
39+ self.models = set()
40
41- humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
42+ self.humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
43
44 # Get a cursor (even though we don't need one yet). This has
45 # the side effect of initializing the test database (if
46@@ -%ld,%ld +%ld,%ld @@
47
48 if len(parts) == 1:
49 fixture_name = parts[0]
50+ fixture_format = None
51 formats = serializers.get_public_serializer_formats()
52 else:
53 fixture_name, format = '.'.join(parts[:-1]), parts[-1]
54 if format in serializers.get_public_serializer_formats():
55+ fixture_format = format
56 formats = [format]
57 else:
58 formats = []
59
60 if formats:
61- if verbosity > 1:
62+ if self.verbosity > 1:
63 print "Loading '%s' fixtures..." % fixture_name
64 else:
65 sys.stderr.write(
66@@ -%ld,%ld +%ld,%ld @@
67 fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + ['']
68
69 for fixture_dir in fixture_dirs:
70- if verbosity > 1:
71- print "Checking %s for fixtures..." % humanize(fixture_dir)
72+ if self.verbosity > 1:
73+ print "Checking %s for fixtures..." % self.humanize(fixture_dir)
74
75 label_found = False
76 for format in formats:
77@@ -%ld,%ld +%ld,%ld @@
78 else:
79 file_name = '.'.join([fixture_name, format])
80
81- if verbosity > 1:
82+ if self.verbosity > 1:
83 print "Trying %s for %s fixture '%s'..." % \
84- (humanize(fixture_dir), file_name, fixture_name)
85+ (self.humanize(fixture_dir), file_name, fixture_name)
86 full_path = os.path.join(fixture_dir, file_name)
87 open_method = compression_types[compression_format]
88 try:
89@@ -%ld,%ld +%ld,%ld @@
90 if label_found:
91 fixture.close()
92 print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." %
93- (fixture_name, humanize(fixture_dir)))
94+ (fixture_name, self.humanize(fixture_dir)))
95 transaction.rollback()
96 transaction.leave_transaction_management()
97 return
98 else:
99 fixture_count += 1
100- objects_in_fixture = 0
101- if verbosity > 0:
102- print "Installing %s fixture '%s' from %s." % \
103- (format, fixture_name, humanize(fixture_dir))
104- try:
105- objects = serializers.deserialize(format, fixture)
106- for obj in objects:
107- objects_in_fixture += 1
108- models.add(obj.object.__class__)
109- obj.save()
110- object_count += objects_in_fixture
111- label_found = True
112- except (SystemExit, KeyboardInterrupt):
113- raise
114- except Exception:
115- import traceback
116- fixture.close()
117- transaction.rollback()
118- transaction.leave_transaction_management()
119- if show_traceback:
120- traceback.print_exc()
121- else:
122- sys.stderr.write(
123- self.style.ERROR("Problem installing fixture '%s': %s\n" %
124- (full_path, ''.join(traceback.format_exception(sys.exc_type,
125- sys.exc_value, sys.exc_traceback)))))
126+ label_found, error = self.install_fixture(format, fixture, fixture_name, fixture_dir, full_path)
127+ if error:
128 return
129- fixture.close()
130-
131- # If the fixture we loaded contains 0 objects, assume that an
132- # error was encountered during fixture loading.
133- if objects_in_fixture == 0:
134- sys.stderr.write(
135- self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" %
136- (fixture_name)))
137- transaction.rollback()
138- transaction.leave_transaction_management()
139- return
140-
141 except Exception, e:
142- if verbosity > 1:
143+ if self.verbosity > 1:
144 print "No %s fixture '%s' in %s." % \
145- (format, fixture_name, humanize(fixture_dir))
146+ (format, fixture_name, self.humanize(fixture_dir))
147
148+ # Check for a group of fixtures with the requested name
149+ if not fixture_format:
150+ group_dir = os.path.join(fixture_dir, fixture_name)
151+ if os.path.isdir(group_dir):
152+ if label_found:
153+ print self.style.ERROR("A fixture named '%s' and a fixture group named '%s' were both found in %s. Aborting." %
154+ (fixture_name, fixture_name, self.humanize(fixture_dir)))
155+ transaction.rollback()
156+ transaction.leave_transaction_management()
157+ return
158+ else:
159+ labels_found = []
160+ if self.verbosity > 1:
161+ print "Checking %s for a group of fixtures." % \
162+ self.humanize(group_dir)
163+ for file_name in os.listdir(group_dir):
164+ parts = file_name.split('.')
165+
166+ if len(parts) > 2 and parts[-1] in compression_types:
167+ compression_format = parts[-1]
168+ parts = parts[:-1]
169+ else:
170+ compression_format = None
171+
172+ if len(parts) == 2:
173+ group_fixture_name, format = '.'.join(parts[:-1]), parts[-1]
174+
175+ if format in serializers.get_public_serializer_formats():
176+ full_path = os.path.join(group_dir, file_name)
177+ if self.verbosity > 1:
178+ print "Trying %s for %s fixture '%s'..." % \
179+ (self.humanize(group_dir), file_name,
180+ fixture_name)
181+ open_method = compression_types[compression_format]
182+ try:
183+ fixture = open_method(full_path, 'r')
184+ if group_fixture_name in labels_found:
185+ fixture.close()
186+ print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." %
187+ (group_fixture_name, self.humanize(group_dir)))
188+ transaction.rollback()
189+ transaction.leave_transaction_management()
190+ return
191+ fixture_count += 1
192+ label_found, error = self.install_fixture(format, fixture, group_fixture_name, group_dir, full_path)
193+ if error:
194+ return
195+ if label_found:
196+ labels_found += [label_found]
197+ except Exception, e:
198+ if self.verbosity > 1:
199+ print "No %s fixture '%s' in %s." % \
200+ (format, group_fixture_name, self.humanize(group_dir))
201+
202 # If we found even one object in a fixture, we need to reset the
203 # database sequences.
204- if object_count > 0:
205- sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
206+ if self.object_count > 0:
207+ sequence_sql = connection.ops.sequence_reset_sql(self.style, self.models)
208 if sequence_sql:
209- if verbosity > 1:
210+ if self.verbosity > 1:
211 print "Resetting sequences"
212 for line in sequence_sql:
213 cursor.execute(line)
214@@ -%ld,%ld +%ld,%ld @@
215 transaction.commit()
216 transaction.leave_transaction_management()
217
218- if object_count == 0:
219- if verbosity > 1:
220+ if self.object_count == 0:
221+ if self.verbosity > 1:
222 print "No fixtures found."
223 else:
224- if verbosity > 0:
225- print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count)
226+ if self.verbosity > 0:
227+ print "Installed %d object(s) from %d fixture(s)" % (self.object_count, fixture_count)
228
229 # Close the DB connection. This is required as a workaround for an
230 # edge case in MySQL: if the same connection is used to
231@@ -%ld,%ld +%ld,%ld @@
232 # incorrect results. See Django #7572, MySQL #37735.
233 if commit:
234 connection.close()
235+
236+ def install_fixture(self, format, fixture, fixture_name, fixture_dir, full_path):
237+ """
238+ Installs the requested fixture.
239+
240+ Returns:
241+ label_found - The name of the label found.
242+ error - Whether there was an error while installing the fixture
243+ """
244+ error = False
245+ label_found = False
246+ objects_in_fixture = 0
247+ if self.verbosity > 0:
248+ print "Installing %s fixture '%s' from %s." % \
249+ (format, fixture_name, self.humanize(fixture_dir))
250+ try:
251+ objects = serializers.deserialize(format, fixture)
252+ for obj in objects:
253+ objects_in_fixture += 1
254+ self.models.add(obj.object.__class__)
255+ obj.save()
256+ self.object_count += objects_in_fixture
257+ label_found = fixture_name
258+ except (SystemExit, KeyboardInterrupt):
259+ raise
260+ except Exception:
261+ import traceback
262+ fixture.close()
263+ transaction.rollback()
264+ transaction.leave_transaction_management()
265+ if self.show_traceback:
266+ traceback.print_exc()
267+ else:
268+ sys.stderr.write(
269+ self.style.ERROR("Problem installing fixture '%s': %s\n" %
270+ (full_path, ''.join(traceback.format_exception(sys.exc_type,
271+ sys.exc_value, sys.exc_traceback)))))
272+ error = True
273+ return label_found, error
274+ fixture.close()
275+
276+ # If the fixture we loaded contains 0 objects, assume that an
277+ # error was encountered during fixture loading.
278+ if objects_in_fixture == 0:
279+ sys.stderr.write(
280+ self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" %
281+ (fixture_name)))
282+ transaction.rollback()
283+ transaction.leave_transaction_management()
284+ error = True
285+ return label_found, error
286+
287+ return label_found, error
288Index: tests/modeltests/fixtures/models.py
289===================================================================
290--- tests/modeltests/fixtures/models.py (revision 11795)
291+++ tests/modeltests/fixtures/models.py (working copy)
292@@ -%ld,%ld +%ld,%ld @@
293 >>> management.call_command('loaddata', 'fixture5', verbosity=0) # doctest: +ELLIPSIS
294 Multiple fixtures named 'fixture5' in '...fixtures'. Aborting.
295
296+# Try to load fixture group 1 using format discovery; this will fail because
297+# there is a fixture and a group both named 'group1' in the fixtures
298+# directory.
299+>>> management.call_command('loaddata', 'group1', verbosity=0) # doctest: +ELLIPSIS
300+A fixture named 'group1' and a fixture group named 'group1' were both found in '...fixtures'. Aborting.
301+
302+# Load the individual fixture group1 with the full filename; this will succeed
303+# because the format was explicitly specified. The group of fixtures
304+# will be ignored.
305+>>> management.call_command('loaddata', 'group1.json', verbosity=0)
306+>>> Article.objects.all()
307+[<Article: Guido awarded Nobel Prize>, <Article: Python program becomes self aware>]
308+
309+>>> management.call_command('flush', verbosity=0, interactive=False)
310+
311+# Try to load group 2; this will fail because there are two fixtures in the
312+# group named 'fixture1'.
313+>>> management.call_command('loaddata', 'group2', verbosity=0) # doctest: +ELLIPSIS
314+Multiple fixtures named 'fixture1' in '.../fixtures/group2'. Aborting.
315+
316+# Load group 3, which will successfully load 2 fixtures.
317+>>> management.call_command('loaddata', 'group3', verbosity=0)
318+>>> Article.objects.all()
319+[<Article: Researchers use Python to produce the Ultimate Question of Life, the Universe, and Everything>, <Article: Python program becomes self aware>]
320+>>> Category.objects.all()
321+[<Category: Catastrophic Disasters>]
322+
323+>>> management.call_command('flush', verbosity=0, interactive=False)
324+
325+# Load group 4, which includes 1 gzipped fixture and 1 plain text fixture.
326+>>> management.call_command('loaddata', 'group4', verbosity=0)
327+>>> Article.objects.all()
328+[<Article: Pinky successfully ponders what Brain is pondering, lab in uproar>, <Article: Python program becomes self aware>]
329+>>> Category.objects.all()
330+[<Category: Hypochondriac Anecdotes>]
331 """
332
333 from django.test import TestCase
334Index: AUTHORS
335===================================================================
336--- AUTHORS (revision 11795)
337+++ AUTHORS (working copy)
338@@ -%ld,%ld +%ld,%ld @@
339 Bruce Kroeze <http://coderseye.com/>
340 krzysiek.pawlik@silvermedia.pl
341 Joseph Kocherhans
342+ Brandon Konkle (bkonkle) <http://brandonkonkle.com/>
343 konrad@gwu.edu
344 knox <christobzr@gmail.com>
345 David Krauth
346Index: docs/howto/initial-data.txt
347===================================================================
348--- docs/howto/initial-data.txt (revision 11795)
349+++ docs/howto/initial-data.txt (working copy)
350@@ -%ld,%ld +%ld,%ld @@
351 but be careful: remember that the data will be refreshed *every time* you run
352 :djadmin:`syncdb`. So don't use ``initial_data`` for data you'll want to edit.
353
354+.. versionadded:: 1.2
355+
356+This also works with fixture groups. If you create a subdirectory called
357+``initial_data`` within a fixtures directory, then all fixture files in
358+that subdirectory will be loaded each time you run :djadmin:`syncdb`.
359+
360 .. seealso::
361
362 Fixtures are also used by the :ref:`testing framework
363Index: docs/ref/django-admin.txt
364===================================================================
365--- docs/ref/django-admin.txt (revision 11795)
366+++ docs/ref/django-admin.txt (working copy)
367@@ -%ld,%ld +%ld,%ld @@
368
369 The ``dumpdata`` command can be used to generate input for ``loaddata``.
370
371+Fixture groups
372+~~~~~~~~~~~~~~
373+
374+.. versionadded:: 1.2
375+
376+Fixtures can also be grouped together into subdirectories under the
377+``fixtures`` directory. The subdirectory can be referred to as a fixture, and
378+:djadmin:`loaddata` will try to load each file in the subdirectory as a fixture
379+file.
380+
381+For example, say you have an app called ``blog`` with two models, ``Entry`` and
382+``Category``. If you want to separate your entries into multiple fixture
383+files based on their category, you could create a subdirectory underneath your
384+``blog`` app's ``fixtures`` directory called ``entries``. Within
385+``blog/fixtures/entries/`` you could create two fixture files, ``django.json``
386+and ``python.json``.
387+
388+In this case, you can refer to the fixture group ``entries`` to install both
389+fixtures at the same time. :djadmin:`loaddata` will first try to find individual
390+fixtures matching the name requested, and if it doesn't find any it will then
391+try to look for a fixture group with that name. The command::
392+
393+ django-admin.py loaddata entries
394+
395+would first search for an individual fixture named ``entries``, and when it
396+didn't find one it would look for a subdirectory with that name under each
397+fixture directory. When it found one, it would attempt to install each file
398+within the subdirectory as a fixture. Both ``django.json`` and ``python.json``
399+would be loaded.
400+
401+If you wanted to install only one of the fixtures in the group, you could refer
402+to it specifically with the command::
403+
404+ django-admin.py loaddata entries/django.json
405+
406+and it would load just the ``django.json`` file.
407+
408 Compressed fixtures
409 ~~~~~~~~~~~~~~~~~~~
410
Back to Top