Commit 65f9e0af authored by Pablo Recio's avatar Pablo Recio
Browse files

Fixes #18896. Add tests verifying that you can get IntegrityErrors using...

Fixes #18896. Add tests verifying that you can get IntegrityErrors using get_or_create through relations like M2M, and it also adds a note into the documentation warning about it
parent d34b1c29
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -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
~~~~~~~~~~~

+9 −0
Original line number Diff line number Diff line
@@ -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)
+26 −1
Original line number Diff line number Diff line
@@ -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')