Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7596. Added Model.objects.bulk_create, and make use of it in s…

…everal places. This provides a performance benefit when inserting multiple objects. THanks to Russ for the review, and Simon Meers for the MySQl implementation.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16739 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 7deb25b8dd5aa1ed02b5e30cbc67cd1fb0c3d6e6 1 parent e55bbf4
Alex Gaynor authored September 09, 2011
20  django/contrib/auth/management/__init__.py
@@ -46,17 +46,15 @@ def create_permissions(app, created_models, verbosity, **kwargs):
46 46
         "content_type", "codename"
47 47
     ))
48 48
 
49  
-    for ctype, (codename, name) in searched_perms:
50  
-        # If the permissions exists, move on.
51  
-        if (ctype.pk, codename) in all_perms:
52  
-            continue
53  
-        p = auth_app.Permission.objects.create(
54  
-            codename=codename,
55  
-            name=name,
56  
-            content_type=ctype
57  
-        )
58  
-        if verbosity >= 2:
59  
-            print "Adding permission '%s'" % p
  49
+    objs = [
  50
+        auth_app.Permission(codename=codename, name=name, content_type=ctype)
  51
+        for ctype, (codename, name) in searched_perms
  52
+        if (ctype.pk, codename) not in all_perms
  53
+    ]
  54
+    auth_app.Permission.objects.bulk_create(objs)
  55
+    if verbosity >= 2:
  56
+        for obj in objs:
  57
+            print "Adding permission '%s'" % obj
60 58
 
61 59
 
62 60
 def create_superuser(app, created_models, verbosity, **kwargs):
50  django/contrib/contenttypes/management.py
@@ -8,25 +8,41 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
8 8
     entries that no longer have a matching model class.
9 9
     """
10 10
     ContentType.objects.clear_cache()
11  
-    content_types = list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2]))
12 11
     app_models = get_models(app)
13 12
     if not app_models:
14 13
         return
15  
-    for klass in app_models:
16  
-        opts = klass._meta
17  
-        try:
18  
-            ct = ContentType.objects.get(app_label=opts.app_label,
19  
-                                         model=opts.object_name.lower())
20  
-            content_types.remove(ct)
21  
-        except ContentType.DoesNotExist:
22  
-            ct = ContentType(name=smart_unicode(opts.verbose_name_raw),
23  
-                app_label=opts.app_label, model=opts.object_name.lower())
24  
-            ct.save()
25  
-            if verbosity >= 2:
26  
-                print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
27  
-    # The presence of any remaining content types means the supplied app has an
28  
-    # undefined model. Confirm that the content type is stale before deletion.
29  
-    if content_types:
  14
+    # They all have the same app_label, get the first one.
  15
+    app_label = app_models[0]._meta.app_label
  16
+    app_models = dict(
  17
+        (model._meta.object_name.lower(), model)
  18
+        for model in app_models
  19
+    )
  20
+    # Get all the content types
  21
+    content_types = dict(
  22
+        (ct.model, ct)
  23
+        for ct in ContentType.objects.filter(app_label=app_label)
  24
+    )
  25
+    to_remove = [
  26
+        ct
  27
+        for (model_name, ct) in content_types.iteritems()
  28
+        if model_name not in app_models
  29
+    ]
  30
+
  31
+    cts = ContentType.objects.bulk_create([
  32
+        ContentType(
  33
+            name=smart_unicode(model._meta.verbose_name_raw),
  34
+            app_label=app_label,
  35
+            model=model_name,
  36
+        )
  37
+        for (model_name, model) in app_models.iteritems()
  38
+        if model_name not in content_types
  39
+    ])
  40
+    if verbosity >= 2:
  41
+        for ct in cts:
  42
+            print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
  43
+
  44
+    # Confirm that the content type is stale before deletion.
  45
+    if to_remove:
30 46
         if kwargs.get('interactive', False):
31 47
             content_type_display = '\n'.join(['    %s | %s' % (ct.app_label, ct.model) for ct in content_types])
32 48
             ok_to_delete = raw_input("""The following content types are stale and need to be deleted:
