diff --git a/AUTHORS b/AUTHORS index 271de3aa355..0e0b7fdabde 100644 --- a/AUTHORS +++ b/AUTHORS @@ -724,6 +724,7 @@ answer newbie questions, and generally made Django that much better: Wiktor KoƂodziej Wiley Kestner Wiliam Alves de Souza + William Schwartz Will Hardy Wilson Miner wojtek diff --git a/django/db/models/query.py b/django/db/models/query.py index 5813ec66881..1484d9dca94 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -411,7 +411,7 @@ class QuerySet(object): 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 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 # 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 # on these for now because they are relatively rare cases. assert batch_size is None or batch_size > 0 - if self.model._meta.parents: - raise ValueError("Can't bulk create an inherited model") + # Check that the parents share the same concrete model with the our + # 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: return objs self._for_write = True connection = connections[self.db] - fields = self.model._meta.local_concrete_fields + fields = self.model._meta.concrete_fields objs = list(objs) self._populate_pk_values(objs) with transaction.atomic(using=self.db, savepoint=False): diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index a333ce7b564..039fea3dbcc 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1768,6 +1768,10 @@ This has a number of caveats though: does not retrieve and set the primary key attribute, as ``save()`` does. * 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 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. diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index b1a799f8bf9..3dc76dbd12a 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -365,6 +365,9 @@ Management Commands Models ^^^^^^ +* :meth:`QuerySet.bulk_create() ` + now works on proxy models. + * Database configuration gained a :setting:`TIME_ZONE ` option for interacting with databases that store datetimes in local time and don't support time zones when :setting:`USE_TZ` is ``True``. diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py index fcc4b4177ea..22d16e75e5f 100644 --- a/tests/bulk_create/models.py +++ b/tests/bulk_create/models.py @@ -6,6 +6,25 @@ class Country(models.Model): 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): name = models.CharField(max_length=100) diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index 924e69e0a51..3a0c6541126 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -7,7 +7,10 @@ from django.test import ( 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): @@ -35,21 +38,36 @@ class BulkCreateTests(TestCase): with self.assertNumQueries(1): Country.objects.bulk_create(self.data) - def test_inheritance(self): - Restaurant.objects.bulk_create([ - Restaurant(name="Nicholas's") - ]) - self.assertQuerysetEqual(Restaurant.objects.all(), [ - "Nicholas's", - ], attrgetter("name")) - with self.assertRaises(ValueError): + def test_multi_table_inheritance_unsupported(self): + expected_message = "Can't bulk create a multi-table inherited model" + with self.assertRaisesMessage(ValueError, expected_message): Pizzeria.objects.bulk_create([ - Pizzeria(name="The Art of Pizza") + Pizzeria(name="The Art of Pizza"), ]) - self.assertQuerysetEqual(Pizzeria.objects.all(), []) - self.assertQuerysetEqual(Restaurant.objects.all(), [ - "Nicholas's", - ], attrgetter("name")) + with self.assertRaisesMessage(ValueError, expected_message): + ProxyMultiCountry.objects.bulk_create([ + ProxyMultiCountry(name="Fillory", iso_two_letter="FL"), + ]) + 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): State.objects.bulk_create([