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
|
||||
|
||||
.. 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
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -28,3 +28,12 @@ class ManualPrimaryKeyTest(models.Model):
|
|||
|
||||
class Profile(models.Model):
|
||||
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.test import TestCase, TransactionTestCase
|
||||
|
||||
from .models import Person, ManualPrimaryKeyTest, Profile
|
||||
from .models import Person, ManualPrimaryKeyTest, Profile, Tag, Thing
|
||||
|
||||
|
||||
class GetOrCreateTests(TestCase):
|
||||
|
@ -77,3 +77,28 @@ class GetOrCreateTransactionTests(TransactionTestCase):
|
|||
pass
|
||||
else:
|
||||
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