@@ -42,7 +58,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
42 58
             ok_to_delete = False
43 59
 
44 60
         if ok_to_delete == 'yes':
45  
-            for ct in content_types:
  61
+            for ct in to_remove:
46 62
                 if verbosity >= 2:
47 63
                     print "Deleting stale content type '%s | %s'" % (ct.app_label, ct.model)
48 64
                 ct.delete()
2  django/db/backends/__init__.py
@@ -301,8 +301,10 @@ class BaseDatabaseFeatures(object):
301 301
 
302 302
     can_use_chunked_reads = True
303 303
     can_return_id_from_insert = False
  304
+    has_bulk_insert = False
304 305
     uses_autocommit = False
305 306
     uses_savepoints = False
  307
+    can_combine_inserts_with_and_without_auto_increment_pk = False
306 308
 
307 309
     # If True, don't use integer foreign keys referring to, e.g., positive
308 310
     # integer primary keys.
5  django/db/backends/mysql/base.py
@@ -124,6 +124,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
124 124
     allows_group_by_pk = True
125 125
     related_fields_match_type = True
126 126
     allow_sliced_subqueries = False
  127
+    has_bulk_insert = True
127 128
     has_select_for_update = True
128 129
     has_select_for_update_nowait = False
129 130
     supports_forward_references = False
@@ -263,6 +264,10 @@ def year_lookup_bounds(self, value):
263 264
     def max_name_length(self):
264 265
         return 64
265 266
 
  267
+    def bulk_insert_sql(self, fields, num_values):
  268
+        items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
  269
+        return "VALUES " + ", ".join([items_sql] * num_values)
  270
+
266 271
 class DatabaseWrapper(BaseDatabaseWrapper):
267 272
     vendor = 'mysql'
