|
38
|
1 |
import sys |
|
|
2 |
import os |
|
|
3 |
import gzip |
|
|
4 |
import zipfile |
|
|
5 |
from optparse import make_option |
|
|
6 |
|
|
|
7 |
from django.conf import settings |
|
|
8 |
from django.core import serializers |
|
|
9 |
from django.core.management.base import BaseCommand |
|
|
10 |
from django.core.management.color import no_style |
|
|
11 |
from django.db import connections, router, transaction, DEFAULT_DB_ALIAS |
|
|
12 |
from django.db.models import get_apps |
|
|
13 |
from django.utils.itercompat import product |
|
|
14 |
|
|
|
15 |
try: |
|
|
16 |
import bz2 |
|
|
17 |
has_bz2 = True |
|
|
18 |
except ImportError: |
|
|
19 |
has_bz2 = False |
|
|
20 |
|
|
|
21 |
class Command(BaseCommand): |
|
|
22 |
help = 'Installs the named fixture(s) in the database.' |
|
|
23 |
args = "fixture [fixture ...]" |
|
|
24 |
|
|
|
25 |
option_list = BaseCommand.option_list + ( |
|
|
26 |
make_option('--database', action='store', dest='database', |
|
|
27 |
default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load ' |
|
|
28 |
'fixtures into. Defaults to the "default" database.'), |
|
|
29 |
) |
|
|
30 |
|
|
|
31 |
def handle(self, *fixture_labels, **options): |
|
|
32 |
using = options.get('database', DEFAULT_DB_ALIAS) |
|
|
33 |
|
|
|
34 |
connection = connections[using] |
|
|
35 |
self.style = no_style() |
|
|
36 |
|
|
|
37 |
verbosity = int(options.get('verbosity', 1)) |
|
|
38 |
show_traceback = options.get('traceback', False) |
|
|
39 |
|
|
|
40 |
# commit is a stealth option - it isn't really useful as |
|
|
41 |
# a command line option, but it can be useful when invoking |
|
|
42 |
# loaddata from within another script. |
|
|
43 |
# If commit=True, loaddata will use its own transaction; |
|
|
44 |
# if commit=False, the data load SQL will become part of |
|
|
45 |
# the transaction in place when loaddata was invoked. |
|
|
46 |
commit = options.get('commit', True) |
|
|
47 |
|
|
|
48 |
# Keep a count of the installed objects and fixtures |
|
|
49 |
fixture_count = 0 |
|
|
50 |
object_count = 0 |
|
|
51 |
models = set() |
|
|
52 |
|
|
|
53 |
humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' |
|
|
54 |
|
|
|
55 |
# Get a cursor (even though we don't need one yet). This has |
|
|
56 |
# the side effect of initializing the test database (if |
|
|
57 |
# it isn't already initialized). |
|
|
58 |
cursor = connection.cursor() |
|
|
59 |
|
|
|
60 |
# Start transaction management. All fixtures are installed in a |
|
|
61 |
# single transaction to ensure that all references are resolved. |
|
|
62 |
if commit: |
|
|
63 |
transaction.commit_unless_managed(using=using) |
|
|
64 |
transaction.enter_transaction_management(using=using) |
|
|
65 |
transaction.managed(True, using=using) |
|
|
66 |
|
|
|
67 |
class SingleZipReader(zipfile.ZipFile): |
|
|
68 |
def __init__(self, *args, **kwargs): |
|
|
69 |
zipfile.ZipFile.__init__(self, *args, **kwargs) |
|
|
70 |
if settings.DEBUG: |
|
|
71 |
assert len(self.namelist()) == 1, "Zip-compressed fixtures must contain only one file." |
|
|
72 |
def read(self): |
|
|
73 |
return zipfile.ZipFile.read(self, self.namelist()[0]) |
|
|
74 |
|
|
|
75 |
compression_types = { |
|
|
76 |
None: file, |
|
|
77 |
'gz': gzip.GzipFile, |
|
|
78 |
'zip': SingleZipReader |
|
|
79 |
} |
|
|
80 |
if has_bz2: |
|
|
81 |
compression_types['bz2'] = bz2.BZ2File |
|
|
82 |
|
|
|
83 |
app_module_paths = [] |
|
|
84 |
for app in get_apps(): |
|
|
85 |
if hasattr(app, '__path__'): |
|
|
86 |
# It's a 'models/' subpackage |
|
|
87 |
for path in app.__path__: |
|
|
88 |
app_module_paths.append(path) |
|
|
89 |
else: |
|
|
90 |
# It's a models.py module |
|
|
91 |
app_module_paths.append(app.__file__) |
|
|
92 |
|
|
|
93 |
app_fixtures = [os.path.join(os.path.dirname(path), 'fixtures') for path in app_module_paths] |
|
|
94 |
for fixture_label in fixture_labels: |
|
|
95 |
parts = fixture_label.split('.') |
|
|
96 |
|
|
|
97 |
if len(parts) > 1 and parts[-1] in compression_types: |
|
|
98 |
compression_formats = [parts[-1]] |
|
|
99 |
parts = parts[:-1] |
|
|
100 |
else: |
|
|
101 |
compression_formats = compression_types.keys() |
|
|
102 |
|
|
|
103 |
if len(parts) == 1: |
|
|
104 |
fixture_name = parts[0] |
|
|
105 |
formats = serializers.get_public_serializer_formats() |
|
|
106 |
else: |
|
|
107 |
fixture_name, format = '.'.join(parts[:-1]), parts[-1] |
|
|
108 |
if format in serializers.get_public_serializer_formats(): |
|
|
109 |
formats = [format] |
|
|
110 |
else: |
|
|
111 |
formats = [] |
|
|
112 |
|
|
|
113 |
if formats: |
|
|
114 |
if verbosity > 1: |
|
|
115 |
print "Loading '%s' fixtures..." % fixture_name |
|
|
116 |
else: |
|
|
117 |
sys.stderr.write( |
|
|
118 |
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format." % |
|
|
119 |
(fixture_name, format))) |
|
|
120 |
transaction.rollback(using=using) |
|
|
121 |
transaction.leave_transaction_management(using=using) |
|
|
122 |
return |
|
|
123 |
|
|
|
124 |
if os.path.isabs(fixture_name): |
|
|
125 |
fixture_dirs = [fixture_name] |
|
|
126 |
else: |
|
|
127 |
fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + [''] |
|
|
128 |
|
|
|
129 |
for fixture_dir in fixture_dirs: |
|
|
130 |
if verbosity > 1: |
|
|
131 |
print "Checking %s for fixtures..." % humanize(fixture_dir) |
|
|
132 |
|
|
|
133 |
label_found = False |
|
|
134 |
for combo in product([using, None], formats, compression_formats): |
|
|
135 |
database, format, compression_format = combo |
|
|
136 |
file_name = '.'.join( |
|
|
137 |
p for p in [ |
|
|
138 |
fixture_name, database, format, compression_format |
|
|
139 |
] |
|
|
140 |
if p |
|
|
141 |
) |
|
|
142 |
|
|
|
143 |
if verbosity > 1: |
|
|
144 |
print "Trying %s for %s fixture '%s'..." % \ |
|
|
145 |
(humanize(fixture_dir), file_name, fixture_name) |
|
|
146 |
full_path = os.path.join(fixture_dir, file_name) |
|
|
147 |
open_method = compression_types[compression_format] |
|
|
148 |
try: |
|
|
149 |
fixture = open_method(full_path, 'r') |
|
|
150 |
if label_found: |
|
|
151 |
fixture.close() |
|
|
152 |
print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." % |
|
|
153 |
(fixture_name, humanize(fixture_dir))) |
|
|
154 |
transaction.rollback(using=using) |
|
|
155 |
transaction.leave_transaction_management(using=using) |
|
|
156 |
return |
|
|
157 |
else: |
|
|
158 |
fixture_count += 1 |
|
|
159 |
objects_in_fixture = 0 |
|
|
160 |
if verbosity > 0: |
|
|
161 |
print "Installing %s fixture '%s' from %s." % \ |
|
|
162 |
(format, fixture_name, humanize(fixture_dir)) |
|
|
163 |
try: |
|
|
164 |
objects = serializers.deserialize(format, fixture, using=using) |
|
|
165 |
for obj in objects: |
|
|
166 |
if router.allow_syncdb(using, obj.object.__class__): |
|
|
167 |
objects_in_fixture += 1 |
|
|
168 |
models.add(obj.object.__class__) |
|
|
169 |
obj.save(using=using) |
|
|
170 |
object_count += objects_in_fixture |
|
|
171 |
label_found = True |
|
|
172 |
except (SystemExit, KeyboardInterrupt): |
|
|
173 |
raise |
|
|
174 |
except Exception: |
|
|
175 |
import traceback |
|
|
176 |
fixture.close() |
|
|
177 |
transaction.rollback(using=using) |
|
|
178 |
transaction.leave_transaction_management(using=using) |
|
|
179 |
if show_traceback: |
|
|
180 |
traceback.print_exc() |
|
|
181 |
else: |
|
|
182 |
sys.stderr.write( |
|
|
183 |
self.style.ERROR("Problem installing fixture '%s': %s\n" % |
|
|
184 |
(full_path, ''.join(traceback.format_exception(sys.exc_type, |
|
|
185 |
sys.exc_value, sys.exc_traceback))))) |
|
|
186 |
return |
|
|
187 |
fixture.close() |
|
|
188 |
|
|
|
189 |
# If the fixture we loaded contains 0 objects, assume that an |
|
|
190 |
# error was encountered during fixture loading. |
|
|
191 |
if objects_in_fixture == 0: |
|
|
192 |
sys.stderr.write( |
|
|
193 |
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" % |
|
|
194 |
(fixture_name))) |
|
|
195 |
transaction.rollback(using=using) |
|
|
196 |
transaction.leave_transaction_management(using=using) |
|
|
197 |
return |
|
|
198 |
|
|
|
199 |
except Exception, e: |
|
|
200 |
if verbosity > 1: |
|
|
201 |
print "No %s fixture '%s' in %s." % \ |
|
|
202 |
(format, fixture_name, humanize(fixture_dir)) |
|
|
203 |
|
|
|
204 |
# If we found even one object in a fixture, we need to reset the |
|
|
205 |
# database sequences. |
|
|
206 |
if object_count > 0: |
|
|
207 |
sequence_sql = connection.ops.sequence_reset_sql(self.style, models) |
|
|
208 |
if sequence_sql: |
|
|
209 |
if verbosity > 1: |
|
|
210 |
print "Resetting sequences" |
|
|
211 |
for line in sequence_sql: |
|
|
212 |
cursor.execute(line) |
|
|
213 |
|
|
|
214 |
if commit: |
|
|
215 |
transaction.commit(using=using) |
|
|
216 |
transaction.leave_transaction_management(using=using) |
|
|
217 |
|
|
|
218 |
if object_count == 0: |
|
|
219 |
if verbosity > 0: |
|
|
220 |
print "No fixtures found." |
|
|
221 |
else: |
|
|
222 |
if verbosity > 0: |
|
|
223 |
print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count) |
|
|
224 |
|
|
|
225 |
# Close the DB connection. This is required as a workaround for an |
|
|
226 |
# edge case in MySQL: if the same connection is used to |
|
|
227 |
# create tables, load data, and query, the query can return |
|
|
228 |
# incorrect results. See Django #7572, MySQL #37735. |
|
|
229 |
if commit: |
|
|
230 |
connection.close() |