1 | diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
|
---|
2 | index 9966849..876d84d 100644
|
---|
3 | --- a/django/contrib/auth/management/__init__.py
|
---|
4 | +++ b/django/contrib/auth/management/__init__.py
|
---|
5 | @@ -46,16 +46,14 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
---|
6 | "content_type", "codename"
|
---|
7 | ))
|
---|
8 |
|
---|
9 | - for ctype, (codename, name) in searched_perms:
|
---|
10 | - # If the permissions exists, move on.
|
---|
11 | - if (ctype.pk, codename) in all_perms:
|
---|
12 | - continue
|
---|
13 | - p = auth_app.Permission.objects.create(
|
---|
14 | - codename=codename,
|
---|
15 | - name=name,
|
---|
16 | - content_type=ctype
|
---|
17 | - )
|
---|
18 | - if verbosity >= 2:
|
---|
19 | + objs = [
|
---|
20 | + auth_app.Permission(codename=codename, name=name, content_type=ctype)
|
---|
21 | + for ctype, (codename, name) in searched_perms
|
---|
22 | + if (ctype.pk, codename) not in all_perms
|
---|
23 | + ]
|
---|
24 | + auth_app.Permission.objects.bulk_create(objs)
|
---|
25 | + if verbosity >= 2:
|
---|
26 | + for obj in objs:
|
---|
27 | print "Adding permission '%s'" % p
|
---|
28 |
|
---|
29 |
|
---|
30 | diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
|
---|
31 | index b64fb01..8ed6d6b 100644
|
---|
32 | --- a/django/db/backends/__init__.py
|
---|
33 | +++ b/django/db/backends/__init__.py
|
---|
34 | @@ -272,8 +272,10 @@ class BaseDatabaseFeatures(object):
|
---|
35 |
|
---|
36 | can_use_chunked_reads = True
|
---|
37 | can_return_id_from_insert = False
|
---|
38 | + has_bulk_insert = False
|
---|
39 | uses_autocommit = False
|
---|
40 | uses_savepoints = False
|
---|
41 | + can_combine_inserts_with_and_without_auto_increment_pk = False
|
---|
42 |
|
---|
43 | # If True, don't use integer foreign keys referring to, e.g., positive
|
---|
44 | # integer primary keys.
|
---|
45 | diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
|
---|
46 | index 67e2877..ad355b8 100644
|
---|
47 | --- a/django/db/backends/postgresql_psycopg2/base.py
|
---|
48 | +++ b/django/db/backends/postgresql_psycopg2/base.py
|
---|
49 | @@ -72,6 +72,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
---|
50 | can_defer_constraint_checks = True
|
---|
51 | has_select_for_update = True
|
---|
52 | has_select_for_update_nowait = True
|
---|
53 | + has_bulk_insert = True
|
---|
54 |
|
---|
55 |
|
---|
56 | class DatabaseWrapper(BaseDatabaseWrapper):
|
---|
57 | diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
|
---|
58 | index 3315913..7a9c406 100644
|
---|
59 | --- a/django/db/backends/postgresql_psycopg2/operations.py
|
---|
60 | +++ b/django/db/backends/postgresql_psycopg2/operations.py
|
---|
61 | @@ -208,3 +208,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
---|
62 |
|
---|
63 | def return_insert_id(self):
|
---|
64 | return "RETURNING %s", ()
|
---|
65 | +
|
---|
66 | + def bulk_insert_sql(self, fields, num_values):
|
---|
67 | + items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
---|
68 | + return "VALUES " + ", ".join([items_sql] * num_values)
|
---|
69 | \ No newline at end of file
|
---|
70 | diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
|
---|
71 | index 5b4a1c2..ea65865 100644
|
---|
72 | --- a/django/db/backends/sqlite3/base.py
|
---|
73 | +++ b/django/db/backends/sqlite3/base.py
|
---|
74 | @@ -57,6 +57,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
---|
75 | supports_unspecified_pk = True
|
---|
76 | supports_1000_query_parameters = False
|
---|
77 | supports_mixed_date_datetime_comparisons = False
|
---|
78 | + has_bulk_insert = True
|
---|
79 | + can_combine_inserts_with_and_without_auto_increment_pk = True
|
---|
80 |
|
---|
81 | def _supports_stddev(self):
|
---|
82 | """Confirm support for STDDEV and related stats functions
|
---|
83 | @@ -105,7 +107,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
---|
84 | return ""
|
---|
85 |
|
---|
86 | def pk_default_value(self):
|
---|
87 | - return 'NULL'
|
---|
88 | + return "NULL"
|
---|
89 |
|
---|
90 | def quote_name(self, name):
|
---|
91 | if name.startswith('"') and name.endswith('"'):
|
---|
92 | @@ -153,6 +155,14 @@ class DatabaseOperations(BaseDatabaseOperations):
|
---|
93 | # No field, or the field isn't known to be a decimal or integer
|
---|
94 | return value
|
---|
95 |
|
---|
96 | + def bulk_insert_sql(self, fields, num_values):
|
---|
97 | + res = []
|
---|
98 | + res.append("SELECT %s" % ", ".join(
|
---|
99 | + "%%s AS %s" % self.quote_name(f.column) for f in fields
|
---|
100 | + ))
|
---|
101 | + res.extend(["UNION SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
|
---|
102 | + return " ".join(res)
|
---|
103 | +
|
---|
104 | class DatabaseWrapper(BaseDatabaseWrapper):
|
---|
105 | vendor = 'sqlite'
|
---|
106 | # SQLite requires LIKE statements to include an ESCAPE clause if the value
|
---|
107 | diff --git a/django/db/models/base.py b/django/db/models/base.py
|
---|
108 | index 31310ea..f71f13e 100644
|
---|
109 | --- a/django/db/models/base.py
|
---|
110 | +++ b/django/db/models/base.py
|
---|
111 | @@ -539,24 +539,16 @@ class Model(object):
|
---|
112 | order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
|
---|
113 | self._order = order_value
|
---|
114 |
|
---|
115 | + fields = meta.local_fields
|
---|
116 | if not pk_set:
|
---|
117 | if force_update:
|
---|
118 | raise ValueError("Cannot force an update in save() with no primary key.")
|
---|
119 | - values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
|
---|
120 | - for f in meta.local_fields if not isinstance(f, AutoField)]
|
---|
121 | - else:
|
---|
122 | - values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
|
---|
123 | - for f in meta.local_fields]
|
---|
124 | + fields = [f for f in fields if not isinstance(f, AutoField)]
|
---|
125 |
|
---|
126 | record_exists = False
|
---|
127 |
|
---|
128 | update_pk = bool(meta.has_auto_field and not pk_set)
|
---|
129 | - if values:
|
---|
130 | - # Create a new record.
|
---|
131 | - result = manager._insert(values, return_id=update_pk, using=using)
|
---|
132 | - else:
|
---|
133 | - # Create a new record with defaults for everything.
|
---|
134 | - result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True, using=using)
|
---|
135 | + result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
|
---|
136 |
|
---|
137 | if update_pk:
|
---|
138 | setattr(self, meta.pk.attname, result)
|
---|
139 | diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
|
---|
140 | index cedf308..4d6a8a7 100644
|
---|
141 | --- a/django/db/models/fields/related.py
|
---|
142 | +++ b/django/db/models/fields/related.py
|
---|
143 | @@ -432,7 +432,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
---|
144 | add.alters_data = True
|
---|
145 |
|
---|
146 | def create(self, **kwargs):
|
---|
147 | - kwargs.update({rel_field.name: instance})
|
---|
148 | + kwargs[rel_field.name] = instance
|
---|
149 | db = router.db_for_write(rel_model, instance=instance)
|
---|
150 | return super(RelatedManager, self.db_manager(db)).create(**kwargs)
|
---|
151 | create.alters_data = True
|
---|
152 | @@ -440,7 +440,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
---|
153 | def get_or_create(self, **kwargs):
|
---|
154 | # Update kwargs with the related object that this
|
---|
155 | # ForeignRelatedObjectsDescriptor knows about.
|
---|
156 | - kwargs.update({rel_field.name: instance})
|
---|
157 | + kwargs[rel_field.name] = instance
|
---|
158 | db = router.db_for_write(rel_model, instance=instance)
|
---|
159 | return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
---|
160 | get_or_create.alters_data = True
|
---|
161 | @@ -580,11 +580,13 @@ def create_many_related_manager(superclass, rel=False):
|
---|
162 | instance=self.instance, reverse=self.reverse,
|
---|
163 | model=self.model, pk_set=new_ids, using=db)
|
---|
164 | # Add the ones that aren't there already
|
---|
165 | - for obj_id in new_ids:
|
---|
166 | - self.through._default_manager.using(db).create(**{
|
---|
167 | + self.through._default_manager.using(db).bulk_create([
|
---|
168 | + self.through(**{
|
---|
169 | '%s_id' % source_field_name: self._pk_val,
|
---|
170 | '%s_id' % target_field_name: obj_id,
|
---|
171 | })
|
---|
172 | + for obj_id in new_ids
|
---|
173 | + ])
|
---|
174 | if self.reverse or source_field_name == self.source_field_name:
|
---|
175 | # Don't send the signal when we are inserting the
|
---|
176 | # duplicate data row for symmetrical reverse entries.
|
---|
177 | @@ -703,12 +705,12 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
---|
178 | def __init__(self, m2m_field):
|
---|
179 | self.field = m2m_field
|
---|
180 |
|
---|
181 | - def _through(self):
|
---|
182 | + @property
|
---|
183 | + def through(self):
|
---|
184 | # through is provided so that you have easy access to the through
|
---|
185 | # model (Book.authors.through) for inlines, etc. This is done as
|
---|
186 | # a property to ensure that the fully resolved value is returned.
|
---|
187 | return self.field.rel.through
|
---|
188 | - through = property(_through)
|
---|
189 |
|
---|
190 | def __get__(self, instance, instance_type=None):
|
---|
191 | if instance is None:
|
---|
192 | diff --git a/django/db/models/manager.py b/django/db/models/manager.py
|
---|
193 | index 4fa4c4a..92090e0 100644
|
---|
194 | --- a/django/db/models/manager.py
|
---|
195 | +++ b/django/db/models/manager.py
|
---|
196 | @@ -137,6 +137,9 @@ class Manager(object):
|
---|
197 | def create(self, **kwargs):
|
---|
198 | return self.get_query_set().create(**kwargs)
|
---|
199 |
|
---|
200 | + def bulk_create(self, *args, **kwargs):
|
---|
201 | + return self.get_query_set().bulk_create(*args, **kwargs)
|
---|
202 | +
|
---|
203 | def filter(self, *args, **kwargs):
|
---|
204 | return self.get_query_set().filter(*args, **kwargs)
|
---|
205 |
|
---|
206 | @@ -194,8 +197,8 @@ class Manager(object):
|
---|
207 | def exists(self, *args, **kwargs):
|
---|
208 | return self.get_query_set().exists(*args, **kwargs)
|
---|
209 |
|
---|
210 | - def _insert(self, values, **kwargs):
|
---|
211 | - return insert_query(self.model, values, **kwargs)
|
---|
212 | + def _insert(self, objs, fields, **kwargs):
|
---|
213 | + return insert_query(self.model, objs, fields, **kwargs)
|
---|
214 |
|
---|
215 | def _update(self, values, **kwargs):
|
---|
216 | return self.get_query_set()._update(values, **kwargs)
|
---|
217 | diff --git a/django/db/models/query.py b/django/db/models/query.py
|
---|
218 | index 6a6a829..86060cd 100644
|
---|
219 | --- a/django/db/models/query.py
|
---|
220 | +++ b/django/db/models/query.py
|
---|
221 | @@ -7,7 +7,7 @@ from itertools import izip
|
---|
222 |
|
---|
223 | from django.db import connections, router, transaction, IntegrityError
|
---|
224 | from django.db.models.aggregates import Aggregate
|
---|
225 | -from django.db.models.fields import DateField
|
---|
226 | +from django.db.models.fields import DateField, AutoField
|
---|
227 | from django.db.models.query_utils import (Q, select_related_descend,
|
---|
228 | deferred_class_factory, InvalidQuery)
|
---|
229 | from django.db.models.deletion import Collector
|
---|
230 | @@ -360,6 +360,39 @@ class QuerySet(object):
|
---|
231 | obj.save(force_insert=True, using=self.db)
|
---|
232 | return obj
|
---|
233 |
|
---|
234 | + def bulk_create(self, objs):
|
---|
235 | + """
|
---|
236 | + Inserts each of the instances into the database. This does *not* call
|
---|
237 | + save() on each of the instances, does not send any pre/post save
|
---|
238 | + signals, and does not set the primary key attribute if it is an
|
---|
239 | + autoincrement field.
|
---|
240 | + """
|
---|
241 | + # So this case is fun. When you bulk insert you don't get the primary
|
---|
242 | + # keys back (if it's an autoincrement), so you can't insert into the
|
---|
243 | + # child tables which references this. There are two workarounds, 1)
|
---|
244 | + # this could be implemented if you didn't have an autoincrement pk,
|
---|
245 | + # and 2) you could do it by doing O(n) normal inserts into the parent
|
---|
246 | + # tables to get the primary keys back, and then doing a single bulk
|
---|
247 | + # insert into the childmost table. We're punting on these for now
|
---|
248 | + # because they are relatively rare cases.
|
---|
249 | + if self.model._meta.parents:
|
---|
250 | + raise ValueError("Can't bulk create an inherited model")
|
---|
251 | + if not objs:
|
---|
252 | + return
|
---|
253 | + self._for_write = True
|
---|
254 | + connection = connections[self.db]
|
---|
255 | + fields = self.model._meta.local_fields
|
---|
256 | + if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
|
---|
257 | + and self.model._meta.has_auto_field):
|
---|
258 | + self.model._base_manager._insert(objs, fields=fields, using=self.db)
|
---|
259 | + else:
|
---|
260 | + objs_with_pk = [o for o in objs if o.pk]
|
---|
261 | + objs_without_pk = [o for o in objs if not o.pk]
|
---|
262 | + if objs_with_pk:
|
---|
263 | + self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db)
|
---|
264 | + if objs_without_pk:
|
---|
265 | + self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db)
|
---|
266 | +
|
---|
267 | def get_or_create(self, **kwargs):
|
---|
268 | """
|
---|
269 | Looks up an object with the given kwargs, creating one if necessary.
|
---|
270 | @@ -1445,12 +1478,12 @@ class RawQuerySet(object):
|
---|
271 | self._model_fields[converter(column)] = field
|
---|
272 | return self._model_fields
|
---|
273 |
|
---|
274 | -def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
---|
275 | +def insert_query(model, objs, fields, return_id=False, raw=False, using=None):
|
---|
276 | """
|
---|
277 | Inserts a new record for the given model. This provides an interface to
|
---|
278 | the InsertQuery class and is how Model.save() is implemented. It is not
|
---|
279 | part of the public API.
|
---|
280 | """
|
---|
281 | query = sql.InsertQuery(model)
|
---|
282 | - query.insert_values(values, raw_values)
|
---|
283 | + query.insert_values(fields, objs, raw=raw)
|
---|
284 | return query.get_compiler(using=using).execute_sql(return_id)
|
---|
285 | diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
|
---|
286 | index 841ec12..84dd7ed 100644
|
---|
287 | --- a/django/db/models/sql/compiler.py
|
---|
288 | +++ b/django/db/models/sql/compiler.py
|
---|
289 | @@ -1,3 +1,5 @@
|
---|
290 | +from itertools import izip
|
---|
291 | +
|
---|
292 | from django.core.exceptions import FieldError
|
---|
293 | from django.db import connections
|
---|
294 | from django.db import transaction
|
---|
295 | @@ -9,6 +11,7 @@ from django.db.models.sql.query import get_proxied_model, get_order_dir, \
|
---|
296 | select_related_descend, Query
|
---|
297 | from django.db.utils import DatabaseError
|
---|
298 |
|
---|
299 | +
|
---|
300 | class SQLCompiler(object):
|
---|
301 | def __init__(self, query, connection, using):
|
---|
302 | self.query = query
|
---|
303 | @@ -794,20 +797,55 @@ class SQLInsertCompiler(SQLCompiler):
|
---|
304 | qn = self.connection.ops.quote_name
|
---|
305 | opts = self.query.model._meta
|
---|
306 | result = ['INSERT INTO %s' % qn(opts.db_table)]
|
---|
307 | - result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
|
---|
308 | - values = [self.placeholder(*v) for v in self.query.values]
|
---|
309 | - result.append('VALUES (%s)' % ', '.join(values))
|
---|
310 | - params = self.query.params
|
---|
311 | +
|
---|
312 | + has_fields = bool(self.query.fields)
|
---|
313 | + fields = self.query.fields if has_fields else [opts.pk]
|
---|
314 | + result.append('(%s)' % ', '.join([qn(f.column) for f in fields]))
|
---|
315 | +
|
---|
316 | + if has_fields:
|
---|
317 | + params = values = [
|
---|
318 | + [
|
---|
319 | + f.get_db_prep_save(getattr(obj, f.attname) if self.query.raw else f.pre_save(obj, True), connection=self.connection)
|
---|
320 | + for f in fields
|
---|
321 | + ]
|
---|
322 | + for obj in self.query.objs
|
---|
323 | + ]
|
---|
324 | + else:
|
---|
325 | + values = [[self.connection.ops.pk_default_value()] for obj in self.query.objs]
|
---|
326 | + params = [[]]
|
---|
327 | + fields = [None]
|
---|
328 | + can_bulk = not any(hasattr(field, "get_placeholder") for field in fields) and not self.return_id
|
---|
329 | +
|
---|
330 | + if can_bulk:
|
---|
331 | + placeholders = [["%s"] * len(fields)]
|
---|
332 | + else:
|
---|
333 | + placeholders = [
|
---|
334 | + [self.placeholder(field, v) for field, v in izip(fields, val)]
|
---|
335 | + for val in values
|
---|
336 | + ]
|
---|
337 | if self.return_id and self.connection.features.can_return_id_from_insert:
|
---|
338 | - col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
|
---|
339 | + params = values[0]
|
---|
340 | + col = "%s.%s" % (qn(opst.db_table), qn(opts.pk.column))
|
---|
341 | + result.append("VALUES (%s)" % ", ".join(placeholders[0]))
|
---|
342 | r_fmt, r_params = self.connection.ops.return_insert_id()
|
---|
343 | result.append(r_fmt % col)
|
---|
344 | - params = params + r_params
|
---|
345 | - return ' '.join(result), params
|
---|
346 | + params += r_params
|
---|
347 | + return [(" ".join(result), tuple(param))]
|
---|
348 | + if can_bulk and self.connection.features.has_bulk_insert:
|
---|
349 | + result.append(self.connection.ops.bulk_insert_sql(fields, len(values)))
|
---|
350 | + return [(" ".join(result), tuple([v for val in values for v in val]))]
|
---|
351 | + else:
|
---|
352 | + return [
|
---|
353 | + (" ".join(result + ["VALUES (%s)" % ", ".join(p)]), vals)
|
---|
354 | + for p, vals in izip(placeholders, params)
|
---|
355 | + ]
|
---|
356 |
|
---|
357 | def execute_sql(self, return_id=False):
|
---|
358 | + assert not (return_id and len(self.query.objs) != 1)
|
---|
359 | self.return_id = return_id
|
---|
360 | - cursor = super(SQLInsertCompiler, self).execute_sql(None)
|
---|
361 | + cursor = self.connection.cursor()
|
---|
362 | + for sql, params in self.as_sql():
|
---|
363 | + cursor.execute(sql, params)
|
---|
364 | if not (return_id and cursor):
|
---|
365 | return
|
---|
366 | if self.connection.features.can_return_id_from_insert:
|
---|
367 | diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
|
---|
368 | index 99663b6..12b13a7 100644
|
---|
369 | --- a/django/db/models/sql/query.py
|
---|
370 | +++ b/django/db/models/sql/query.py
|
---|
371 | @@ -8,9 +8,10 @@ all about the internals of models in order to get the information it needs.
|
---|
372 | """
|
---|
373 |
|
---|
374 | import copy
|
---|
375 | -from django.utils.tree import Node
|
---|
376 | +
|
---|
377 | from django.utils.datastructures import SortedDict
|
---|
378 | from django.utils.encoding import force_unicode
|
---|
379 | +from django.utils.tree import Node
|
---|
380 | from django.db import connections, DEFAULT_DB_ALIAS
|
---|
381 | from django.db.models import signals
|
---|
382 | from django.db.models.fields import FieldDoesNotExist
|
---|
383 | diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
|
---|
384 | index 003bf43..39cfc03 100644
|
---|
385 | --- a/django/db/models/sql/subqueries.py
|
---|
386 | +++ b/django/db/models/sql/subqueries.py
|
---|
387 | @@ -138,20 +138,19 @@ class InsertQuery(Query):
|
---|
388 |
|
---|
389 | def __init__(self, *args, **kwargs):
|
---|
390 | super(InsertQuery, self).__init__(*args, **kwargs)
|
---|
391 | - self.columns = []
|
---|
392 | - self.values = []
|
---|
393 | - self.params = ()
|
---|
394 | + self.fields = []
|
---|
395 | + self.objs = []
|
---|
396 |
|
---|
397 | def clone(self, klass=None, **kwargs):
|
---|
398 | extras = {
|
---|
399 | - 'columns': self.columns[:],
|
---|
400 | - 'values': self.values[:],
|
---|
401 | - 'params': self.params
|
---|
402 | + 'fields': self.fields[:],
|
---|
403 | + 'objs': self.objs[:],
|
---|
404 | + 'raw': self.raw,
|
---|
405 | }
|
---|
406 | extras.update(kwargs)
|
---|
407 | return super(InsertQuery, self).clone(klass, **extras)
|
---|
408 |
|
---|
409 | - def insert_values(self, insert_values, raw_values=False):
|
---|
410 | + def insert_values(self, fields, objs, raw=False):
|
---|
411 | """
|
---|
412 | Set up the insert query from the 'insert_values' dictionary. The
|
---|
413 | dictionary gives the model field names and their target values.
|
---|
414 | @@ -161,16 +160,9 @@ class InsertQuery(Query):
|
---|
415 | parameters. This provides a way to insert NULL and DEFAULT keywords
|
---|
416 | into the query, for example.
|
---|
417 | """
|
---|
418 | - placeholders, values = [], []
|
---|
419 | - for field, val in insert_values:
|
---|
420 | - placeholders.append((field, val))
|
---|
421 | - self.columns.append(field.column)
|
---|
422 | - values.append(val)
|
---|
423 | - if raw_values:
|
---|
424 | - self.values.extend([(None, v) for v in values])
|
---|
425 | - else:
|
---|
426 | - self.params += tuple(values)
|
---|
427 | - self.values.extend(placeholders)
|
---|
428 | + self.fields = fields
|
---|
429 | + self.objs = objs
|
---|
430 | + self.raw = raw
|
---|
431 |
|
---|
432 | class DateQuery(Query):
|
---|
433 | """
|
---|
434 | diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
|
---|
435 | index 2bd813d..b7a84cd 100644
|
---|
436 | --- a/docs/ref/models/querysets.txt
|
---|
437 | +++ b/docs/ref/models/querysets.txt
|
---|
438 | @@ -139,7 +139,7 @@ Though you usually won't create one manually -- you'll go through a
|
---|
439 | clause or a default ordering on the model. ``False`` otherwise.
|
---|
440 |
|
---|
441 | .. attribute:: db
|
---|
442 | -
|
---|
443 | +
|
---|
444 | The database that will be used if this query is executed now.
|
---|
445 |
|
---|
446 | .. note::
|
---|
447 | @@ -1139,6 +1139,29 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
|
---|
448 |
|
---|
449 | .. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
---|
450 |
|
---|
451 | +bulk_create
|
---|
452 | +~~~~~~~~~~~
|
---|
453 | +
|
---|
454 | +.. method:: bulk_create(objs)
|
---|
455 | +
|
---|
456 | +This method inserts the provided list of objects into the database in an
|
---|
457 | +efficient manner (generally only 1 query, no matter how many objects there
|
---|
458 | +are)::
|
---|
459 | +
|
---|
460 | + >>> Entry.objects.bulk_create([
|
---|
461 | + ... Entry(headline="Django 1.0 Released"),
|
---|
462 | + ... Entry(headline="Django 1.1 Announced"),
|
---|
463 | + ... Entry(headline="Breaking: Django is awesome")
|
---|
464 | + ... ])
|
---|
465 | +
|
---|
466 | +This has a number of caveats though:
|
---|
467 | +
|
---|
468 | + * The model's ``save()`` method will not be called, and the ``pre_save`` and
|
---|
469 | + ``post_save`` signals will not be sent.
|
---|
470 | + * It does not work with child models in a multi-table inheritance scenario.
|
---|
471 | + * If the model's primary key is an :class:`AutoField` it does not retrieve
|
---|
472 | + and set the primary key attribute, as ``save()`` does.
|
---|
473 | +
|
---|
474 | count
|
---|
475 | ~~~~~
|
---|
476 |
|
---|
477 | diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt
|
---|
478 | index 265ef55..24830aa 100644
|
---|
479 | --- a/docs/topics/db/optimization.txt
|
---|
480 | +++ b/docs/topics/db/optimization.txt
|
---|
481 | @@ -268,3 +268,29 @@ instead of::
|
---|
482 |
|
---|
483 | entry.blog.id
|
---|
484 |
|
---|
485 | +Insert in bulk
|
---|
486 | +==============
|
---|
487 | +
|
---|
488 | +When creating objects, where possible, use the :meth:`QuerySet.bulk_create()`
|
---|
489 | +method to reduce the number of SQL queries. For example::
|
---|
490 | +
|
---|
491 | + Entry.objects.bulk_create([
|
---|
492 | + Entry(headline="Python 3.0 Released"),
|
---|
493 | + Entry(headline="Python 3.1 Planned")
|
---|
494 | + ])
|
---|
495 | +
|
---|
496 | +Is preferable to::
|
---|
497 | +
|
---|
498 | + Entry.objects.create(headline="Python 3.0 Released")
|
---|
499 | + Entry.objects.create(headline="Python 3.1 Planned")
|
---|
500 | +
|
---|
501 | +This also applies to :class:`ManyToManyFields`, doing::
|
---|
502 | +
|
---|
503 | + my_band.members.add(me, my_friend)
|
---|
504 | +
|
---|
505 | +Is preferable to::
|
---|
506 | +
|
---|
507 | + my_band.members.add(me)
|
---|
508 | + my_band.members.add(my_friend)
|
---|
509 | +
|
---|
510 | +Where ``Bands`` and ``Artists`` have a many-to-many relationship.
|
---|
511 | \ No newline at end of file
|
---|
512 | diff --git a/tests/regressiontests/bulk_create/models.py b/tests/regressiontests/bulk_create/models.py
|
---|
513 | index 1f98a40..a4c611d 100644
|
---|
514 | --- a/tests/regressiontests/bulk_create/models.py
|
---|
515 | +++ b/tests/regressiontests/bulk_create/models.py
|
---|
516 | @@ -3,4 +3,19 @@ from django.db import models
|
---|
517 |
|
---|
518 | class Country(models.Model):
|
---|
519 | name = models.CharField(max_length=255)
|
---|
520 | - iso_two_letter = models.CharField(max_length=2)
|
---|
521 | \ No newline at end of file
|
---|
522 | + iso_two_letter = models.CharField(max_length=2)
|
---|
523 | +
|
---|
524 | +class Place(models.Model):
|
---|
525 | + name = models.CharField(max_length=100)
|
---|
526 | +
|
---|
527 | + class Meta:
|
---|
528 | + abstract = True
|
---|
529 | +
|
---|
530 | +class Restaurant(Place):
|
---|
531 | + pass
|
---|
532 | +
|
---|
533 | +class Pizzeria(Restaurant):
|
---|
534 | + pass
|
---|
535 | +
|
---|
536 | +class State(models.Model):
|
---|
537 | + two_letter_code = models.CharField(max_length=2, primary_key=True)
|
---|
538 | \ No newline at end of file
|
---|
539 | diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py
|
---|
540 | index 42ba095..020841c 100644
|
---|
541 | --- a/tests/regressiontests/bulk_create/tests.py
|
---|
542 | +++ b/tests/regressiontests/bulk_create/tests.py
|
---|
543 | @@ -1,8 +1,10 @@
|
---|
544 | +from __future__ import with_statement
|
---|
545 | +
|
---|
546 | from operator import attrgetter
|
---|
547 |
|
---|
548 | from django.test import TestCase, skipUnlessDBFeature
|
---|
549 |
|
---|
550 | -from models import Country
|
---|
551 | +from models import Country, Restaurant, Pizzeria, State
|
---|
552 |
|
---|
553 |
|
---|
554 | class BulkCreateTests(TestCase):
|
---|
555 | @@ -23,4 +25,29 @@ class BulkCreateTests(TestCase):
|
---|
556 | @skipUnlessDBFeature("has_bulk_insert")
|
---|
557 | def test_efficiency(self):
|
---|
558 | with self.assertNumQueries(1):
|
---|
559 | - Country.objects.bulk_create(self.data)
|
---|
560 | \ No newline at end of file
|
---|
561 | + Country.objects.bulk_create(self.data)
|
---|
562 | +
|
---|
563 | + def test_inheritance(self):
|
---|
564 | + Restaurant.objects.bulk_create([
|
---|
565 | + Restaurant(name="Nicholas's")
|
---|
566 | + ])
|
---|
567 | + self.assertQuerysetEqual(Restaurant.objects.all(), [
|
---|
568 | + "Nicholas's",
|
---|
569 | + ], attrgetter("name"))
|
---|
570 | + with self.assertRaises(ValueError):
|
---|
571 | + Pizzeria.objects.bulk_create([
|
---|
572 | + Pizzeria(name="The Art of Pizza")
|
---|
573 | + ])
|
---|
574 | + self.assertQuerysetEqual(Pizzeria.objects.all(), [])
|
---|
575 | + self.assertQuerysetEqual(Restaurant.objects.all(), [
|
---|
576 | + "Nicholas's",
|
---|
577 | + ], attrgetter("name"))
|
---|
578 | +
|
---|
579 | + def test_non_auto_increment_pk(self):
|
---|
580 | + State.objects.bulk_create([
|
---|
581 | + State(two_letter_code=s)
|
---|
582 | + for s in ["IL", "NY", "CA", "ME"]
|
---|
583 | + ])
|
---|
584 | + self.assertQuerysetEqual(State.objects.order_by("two_letter_code"), [
|
---|
585 | + "CA", "IL", "ME", "NY",
|
---|
586 | + ], attrgetter("two_letter_code"))
|
---|
587 | \ No newline at end of file
|
---|
588 | diff --git a/tests/regressiontests/db_typecasts/tests.py b/tests/regressiontests/db_typecasts/tests.py
|
---|
589 | index 8c71c8f..1d3bbfa 100644
|
---|
590 | --- a/tests/regressiontests/db_typecasts/tests.py
|
---|
591 | +++ b/tests/regressiontests/db_typecasts/tests.py
|
---|
592 | @@ -53,10 +53,10 @@ TEST_CASES = {
|
---|
593 |
|
---|
594 | class DBTypeCasts(unittest.TestCase):
|
---|
595 | def test_typeCasts(self):
|
---|
596 | - for k, v in TEST_CASES.items():
|
---|
597 | + for k, v in TEST_CASES.iteritems():
|
---|
598 | for inpt, expected in v:
|
---|
599 | got = getattr(typecasts, k)(inpt)
|
---|
600 | - assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
|
---|
601 | + self.assertEqual(got, expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got))
|
---|
602 |
|
---|
603 | if __name__ == '__main__':
|
---|
604 | unittest.main()
|
---|