268 273
     operators = {
1  django/db/backends/postgresql_psycopg2/base.py
@@ -74,6 +74,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
74 74
     can_defer_constraint_checks = True
75 75
     has_select_for_update = True
76 76
     has_select_for_update_nowait = True
  77
+    has_bulk_insert = True
77 78
 
78 79
 
79 80
 class DatabaseWrapper(BaseDatabaseWrapper):
4  django/db/backends/postgresql_psycopg2/operations.py
@@ -180,3 +180,7 @@ def last_executed_query(self, cursor, sql, params):
180 180
 
181 181
     def return_insert_id(self):
182 182
         return "RETURNING %s", ()
  183
+
  184
+    def bulk_insert_sql(self, fields, num_values):
  185
+        items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
  186
+        return "VALUES " + ", ".join([items_sql] * num_values)
12  django/db/backends/sqlite3/base.py
@@ -58,6 +58,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
58 58
     supports_unspecified_pk = True
59 59
     supports_1000_query_parameters = False
60 60
     supports_mixed_date_datetime_comparisons = False
  61
+    has_bulk_insert = True
  62
+    can_combine_inserts_with_and_without_auto_increment_pk = True
61 63
 
62 64
     def _supports_stddev(self):
63 65
         """Confirm support for STDDEV and related stats functions
@@ -106,7 +108,7 @@ def drop_foreignkey_sql(self):
106 108
         return ""
107 109
 
108 110
     def pk_default_value(self):
109  
-        return 'NULL'
  111
+        return "NULL"
110 112
 
111 113
     def quote_name(self, name):
112 114
         if name.startswith('"') and name.endswith('"'):
@@ -154,6 +156,14 @@ def convert_values(self, value, field):
154 156
         # No field, or the field isn't known to be a decimal or integer
155 157
         return value
156 158
 
  159
+    def bulk_insert_sql(self, fields, num_values):
  160
+        res = []
  161
+        res.append("SELECT %s" % ", ".join(
  162
+            "%%s AS %s" % self.quote_name(f.column) for f in fields
  163
+        ))
  164
+        res.extend(["UNION SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
  165
+        return " ".join(res)
  166
+
157 167
 class DatabaseWrapper(BaseDatabaseWrapper):
158 168
     vendor = 'sqlite'
159 169
     # SQLite requires LIKE statements to include an ESCAPE clause if the value
14  django/db/models/base.py
@@ -540,24 +540,16 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
540 540
                     order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
541 541
                     self._order = order_value
542 542
 
  543
+                fields = meta.local_fields
543 544
                 if not pk_set:
544 545
                     if force_update:
545 546
                         raise ValueError("Cannot force an update in save() with no primary key.")
546  
-                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
547  
-                        for f in meta.local_fields if not isinstance(f, AutoField)]
548  
-                else:
549  
-                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
550  
-                        for f in meta.local_fields]
  547
+                    fields = [f for f in fields if not isinstance(f, AutoField)]
551 548
 
552 549
                 record_exists = False
553 550
 
554 551
                 update_pk = bool(meta.has_auto_field and not pk_set)
555  
-                if values:
556  
-                    # Create a new record.
557  
-                    result = manager._insert(values, return_id=update_pk, using=using)
558  
-                else:
559  
-                    # Create a new record with defaults for everything.
560  
-                    result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True, using=using)
  552
+                result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
561 553
 
562 554
                 if update_pk:
563 555
                     setattr(self, meta.pk.attname, result)
14  django/db/models/fields/related.py
@@ -430,7 +430,7 @@ def add(self, *objs):
@@ -438,7 +438,7 @@ def create(self, **kwargs):
@@ -578,11 +578,13 @@ def _add_items(self, source_field_name, target_field_name, *objs):
@@ -701,12 +703,12 @@ class ReverseManyRelatedObjectsDescriptor(object):
7  django/db/models/manager.py
@@ -136,6 +136,9 @@ def get_or_create(self, **kwargs):
136 136
     def create(self, **kwargs):
137 137
         return self.get_query_set().create(**kwargs)
138 138
 
  139
+    def bulk_create(self, *args, **kwargs):
  140
+        return self.get_query_set().bulk_create(*args, **kwargs)
  141
+
139 142
     def filter(self, *args, **kwargs):
140 143
         return self.get_query_set().filter(*args, **kwargs)
141 144
 
@@ -193,8 +196,8 @@ def using(self, *args, **kwargs):
193 196
     def exists(self, *args, **kwargs):
194 197
         return self.get_query_set().exists(*args, **kwargs)
195 198
 
196  
-    def _insert(self, values, **kwargs):
197  
-        return insert_query(self.model, values, **kwargs)
  199
+    def _insert(self, objs, fields, **kwargs):
  200
+        return insert_query(self.model, objs, fields, **kwargs)
198 201
 
199 202
     def _update(self, values, **kwargs):
200 203
         return self.get_query_set()._update(values, **kwargs)
41  django/db/models/query.py
@@ -5,10 +5,12 @@
5 5
 import copy
6 6
 
7 7
 from django.db import connections, router, transaction, IntegrityError
  8
+from django.db.models.fields import AutoField
8 9
 from django.db.models.query_utils import (Q, select_related_descend,
9 10
     deferred_class_factory, InvalidQuery)
10 11
 from django.db.models.deletion import Collector
11 12
 from django.db.models import signals, sql
  13
+from django.utils.functional import partition
12 14
 
13 15
 # Used to control how many objects are worked with at once in some cases (e.g.
14 16
 # when deleting objects).
@@ -352,6 +354,41 @@ def create(self, **kwargs):
352 354
         obj.save(force_insert=True, using=self.db)
353 355
         return obj
354 356
 
  357
+    def bulk_create(self, objs):
  358
+        """
  359
+        Inserts each of the instances into the database. This does *not* call
  360
+        save() on each of the instances, does not send any pre/post save
  361
+        signals, and does not set the primary key attribute if it is an
  362
+        autoincrement field.
  363
+        """
  364
+        # So this case is fun. When you bulk insert you don't get the primary
  365
+        # keys back (if it's an autoincrement), so you can't insert into the
  366
+        # child tables which references this. There are two workarounds, 1)
  367
+        # this could be implemented if you didn't have an autoincrement pk,
  368
+        # and 2) you could do it by doing O(n) normal inserts into the parent
  369
+        # tables to get the primary keys back, and then doing a single bulk
  370
+        # insert into the childmost table. We're punting on these for now
  371
+        # because they are relatively rare cases.
  372
+        if self.model._meta.parents:
  373
+            raise ValueError("Can't bulk create an inherited model")
  374
+        if not objs:
  375
+            return
  376
+        self._for_write = True
  377
+        connection = connections[self.db]
  378
+        fields = self.model._meta.local_fields
  379
+        if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
  380
+            and self.model._meta.has_auto_field):
  381
+            self.model._base_manager._insert(objs, fields=fields, using=self.db)
  382
+        else:
  383
+            objs_with_pk, objs_without_pk = partition(
  384
+                lambda o: o.pk is None,
  385
+                objs
  386
+            )
  387
+            if objs_with_pk:
  388
+                self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db)
  389
+            if objs_without_pk:
  390
+                self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db)
  391
+
355 392
     def get_or_create(self, **kwargs):
356 393
         """
357 394
         Looks up an object with the given kwargs, creating one if necessary.
@@ -1437,12 +1474,12 @@ def model_fields(self):
1437 1474
                 self._model_fields[converter(column)] = field
1438 1475
         return self._model_fields
1439 1476
 
1440  
-def insert_query(model, values, return_id=False, raw_values=False, using=None):
  1477
+def insert_query(model, objs, fields, return_id=False, raw=False, using=None):
1441 1478
     """
1442 1479
     Inserts a new record for the given model. This provides an interface to
1443 1480
     the InsertQuery class and is how Model.save() is implemented. It is not
1444 1481
     part of the public API.
1445 1482
     """
1446 1483
     query = sql.InsertQuery(model)
1447  
-    query.insert_values(values, raw_values)
  1484
+    query.insert_values(fields, objs, raw=raw)
1448 1485
     return query.get_compiler(using=using).execute_sql(return_id)
52  django/db/models/sql/compiler.py
... ...
@@ -1,3 +1,5 @@
  1
+from itertools import izip
  2
+
1 3
 from django.core.exceptions import FieldError
2 4
 from django.db import connections
3 5
 from django.db import transaction
@@ -9,6 +11,7 @@
9 11
      select_related_descend, Query)
