Merge pull request #1164 from pyriku/get_or_create_through_m2m
Integrity problems when using get_or_create through M2M
This commit is contained in:
commit
c28b795804
|
@ -1409,6 +1409,41 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
|
||||||
|
|
||||||
.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
You can use ``get_or_create()`` through :class:`~django.db.models.ManyToManyField`
|
||||||
|
attributes and reverse relations. In that case you will restrict the queries
|
||||||
|
inside the context of that relation. That could lead you to some integrity
|
||||||
|
problems if you don't use it consistently.
|
||||||
|
|
||||||
|
Being the following models::
|
||||||
|
|
||||||
|
class Chapter(models.Model):
|
||||||
|
title = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
|
class Book(models.Model):
|
||||||
|
title = models.CharField(max_length=256)
|
||||||
|
chapters = models.ManyToManyField(Chapter)
|
||||||
|
|
||||||
|
You can use ``get_or_create()`` through Book's chapters field, but it only
|
||||||
|
fetches inside the context of that book::
|
||||||
|
|
||||||
|
>>> book = Book.objects.create(title="Ulysses")
|
||||||
|
>>> book.chapters.get_or_create(title="Telemachus")
|
||||||
|
(<Chapter: Telemachus>, True)
|
||||||
|
>>> book.chapters.get_or_create(title="Telemachus")
|
||||||
|
(<Chapter: Telemachus>, False)
|
||||||
|
>>> Chapter.objects.create(title="Chapter 1")
|
||||||
|
<Chapter: Chapter 1>
|
||||||
|
>>> book.chapters.get_or_create(title="Chapter 1")
|
||||||
|
# Raises IntegrityError
|
||||||
|
|
||||||
|
This is happening because it's trying to get or create "Chapter 1" through the
|
||||||
|
book "Ulysses", but it can't do any of them: the relation can't fetch that
|
||||||
|
chapter because it isn't related to that book, but it can't create it either
|
||||||
|
because ``title`` field should be unique.
|
||||||
|
|
||||||
|
|
||||||
bulk_create
|
bulk_create
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -28,3 +28,12 @@ class ManualPrimaryKeyTest(models.Model):
|
||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
person = models.ForeignKey(Person, primary_key=True)
|
person = models.ForeignKey(Person, primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(models.Model):
|
||||||
|
text = models.CharField(max_length=256, unique=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Thing(models.Model):
|
||||||
|
name = models.CharField(max_length=256)
|
||||||
|
tags = models.ManyToManyField(Tag)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import traceback
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.test import TestCase, TransactionTestCase
|
from django.test import TestCase, TransactionTestCase
|
||||||
|
|
||||||
from .models import Person, ManualPrimaryKeyTest, Profile
|
from .models import Person, ManualPrimaryKeyTest, Profile, Tag, Thing
|
||||||
|
|
||||||
|
|
||||||
class GetOrCreateTests(TestCase):
|
class GetOrCreateTests(TestCase):
|
||||||
|
@ -77,3 +77,28 @@ class GetOrCreateTransactionTests(TransactionTestCase):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.skipTest("This backend does not support integrity checks.")
|
self.skipTest("This backend does not support integrity checks.")
|
||||||
|
|
||||||
|
|
||||||
|
class GetOrCreateThroughManyToMany(TestCase):
|
||||||
|
|
||||||
|
def test_get_get_or_create(self):
|
||||||
|
tag = Tag.objects.create(text='foo')
|
||||||
|
a_thing = Thing.objects.create(name='a')
|
||||||
|
a_thing.tags.add(tag)
|
||||||
|
obj, created = a_thing.tags.get_or_create(text='foo')
|
||||||
|
|
||||||
|
self.assertFalse(created)
|
||||||
|
self.assertEqual(obj.pk, tag.pk)
|
||||||
|
|
||||||
|
def test_create_get_or_create(self):
|
||||||
|
a_thing = Thing.objects.create(name='a')
|
||||||
|
obj, created = a_thing.tags.get_or_create(text='foo')
|
||||||
|
|
||||||
|
self.assertTrue(created)
|
||||||
|
self.assertEqual(obj.text, 'foo')
|
||||||
|
self.assertIn(obj, a_thing.tags.all())
|
||||||
|
|
||||||
|
def test_something(self):
|
||||||
|
Tag.objects.create(text='foo')
|
||||||
|
a_thing = Thing.objects.create(name='a')
|
||||||
|
self.assertRaises(IntegrityError, a_thing.tags.get_or_create, text='foo')
|
||||||
|
|
Loading…
Reference in New Issue