Fixed #24997 -- Enabled bulk_create() on proxy models

This commit is contained in:
William Schwartz 2015-06-18 10:12:07 -04:00 committed by Tim Graham
parent fedef7b2c6
commit 9a5cfa05a0
6 changed files with 68 additions and 18 deletions

View File

@ -724,6 +724,7 @@ answer newbie questions, and generally made Django that much better:
Wiktor Kołodziej <wiktor@pykonik.org> Wiktor Kołodziej <wiktor@pykonik.org>
Wiley Kestner <wiley.kestner@gmail.com> Wiley Kestner <wiley.kestner@gmail.com>
Wiliam Alves de Souza <wiliamsouza83@gmail.com> Wiliam Alves de Souza <wiliamsouza83@gmail.com>
William Schwartz <wkschwartz@gmail.com>
Will Hardy <django@willhardy.com.au> Will Hardy <django@willhardy.com.au>
Wilson Miner <wminer@gmail.com> Wilson Miner <wminer@gmail.com>
wojtek wojtek

View File

@ -411,7 +411,7 @@ class QuerySet(object):
Inserts each of the instances into the database. This does *not* call Inserts each of the instances into the database. This does *not* call
save() on each of the instances, does not send any pre/post save save() on each of the instances, does not send any pre/post save
signals, and does not set the primary key attribute if it is an signals, and does not set the primary key attribute if it is an
autoincrement field. autoincrement field. Multi-table models are not supported.
""" """
# So this case is fun. When you bulk insert you don't get the primary # So this case is fun. When you bulk insert you don't get the primary
# keys back (if it's an autoincrement), so you can't insert into the # keys back (if it's an autoincrement), so you can't insert into the
@ -423,13 +423,18 @@ class QuerySet(object):
# this by using RETURNING clause for the insert query. We're punting # this by using RETURNING clause for the insert query. We're punting
# on these for now because they are relatively rare cases. # on these for now because they are relatively rare cases.
assert batch_size is None or batch_size > 0 assert batch_size is None or batch_size > 0
if self.model._meta.parents: # Check that the parents share the same concrete model with the our
raise ValueError("Can't bulk create an inherited model") # model to detect the inheritance pattern ConcreteGrandParent ->
# MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy
# would not identify that case as involving multiple tables.
for parent in self.model._meta.get_parent_list():
if parent._meta.concrete_model is not self.model._meta.concrete_model:
raise ValueError("Can't bulk create a multi-table inherited model")
if not objs: if not objs:
return objs return objs
self._for_write = True self._for_write = True
connection = connections[self.db] connection = connections[self.db]
fields = self.model._meta.local_concrete_fields fields = self.model._meta.concrete_fields
objs = list(objs) objs = list(objs)
self._populate_pk_values(objs) self._populate_pk_values(objs)
with transaction.atomic(using=self.db, savepoint=False): with transaction.atomic(using=self.db, savepoint=False):

View File

@ -1768,6 +1768,10 @@ This has a number of caveats though:
does not retrieve and set the primary key attribute, as ``save()`` does. does not retrieve and set the primary key attribute, as ``save()`` does.
* It does not work with many-to-many relationships. * It does not work with many-to-many relationships.
.. versionchanged:: 1.9
Support for using ``bulk_create()`` with proxy models was added.
The ``batch_size`` parameter controls how many objects are created in single The ``batch_size`` parameter controls how many objects are created in single
query. The default is to create all objects in one batch, except for SQLite query. The default is to create all objects in one batch, except for SQLite
where the default is such that at most 999 variables per query are used. where the default is such that at most 999 variables per query are used.

View File

@ -365,6 +365,9 @@ Management Commands
Models Models
^^^^^^ ^^^^^^
* :meth:`QuerySet.bulk_create() <django.db.models.query.QuerySet.bulk_create>`
now works on proxy models.
* Database configuration gained a :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` * Database configuration gained a :setting:`TIME_ZONE <DATABASE-TIME_ZONE>`
option for interacting with databases that store datetimes in local time and option for interacting with databases that store datetimes in local time and
don't support time zones when :setting:`USE_TZ` is ``True``. don't support time zones when :setting:`USE_TZ` is ``True``.

View File

@ -6,6 +6,25 @@ class Country(models.Model):
iso_two_letter = models.CharField(max_length=2) iso_two_letter = models.CharField(max_length=2)
class ProxyCountry(Country):
class Meta:
proxy = True
class ProxyProxyCountry(ProxyCountry):
class Meta:
proxy = True
class ProxyMultiCountry(ProxyCountry):
pass
class ProxyMultiProxyCountry(ProxyMultiCountry):
class Meta:
proxy = True
class Place(models.Model): class Place(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)

View File

@ -7,7 +7,10 @@ from django.test import (
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature, TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
) )
from .models import Country, Pizzeria, Restaurant, State, TwoFields from .models import (
Country, Pizzeria, ProxyCountry, ProxyMultiCountry, ProxyMultiProxyCountry,
ProxyProxyCountry, Restaurant, State, TwoFields,
)
class BulkCreateTests(TestCase): class BulkCreateTests(TestCase):
@ -35,21 +38,36 @@ class BulkCreateTests(TestCase):
with self.assertNumQueries(1): with self.assertNumQueries(1):
Country.objects.bulk_create(self.data) Country.objects.bulk_create(self.data)
def test_inheritance(self): def test_multi_table_inheritance_unsupported(self):
Restaurant.objects.bulk_create([ expected_message = "Can't bulk create a multi-table inherited model"
Restaurant(name="Nicholas's") with self.assertRaisesMessage(ValueError, expected_message):
])
self.assertQuerysetEqual(Restaurant.objects.all(), [
"Nicholas's",
], attrgetter("name"))
with self.assertRaises(ValueError):
Pizzeria.objects.bulk_create([ Pizzeria.objects.bulk_create([
Pizzeria(name="The Art of Pizza") Pizzeria(name="The Art of Pizza"),
]) ])
self.assertQuerysetEqual(Pizzeria.objects.all(), []) with self.assertRaisesMessage(ValueError, expected_message):
self.assertQuerysetEqual(Restaurant.objects.all(), [ ProxyMultiCountry.objects.bulk_create([
"Nicholas's", ProxyMultiCountry(name="Fillory", iso_two_letter="FL"),
], attrgetter("name")) ])
with self.assertRaisesMessage(ValueError, expected_message):
ProxyMultiProxyCountry.objects.bulk_create([
ProxyMultiProxyCountry(name="Fillory", iso_two_letter="FL"),
])
def test_proxy_inheritance_supported(self):
ProxyCountry.objects.bulk_create([
ProxyCountry(name="Qwghlm", iso_two_letter="QW"),
Country(name="Tortall", iso_two_letter="TA"),
])
self.assertQuerysetEqual(ProxyCountry.objects.all(), {
"Qwghlm", "Tortall"
}, attrgetter("name"), ordered=False)
ProxyProxyCountry.objects.bulk_create([
ProxyProxyCountry(name="Neitherlands", iso_two_letter="NT"),
])
self.assertQuerysetEqual(ProxyProxyCountry.objects.all(), {
"Qwghlm", "Tortall", "Neitherlands",
}, attrgetter("name"), ordered=False)
def test_non_auto_increment_pk(self): def test_non_auto_increment_pk(self):
State.objects.bulk_create([ State.objects.bulk_create([