10 12
 from django.db.utils import DatabaseError
11 13
 
  14
+
12 15
 class SQLCompiler(object):
13 16
     def __init__(self, query, connection, using):
14 17
         self.query = query
@@ -794,20 +797,55 @@ def as_sql(self):
794 797
         qn = self.connection.ops.quote_name
795 798
         opts = self.query.model._meta
796 799
         result = ['INSERT INTO %s' % qn(opts.db_table)]
797  
-        result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
798  
-        values = [self.placeholder(*v) for v in self.query.values]
799  
-        result.append('VALUES (%s)' % ', '.join(values))
800  
-        params = self.query.params
  800
+
  801
+        has_fields = bool(self.query.fields)
  802
+        fields = self.query.fields if has_fields else [opts.pk]
  803
+        result.append('(%s)' % ', '.join([qn(f.column) for f in fields]))
  804
+
  805
+        if has_fields:
  806
+            params = values = [
  807
+                [
  808
+                    f.get_db_prep_save(getattr(obj, f.attname) if self.query.raw else f.pre_save(obj, True), connection=self.connection)
  809
+                    for f in fields
  810
+                ]
  811
+                for obj in self.query.objs
  812
+            ]
  813
+        else:
  814
+            values = [[self.connection.ops.pk_default_value()] for obj in self.query.objs]
  815
+            params = [[]]
  816
+            fields = [None]
  817
+        can_bulk = not any(hasattr(field, "get_placeholder") for field in fields) and not self.return_id
  818
+
  819
+        if can_bulk:
  820
+            placeholders = [["%s"] * len(fields)]
  821
+        else:
  822
+            placeholders = [
  823
+                [self.placeholder(field, v) for field, v in izip(fields, val)]
  824
+                for val in values
  825
+            ]
801 826
         if self.return_id and self.connection.features.can_return_id_from_insert:
  827
+            params = values[0]
802 828
             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
  829
