Ticket #7596: 7596.diff

File 7596.diff, 26.6 KB (added by Simon Meers, 13 years ago)

Updated/tweaked version of Alex's patch

  • django/contrib/auth/management/__init__.py

    diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
    index f82060e..532654f 100644
    a b def create_permissions(app, created_models, verbosity, **kwargs):  
    4646        "content_type", "codename"
    4747    ))
    4848
    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
    6058
    6159
    6260def create_superuser(app, created_models, verbosity, **kwargs):
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    index 23ddedb..06ce9de 100644
    a b class BaseDatabaseFeatures(object):  
    301301
    302302    can_use_chunked_reads = True
    303303    can_return_id_from_insert = False
     304    has_bulk_insert = False
    304305    uses_autocommit = False
    305306    uses_savepoints = False
     307    can_combine_inserts_with_and_without_auto_increment_pk = False
    306308
    307309    # If True, don't use integer foreign keys referring to, e.g., positive
    308310    # integer primary keys.
  • django/db/backends/mysql/base.py

    diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
    index 2dd9e84..b4d497b 100644
    a b class DatabaseFeatures(BaseDatabaseFeatures):  
    124124    allows_group_by_pk = True
    125125    related_fields_match_type = True
    126126    allow_sliced_subqueries = False
     127    has_bulk_insert = True
    127128    has_select_for_update = True
    128129    has_select_for_update_nowait = False
    129130    supports_forward_references = False
    class DatabaseOperations(BaseDatabaseOperations):  
    263264    def max_name_length(self):
    264265        return 64
    265266
     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
     271
    266272class DatabaseWrapper(BaseDatabaseWrapper):
    267273    vendor = 'mysql'
    268274    operators = {
  • django/db/backends/postgresql_psycopg2/base.py

    diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
    index 7efa6e6..f0a89e5 100644
    a b class DatabaseFeatures(BaseDatabaseFeatures):  
    7474    can_defer_constraint_checks = True
    7575    has_select_for_update = True
    7676    has_select_for_update_nowait = True
     77    has_bulk_insert = True
    7778
    7879
    7980class DatabaseWrapper(BaseDatabaseWrapper):
  • django/db/backends/postgresql_psycopg2/operations.py

    diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
    index d535ee3..c3a23c2 100644
    a b class DatabaseOperations(BaseDatabaseOperations):  
    180180
    181181    def return_insert_id(self):
    182182        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)
  • django/db/backends/sqlite3/base.py

    diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
    index e3c12f7..b45c0fb 100644
    a b class DatabaseFeatures(BaseDatabaseFeatures):  
    5858    supports_unspecified_pk = True
    5959    supports_1000_query_parameters = False
    6060    supports_mixed_date_datetime_comparisons = False
     61    has_bulk_insert = True
     62    can_combine_inserts_with_and_without_auto_increment_pk = True
    6163
    6264    def _supports_stddev(self):
    6365        """Confirm support for STDDEV and related stats functions
    class DatabaseOperations(BaseDatabaseOperations):  
    106108        return ""
    107109
    108110    def pk_default_value(self):
    109         return 'NULL'
     111        return "NULL"
    110112
    111113    def quote_name(self, name):
    112114        if name.startswith('"') and name.endswith('"'):
    class DatabaseOperations(BaseDatabaseOperations):  
    154156        # No field, or the field isn't known to be a decimal or integer
    155157        return value
    156158
     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
    157167class DatabaseWrapper(BaseDatabaseWrapper):
    158168    vendor = 'sqlite'
    159169    # SQLite requires LIKE statements to include an ESCAPE clause if the value
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 71fd1f7..4b3220b 100644
    a b class Model(object):  
    540540                    order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
    541541                    self._order = order_value
    542542
     543                fields = meta.local_fields
    543544                if not pk_set:
    544545                    if force_update:
    545546                        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)]
    551548
    552549                record_exists = False
    553550
    554551                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)
    561553
    562554                if update_pk:
    563555                    setattr(self, meta.pk.attname, result)
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index d904f56..fa7b482 100644
    a b class ForeignRelatedObjectsDescriptor(object):  
    430430            add.alters_data = True
    431431
    432432            def create(self, **kwargs):
    433                 kwargs.update({rel_field.name: instance})
     433                kwargs[rel_field.name] = instance
    434434                db = router.db_for_write(rel_model, instance=instance)
    435435                return super(RelatedManager, self.db_manager(db)).create(**kwargs)
    436436            create.alters_data = True
    class ForeignRelatedObjectsDescriptor(object):  
    438438            def get_or_create(self, **kwargs):
    439439                # Update kwargs with the related object that this
    440440                # ForeignRelatedObjectsDescriptor knows about.
    441                 kwargs.update({rel_field.name: instance})
     441                kwargs[rel_field.name] = instance
    442442                db = router.db_for_write(rel_model, instance=instance)
    443443                return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
    444444            get_or_create.alters_data = True
    def create_many_related_manager(superclass, rel=False):  
    578578                        instance=self.instance, reverse=self.reverse,
    579579                        model=self.model, pk_set=new_ids, using=db)
    580580                # Add the ones that aren't there already
    581                 for obj_id in new_ids:
    582                     self.through._default_manager.using(db).create(**{
     581                self.through._default_manager.using(db).bulk_create([
     582                    self.through(**{
    583583                        '%s_id' % source_field_name: self._pk_val,
    584584                        '%s_id' % target_field_name: obj_id,
    585585                    })
     586                    for obj_id in new_ids
     587                ])
    586588                if self.reverse or source_field_name == self.source_field_name:
    587589                    # Don't send the signal when we are inserting the
    588590                    # duplicate data row for symmetrical reverse entries.
    class ReverseManyRelatedObjectsDescriptor(object):  
    701703    def __init__(self, m2m_field):
    702704        self.field = m2m_field
    703705
    704     def _through(self):
     706    @property
     707    def through(self):
    705708        # through is provided so that you have easy access to the through
    706709        # model (Book.authors.through) for inlines, etc. This is done as
    707710        # a property to ensure that the fully resolved value is returned.
    708711        return self.field.rel.through
    709     through = property(_through)
    710712
    711713    def __get__(self, instance, instance_type=None):
    712714        if instance is None:
  • django/db/models/manager.py

    diff --git a/django/db/models/manager.py b/django/db/models/manager.py
    index bdd86bb..baf701f 100644
    a b class Manager(object):  
    136136    def create(self, **kwargs):
    137137        return self.get_query_set().create(**kwargs)
    138138
     139    def bulk_create(self, *args, **kwargs):
     140        return self.get_query_set().bulk_create(*args, **kwargs)
     141
    139142    def filter(self, *args, **kwargs):
    140143        return self.get_query_set().filter(*args, **kwargs)
    141144
    class Manager(object):  
    193196    def exists(self, *args, **kwargs):
    194197        return self.get_query_set().exists(*args, **kwargs)
    195198
    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)
    198201
    199202    def _update(self, values, **kwargs):
    200203        return self.get_query_set()._update(values, **kwargs)
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index ff5289c..14ef144 100644
    a b The main QuerySet implementation. This provides the public API for the ORM.  
    55import copy
    66
    77from django.db import connections, router, transaction, IntegrityError
     8from django.db.models.fields import AutoField
    89from django.db.models.query_utils import (Q, select_related_descend,
    910    deferred_class_factory, InvalidQuery)
    1011from django.db.models.deletion import Collector
    class QuerySet(object):  
    352353        obj.save(force_insert=True, using=self.db)
    353354        return obj
    354355
     356    def bulk_create(self, objs):
     357        """
     358        Inserts each of the instances into the database. This does *not* call
     359        save() on each of the instances, does not send any pre/post save
     360        signals, and does not set the primary key attribute if it is an
     361        autoincrement field.
     362        """
     363        # So this case is fun. When you bulk insert you don't get the primary
     364        # keys back (if it's an autoincrement), so you can't insert into the
     365        # child tables which references this. There are two workarounds, 1)
     366        # this could be implemented if you didn't have an autoincrement pk,
     367        # and 2) you could do it by doing O(n) normal inserts into the parent
     368        # tables to get the primary keys back, and then doing a single bulk
     369        # insert into the childmost table. We're punting on these for now
     370        # because they are relatively rare cases.
     371        if self.model._meta.parents:
     372            raise ValueError("Can't bulk create an inherited model")
     373        if not objs:
     374            return
     375        self._for_write = True
     376        connection = connections[self.db]
     377        features = connection.features
     378        fields = self.model._meta.local_fields
     379        if (features.can_combine_inserts_with_and_without_auto_increment_pk
     380            and self.model._meta.has_auto_field):
     381            self.model._base_manager._insert(
     382                objs, fields=fields, using=self.db)
     383        else:
     384            objs_with_pk = [o for o in objs if o.pk is not None]
     385            objs_without_pk = [o for o in objs if o.pk is None]
     386            if objs_with_pk:
     387                self.model._base_manager._insert(
     388                    objs_with_pk, fields=fields, using=self.db)
     389            if objs_without_pk:
     390                self.model._base_manager._insert(
     391                    objs_without_pk,
     392                    fields=[f for f in fields if not isinstance(f, AutoField)],
     393                    using=self.db)
     394
    355395    def get_or_create(self, **kwargs):
    356396        """
    357397        Looks up an object with the given kwargs, creating one if necessary.
    class RawQuerySet(object):  
    14371477                self._model_fields[converter(column)] = field
    14381478        return self._model_fields
    14391479
    1440 def insert_query(model, values, return_id=False, raw_values=False, using=None):
     1480def insert_query(model, objs, fields, return_id=False, raw=False, using=None):
    14411481    """
    14421482    Inserts a new record for the given model. This provides an interface to
    14431483    the InsertQuery class and is how Model.save() is implemented. It is not
    14441484    part of the public API.
    14451485    """
    14461486    query = sql.InsertQuery(model)
    1447     query.insert_values(values, raw_values)
     1487    query.insert_values(fields, objs, raw=raw)
    14481488    return query.get_compiler(using=using).execute_sql(return_id)
  • django/db/models/sql/compiler.py

    diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
    index 05c19f3..bbb851d 100644
    a b  
     1from itertools import izip
     2
    13from django.core.exceptions import FieldError
    24from django.db import connections
    35from django.db import transaction
    from django.db.models.sql.query import (get_proxied_model, get_order_dir,  
    911     select_related_descend, Query)
    1012from django.db.utils import DatabaseError
    1113
     14
    1215class SQLCompiler(object):
    1316    def __init__(self, query, connection, using):
    1417        self.query = query
    class SQLInsertCompiler(SQLCompiler):  
    794797        qn = self.connection.ops.quote_name
    795798        opts = self.query.model._meta
    796799        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
    801         if self.return_id and self.connection.features.can_return_id_from_insert:
     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(
     809                        getattr(obj, f.attname) if self.query.raw else
     810                        f.pre_save(obj, True), connection=self.connection)
     811                    for f in fields
     812                ]
     813                for obj in self.query.objs
     814            ]
     815        else:
     816            values = [
     817                [self.connection.ops.pk_default_value()]
     818                for obj in self.query.objs
     819            ]
     820            params = [[]]
     821            fields = [None]
     822
     823        can_bulk = (
     824            not any(hasattr(field, "get_placeholder") for field in fields) and
     825            not self.return_id)
     826
     827        if can_bulk:
     828            placeholders = [["%s"] * len(fields)]
     829        else:
     830            placeholders = [
     831                [self.placeholder(field, v) for field, v in izip(fields, val)]
     832                for val in values
     833            ]
     834        if self.return_id and \
     835                self.connection.features.can_return_id_from_insert:
     836            (params,) = values
     837            (placeholders,) = placeholders
    802838            col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
     839            result.append("VALUES (%s)" % ", ".join(placeholders))
    803840            r_fmt, r_params = self.connection.ops.return_insert_id()
    804841            result.append(r_fmt % col)
    805             params = params + r_params
    806         return ' '.join(result), params
     842            params += r_params
     843            return [(" ".join(result), tuple(params))]
     844        if can_bulk and self.connection.features.has_bulk_insert:
     845            result.append(
     846                self.connection.ops.bulk_insert_sql(fields, len(values)))
     847            return [
     848                (" ".join(result), tuple([v for val in values for v in val]))]
     849        else:
     850            return [
     851                (" ".join(result + ["VALUES (%s)" % ", ".join(p)]), vals)
     852                for p, vals in izip(placeholders, params)
     853            ]
    807854
    808855    def execute_sql(self, return_id=False):
     856        assert not (return_id and len(self.query.objs) != 1)
    809857        self.return_id = return_id
    810         cursor = super(SQLInsertCompiler, self).execute_sql(None)
     858        cursor = self.connection.cursor()
     859        for sql, params in self.as_sql():
     860            cursor.execute(sql, params)
    811861        if not (return_id and cursor):
    812862            return
    813863        if self.connection.features.can_return_id_from_insert:
    814864            return self.connection.ops.fetch_returned_insert_id(cursor)
    815         return self.connection.ops.last_insert_id(cursor,
    816                 self.query.model._meta.db_table, self.query.model._meta.pk.column)
     865        return self.connection.ops.last_insert_id(
     866            cursor, self.query.model._meta.db_table,
     867            self.query.model._meta.pk.column)
    817868
    818869
    819870class SQLDeleteCompiler(SQLCompiler):
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index b55de3d..101d4ac 100644
    a b all about the internals of models in order to get the information it needs.  
    88"""
    99
    1010import copy
    11 from django.utils.tree import Node
     11
    1212from django.utils.datastructures import SortedDict
    1313from django.utils.encoding import force_unicode
     14from django.utils.tree import Node
    1415from django.db import connections, DEFAULT_DB_ALIAS
    1516from django.db.models import signals
    1617from django.db.models.fields import FieldDoesNotExist
  • django/db/models/sql/subqueries.py

    diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
    index ecde857..1c409c2 100644
    a b class InsertQuery(Query):  
    136136
    137137    def __init__(self, *args, **kwargs):
    138138        super(InsertQuery, self).__init__(*args, **kwargs)
    139         self.columns = []
    140         self.values = []
    141         self.params = ()
     139        self.fields = []
     140        self.objs = []
     141        self.raw = False
    142142
    143143    def clone(self, klass=None, **kwargs):
    144144        extras = {
    145             'columns': self.columns[:],
    146             'values': self.values[:],
    147             'params': self.params
     145            'fields': self.fields[:],
     146            'objs': self.objs[:],
     147            'raw': self.raw,
    148148        }
    149149        extras.update(kwargs)
    150150        return super(InsertQuery, self).clone(klass, **extras)
    151151
    152     def insert_values(self, insert_values, raw_values=False):
     152    def insert_values(self, fields, objs, raw=False):
    153153        """
    154154        Set up the insert query from the 'insert_values' dictionary. The
    155155        dictionary gives the model field names and their target values.
    class InsertQuery(Query):  
    159159        parameters. This provides a way to insert NULL and DEFAULT keywords
    160160        into the query, for example.
    161161        """
    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)
     162        self.fields = fields
     163        self.objs = objs
     164        self.raw = raw
    172165
    173166class DateQuery(Query):
    174167    """
  • docs/ref/models/querysets.txt

    diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
    index b2c521e..898306d 100644
    a b has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.  
    11581158
    11591159.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
    11601160
     1161bulk_create
     1162~~~~~~~~~~~
     1163
     1164.. method:: bulk_create(objs)
     1165
     1166This method inserts the provided list of objects into the database in an
     1167efficient manner (generally only 1 query, no matter how many objects there
     1168are)::
     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
     1176This 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
    11611184count
    11621185~~~~~
    11631186
  • docs/topics/db/optimization.txt

    diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt
    index 265ef55..5093917 100644
    a b instead of::  
    268268
    269269   entry.blog.id
    270270
     271Insert in bulk
     272==============
     273
     274When creating objects, where possible, use the
     275:meth:`~django.db.models.query.QuerySet.bulk_create()` method to reduce the
     276number 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
     283Is preferable to::
     284
     285    Entry.objects.create(headline="Python 3.0 Released")
     286    Entry.objects.create(headline="Python 3.1 Planned")
     287
     288Note 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
     290your use case. This also applies to :class:`ManyToManyFields
     291<django.db.models.ManyToManyField>`, doing::
     292
     293    my_band.members.add(me, my_friend)
     294
     295Is preferable to::
     296
     297    my_band.members.add(me)
     298    my_band.members.add(my_friend)
     299
     300Where ``Bands`` and ``Artists`` have a many-to-many relationship.
  • new file tests/regressiontests/bulk_create/models.py

    diff --git a/tests/regressiontests/bulk_create/__init__.py b/tests/regressiontests/bulk_create/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/bulk_create/models.py b/tests/regressiontests/bulk_create/models.py
    new file mode 100644
    index 0000000..a4c611d
    - +  
     1from django.db import models
     2
     3
     4class Country(models.Model):
     5    name = models.CharField(max_length=255)
     6    iso_two_letter = models.CharField(max_length=2)
     7
     8class Place(models.Model):
     9    name = models.CharField(max_length=100)
     10
     11    class Meta:
     12        abstract = True
     13
     14class Restaurant(Place):
     15    pass
     16
     17class Pizzeria(Restaurant):
     18    pass
     19
     20class State(models.Model):
     21    two_letter_code = models.CharField(max_length=2, primary_key=True)
     22 No newline at end of file
  • new file tests/regressiontests/bulk_create/tests.py

    diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py
    new file mode 100644
    index 0000000..64192a1
    - +  
     1from __future__ import with_statement
     2
     3from operator import attrgetter
     4
     5from django.test import TestCase, skipUnlessDBFeature
     6
     7from .models import Country, Restaurant, Pizzeria, State
     8
     9
     10class 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",
     23            "Czech Republic"
     24        ], attrgetter("name"))
     25
     26    @skipUnlessDBFeature("has_bulk_insert")
     27    def test_efficiency(self):
     28        with self.assertNumQueries(1):
     29            Country.objects.bulk_create(self.data)
     30
     31    def test_inheritance(self):
     32        Restaurant.objects.bulk_create([
     33            Restaurant(name="Nicholas's")
     34        ])
     35        self.assertQuerysetEqual(Restaurant.objects.all(), [
     36            "Nicholas's",
     37        ], attrgetter("name"))
     38        with self.assertRaises(ValueError):
     39            Pizzeria.objects.bulk_create([
     40                Pizzeria(name="The Art of Pizza")
     41            ])
     42        self.assertQuerysetEqual(Pizzeria.objects.all(), [])
     43        self.assertQuerysetEqual(Restaurant.objects.all(), [
     44            "Nicholas's",
     45        ], attrgetter("name"))
     46
     47    def test_non_auto_increment_pk(self):
     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"))
  • tests/regressiontests/db_typecasts/tests.py

    diff --git a/tests/regressiontests/db_typecasts/tests.py b/tests/regressiontests/db_typecasts/tests.py
    index 8c71c8f..1d3bbfa 100644
    a b TEST_CASES = {  
    5353
    5454class DBTypeCasts(unittest.TestCase):
    5555    def test_typeCasts(self):
    56         for k, v in TEST_CASES.items():
     56        for k, v in TEST_CASES.iteritems():
    5757            for inpt, expected in v:
    5858                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))
    6060
    6161if __name__ == '__main__':
    6262    unittest.main()
Back to Top