+            result.append("VALUES (%s)" % ", ".join(placeholders[0]))
803 830
             r_fmt, r_params = self.connection.ops.return_insert_id()
804 831
             result.append(r_fmt % col)
805  
-            params = params + r_params
806  
-        return ' '.join(result), params
  832
+            params += r_params
  833
+            return [(" ".join(result), tuple(params))]
  834
+        if can_bulk and self.connection.features.has_bulk_insert:
  835
+            result.append(self.connection.ops.bulk_insert_sql(fields, len(values)))
  836
+            return [(" ".join(result), tuple([v for val in values for v in val]))]
  837
+        else:
  838
+            return [
  839
+                (" ".join(result + ["VALUES (%s)" % ", ".join(p)]), vals)
  840
+                for p, vals in izip(placeholders, params)
  841
+            ]
807 842
 
808 843
     def execute_sql(self, return_id=False):
  844
+        assert not (return_id and len(self.query.objs) != 1)
809 845
         self.return_id = return_id
810  
-        cursor = super(SQLInsertCompiler, self).execute_sql(None)
  846
+        cursor = self.connection.cursor()
  847
+        for sql, params in self.as_sql():
  848
+            cursor.execute(sql, params)
811 849
         if not (return_id and cursor):
812 850
             return
813 851
         if self.connection.features.can_return_id_from_insert:
3  django/db/models/sql/query.py
@@ -8,9 +8,10 @@
8 8
 """
9 9
 
10 10
 import copy
11  
-from django.utils.tree import Node
  11
+
12 12
 from django.utils.datastructures import SortedDict
13 13
 from django.utils.encoding import force_unicode
  14
+from django.utils.tree import Node
14 15
 from django.db import connections, DEFAULT_DB_ALIAS
15 16
 from django.db.models import signals
16 17
 from django.db.models.fields import FieldDoesNotExist
26  django/db/models/sql/subqueries.py
@@ -136,20 +136,19 @@ class InsertQuery(Query):
136 136
 
137 137
     def __init__(self, *args, **kwargs):
138 138
         super(InsertQuery, self).__init__(*args, **kwargs)
139  
-        self.columns = []
140  
-        self.values = []
141  
-        self.params = ()
  139
+        self.fields = []
  140
+        self.objs = []
142 141
 
143 142
     def clone(self, klass=None, **kwargs):
144 143
         extras = {
145  
-            'columns': self.columns[:],
146  
-            'values': self.values[:],
147  
-            'params': self.params
  144
+            'fields': self.fields[:],
  145
+            'objs': self.objs[:],
  146
+            'raw': self.raw,
148 147
         }
149 148
         extras.update(kwargs)
150 149
         return super(InsertQuery, self).clone(klass, **extras)
151 150
 
152  
-    def insert_values(self, insert_values, raw_values=False):
  151
+    def insert_values(self, fields, objs, raw=False):
153 152
         """
154 153
         Set up the insert query from the 'insert_values' dictionary. The
155 154
         dictionary gives the model field names and their target values.
@@ -159,16 +158,9 @@ def insert_values(self, insert_values, raw_values=False):
159 158
         parameters. This provides a way to insert NULL and DEFAULT keywords
160 159
         into the query, for example.
161 160
         """
162  
-        placeholders, values = [], []
163  
-        for field, val in insert_values:
164  
-            placeholders.append((field, val))
165  
-            self.columns.append(field.column)
166  
-            values.append(val)
167  
-        if raw_values:
168  
-            self.values.extend([(None, v) for v in values])
169  
-        else:
170  
-            self.params += tuple(values)
171  
-            self.values.extend(placeholders)
  161
+        self.fields = fields
  162
+        self.objs = objs
  163
+        self.raw = raw
172 164
 
173 165
 class DateQuery(Query):
174 166
     """
15  django/utils/functional.py
@@ -275,4 +275,17 @@ def fset(instance, value, name=fset.__name__):
275 275
             @wraps(fdel)
276 276
             def fdel(instance, name=fdel.__name__):
277 277
                 return getattr(instance, name)()
278  
-        return property(fget, fset, fdel, doc)
  278
+        return property(fget, fset, fdel, doc)
  279
+
  280
+def partition(predicate, values):
  281
+    """
  282
+    Splits the values into two sets, based on the return value of the function
  283
+    (True/False). e.g.:
  284
+
  285
+        >>> partition(lambda: x > 3, range(5))
  286
+        [1, 2, 3], [4]
  287
+    """
  288
+    results = ([], [])
  289
+    for item in values:
  290
+        results[predicate(item)].append(item)
  291
+    return results
23  docs/ref/models/querysets.txt
@@ -1158,6 +1158,29 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
1158 1158
 
1159 1159
 .. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
1160 1160
 
  1161
+bulk_create
  1162
+~~~~~~~~~~~
  1163
+
  1164
+.. method:: bulk_create(objs)
  1165
+
  1166
+This method inserts the provided list of objects into the database in an
  1167
+efficient manner (generally only 1 query, no matter how many objects there
  1168
+are)::
  1169
+
  1170
+    >>> Entry.objects.bulk_create([
  1171
+    ...     Entry(headline="Django 1.0 Released"),
  1172
+    ...     Entry(headline="Django 1.1 Announced"),
  1173
+    ...     Entry(headline="Breaking: Django is awesome")
  1174
+    ... ])
  1175
+
  1176
+This has a number of caveats though:
  1177
+
  1178
+  * The model's ``save()`` method will not be called, and the ``pre_save`` and
  1179
+    ``post_save`` signals will not be sent.
  1180
+  * It does not work with child models in a multi-table inheritance scenario.
  1181
+  * If the model's primary key is an :class:`~django.db.models.AutoField` it
  1182
+    does not retrieve and set the primary key attribute, as ``save()`` does.
  1183
+
1161 1184
 count
1162 1185
 ~~~~~
1163 1186
 
11  docs/releases/1.4.txt
@@ -252,6 +252,17 @@ filename. For example, the file ``css/styles.css`` would also be saved as
252 252
 See the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
253 253
 docs for more information.
254 254
 
  255
+``Model.objects.bulk_create`` in the ORM
  256
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  257
+
  258
+This method allows for more efficient creation of multiple objects in the ORM.
  259
+It can provide significant performance increases if you have many objects,
  260
+Django makes use of this internally, meaning some operations (such as database
  261
+setup for test suites) has seen a performance benefit as a result.
  262
+
  263
+See the :meth:`~django.db.models.query.QuerySet.bulk_create` docs for more
  264
+information.
  265
+
255 266
 Minor features
256 267
 ~~~~~~~~~~~~~~
257 268
 
30  docs/topics/db/optimization.txt
@@ -268,3 +268,33 @@ instead of::
268 268
 
269 269
    entry.blog.id
270 270
 
  271
+Insert in bulk
  272
+==============
  273
+
  274
+When creating objects, where possible, use the
  275
+:meth:`~django.db.models.query.QuerySet.bulk_create()` method to reduce the
  276
+number of SQL queries. For example::
  277
+
  278
+    Entry.objects.bulk_create([
  279
+        Entry(headline="Python 3.0 Released"),
  280
+        Entry(headline="Python 3.1 Planned")
  281
+    ])
  282
+
  283
+Is preferable to::
  284
+
  285
+    Entry.objects.create(headline="Python 3.0 Released")
  286
+    Entry.objects.create(headline="Python 3.1 Planned")
  287
+
  288
+Note that there are a number of :meth:`caveats to this method
  289
+<django.db.models.query.QuerySet.bulk_create>`, make sure it is appropriate for
  290
+your use case. This also applies to :class:`ManyToManyFields
  291
+<django.db.models.ManyToManyField>`, doing::
  292
+
  293
+    my_band.members.add(me, my_friend)
  294
+
  295
+Is preferable to::
  296
+
  297
+    my_band.members.add(me)
  298
+    my_band.members.add(my_friend)
  299
+
  300
+Where ``Bands`` and ``Artists`` have a many-to-many relationship.
0  tests/regressiontests/bulk_create/__init__.py
No changes.
21  tests/regressiontests/bulk_create/models.py
... ...
@@ -0,0 +1,21 @@
  1
+from django.db import models
  2
+
  3
+
  4
+class Country(models.Model):
  5
+    name = models.CharField(max_length=255)
  6
+    iso_two_letter = models.CharField(max_length=2)
  7
+
  8
+class Place(models.Model):
  9
+    name = models.CharField(max_length=100)
  10
+
  11
+    class Meta:
  12
+        abstract = True
  13
+
  14
+class Restaurant(Place):
  15
+    pass
  16
+
  17
+class Pizzeria(Restaurant):
  18
+    pass
  19
+
  20
+class State(models.Model):
  21
+    two_letter_code = models.CharField(max_length=2, primary_key=True)
54  tests/regressiontests/bulk_create/tests.py
... ...
@@ -0,0 +1,54 @@
  1
+from __future__ import with_statement
  2
+
  3
+from operator import attrgetter
  4
+
  5
+from django.test import TestCase, skipUnlessDBFeature
  6
+
  7
+from models import Country, Restaurant, Pizzeria, State
  8
+
  9
+
  10
+class BulkCreateTests(TestCase):
  11
+    def setUp(self):
  12
+        self.data = [
  13
+            Country(name="United States of America", iso_two_letter="US"),
  14
+            Country(name="The Netherlands", iso_two_letter="NL"),
  15
+            Country(name="Germany", iso_two_letter="DE"),
  16
+            Country(name="Czech Republic", iso_two_letter="CZ")
  17
+        ]
  18
+
  19
+    def test_simple(self):
  20
+        Country.objects.bulk_create(self.data)
  21
+        self.assertQuerysetEqual(Country.objects.order_by("-name"), [
  22
+            "United States of America", "The Netherlands", "Germany", "Czech Republic"
  23
+        ], attrgetter("name"))
  24
+
  25
+    @skipUnlessDBFeature("has_bulk_insert")
  26
+    def test_efficiency(self):
  27
+        with self.assertNumQueries(1):
  28
+            Country.objects.bulk_create(self.data)
  29
+
  30
+    def test_inheritance(self):
  31
+        Restaurant.objects.bulk_create([
  32
+            Restaurant(name="Nicholas's")
  33
+        ])
  34
+        self.assertQuerysetEqual(Restaurant.objects.all(), [
  35
+            "Nicholas's",
  36
+        ], attrgetter("name"))
  37
+        with self.assertRaises(ValueError):
  38
+            Pizzeria.objects.bulk_create([
  39
+                Pizzeria(name="The Art of Pizza")
  40
+            ])
  41
+        self.assertQuerysetEqual(Pizzeria.objects.all(), [])
  42
+        self.assertQuerysetEqual(Restaurant.objects.all(), [
  43
+            "Nicholas's",
  44
+        ], attrgetter("name"))
  45
+
  46
+    def test_non_auto_increment_pk(self):
  47
+        with self.assertNumQueries(1):
  48
+            State.objects.bulk_create([
  49
+                State(two_letter_code=s)
  50
+                for s in ["IL", "NY", "CA", "ME"]
  51
+            ])
  52
+        self.assertQuerysetEqual(State.objects.order_by("two_letter_code"), [
  53
+            "CA", "IL", "ME", "NY",
  54
+        ], attrgetter("two_letter_code"))
4  tests/regressiontests/db_typecasts/tests.py
@@ -53,10 +53,10 @@
53 53
 
54 54
 class DBTypeCasts(unittest.TestCase):
55 55
     def test_typeCasts(self):
56  
-        for k, v in TEST_CASES.items():
  56
+        for k, v in TEST_CASES.iteritems():
57 57
             for inpt, expected in v:
58 58
                 got = getattr(typecasts, k)(inpt)
59  
-                assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
  59
+                self.assertEqual(got, expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got))
60 60
 
61 61
 if __name__ == '__main__':
62 62
     unittest.main()

0 notes on commit 7deb25b

Please sign in to comment.
Something went wrong with that request. Please try again.