Commit fa4b6482 authored by André Ericson's avatar André Ericson Committed by Loic Bistuer
Browse files

[1.7.x] Fixed #23611 -- update_or_create failing from a related manager

Added update_or_create to RelatedManager, ManyRelatedManager and
GenericRelatedObjectManager.
Added missing get_or_create to GenericRelatedObjectManager.

Conflicts:
	tests/generic_relations/tests.py
	tests/get_or_create/tests.py

Backport of ed37f7e9 from master
parent dbd52f33
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -523,6 +523,20 @@ def create_generic_related_manager(superclass):
            return super(GenericRelatedObjectManager, self).using(db).create(**kwargs)
        create.alters_data = True

        def get_or_create(self, **kwargs):
            kwargs[self.content_type_field_name] = self.content_type
            kwargs[self.object_id_field_name] = self.pk_val
            db = router.db_for_write(self.model, instance=self.instance)
            return super(GenericRelatedObjectManager, self).using(db).get_or_create(**kwargs)
        get_or_create.alters_data = True

        def update_or_create(self, **kwargs):
            kwargs[self.content_type_field_name] = self.content_type
            kwargs[self.object_id_field_name] = self.pk_val
            db = router.db_for_write(self.model, instance=self.instance)
            return super(GenericRelatedObjectManager, self).using(db).update_or_create(**kwargs)
        update_or_create.alters_data = True

    return GenericRelatedObjectManager


+17 −4
Original line number Diff line number Diff line
@@ -715,13 +715,17 @@ def create_foreign_related_manager(superclass, rel_field, rel_model):
        create.alters_data = True

        def get_or_create(self, **kwargs):
            # Update kwargs with the related object that this
            # ForeignRelatedObjectsDescriptor knows about.
            kwargs[rel_field.name] = self.instance
            db = router.db_for_write(self.model, instance=self.instance)
            return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
        get_or_create.alters_data = True

        def update_or_create(self, **kwargs):
            kwargs[rel_field.name] = self.instance
            db = router.db_for_write(self.model, instance=self.instance)
            return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
        update_or_create.alters_data = True

        # remove() and clear() are only provided if the ForeignKey can have a value of null.
        if rel_field.null:
            def remove(self, *objs, **kwargs):
@@ -963,8 +967,7 @@ def create_many_related_manager(superclass, rel):

        def get_or_create(self, **kwargs):
            db = router.db_for_write(self.instance.__class__, instance=self.instance)
            obj, created = \
                super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
            obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
            # We only need to add() if created because if we got an object back
            # from get() then the relationship already exists.
            if created:
@@ -972,6 +975,16 @@ def create_many_related_manager(superclass, rel):
            return obj, created
        get_or_create.alters_data = True

        def update_or_create(self, **kwargs):
            db = router.db_for_write(self.instance.__class__, instance=self.instance)
            obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
            # We only need to add() if created because if we got an object back
            # from get() then the relationship already exists.
            if created:
                self.add(obj)
            return obj, created
        update_or_create.alters_data = True

        def _add_items(self, source_field_name, target_field_name, *objs):
            # source_field_name: the PK fieldname in join table for the source object
            # target_field_name: the PK fieldname in join table for the target object
+3 −0
Original line number Diff line number Diff line
@@ -100,3 +100,6 @@ Bugfixes

* Fixed ``UnicodeDecodeError`` crash in ``AdminEmailHandler`` with non-ASCII
  characters in the request (:ticket:`23593`).

* Fixed missing ``get_or_create`` and ``update_or_create`` on related managers
  causing ``IntegrityError`` (:ticket:`23611`).
+58 −0
Original line number Diff line number Diff line
@@ -309,6 +309,64 @@ class GenericRelationsTests(TestCase):
            TaggedItem.objects.get(content_object='')


class GetOrCreateAndUpdateOrCreateTests(TestCase):
    """
    GenericRelationsTests has changed significantly on master, this
    standalone TestCase is part of the backport for #23611.
    """
    def setUp(self):
        self.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
        self.bacon.tags.create(tag="fatty")
        self.bacon.tags.create(tag="salty")

    def test_generic_update_or_create_when_created(self):
        """
        Should be able to use update_or_create from the generic related manager
        to create a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag, created = self.bacon.tags.update_or_create(tag='stinky')
        self.assertTrue(created)
        self.assertEqual(count + 1, self.bacon.tags.count())

    def test_generic_update_or_create_when_updated(self):
        """
        Should be able to use update_or_create from the generic related manager
        to update a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag = self.bacon.tags.create(tag='stinky')
        self.assertEqual(count + 1, self.bacon.tags.count())
        tag, created = self.bacon.tags.update_or_create(defaults={'tag': 'juicy'}, id=tag.id)
        self.assertFalse(created)
        self.assertEqual(count + 1, self.bacon.tags.count())
        self.assertEqual(tag.tag, 'juicy')

    def test_generic_get_or_create_when_created(self):
        """
        Should be able to use get_or_create from the generic related manager
        to create a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag, created = self.bacon.tags.get_or_create(tag='stinky')
        self.assertTrue(created)
        self.assertEqual(count + 1, self.bacon.tags.count())

    def test_generic_get_or_create_when_exists(self):
        """
        Should be able to use get_or_create from the generic related manager
        to get a tag. Refs #23611.
        """
        count = self.bacon.tags.count()
        tag = self.bacon.tags.create(tag="stinky")
        self.assertEqual(count + 1, self.bacon.tags.count())
        tag, created = self.bacon.tags.get_or_create(id=tag.id, defaults={'tag': 'juicy'})
        self.assertFalse(created)
        self.assertEqual(count + 1, self.bacon.tags.count())
        # shouldn't had changed the tag
        self.assertEqual(tag.tag, 'stinky')


class CustomWidget(forms.TextInput):
    pass

+14 −0
Original line number Diff line number Diff line
@@ -42,3 +42,17 @@ class Tag(models.Model):
class Thing(models.Model):
    name = models.CharField(max_length=256)
    tags = models.ManyToManyField(Tag)


class Publisher(models.Model):
    name = models.CharField(max_length=100)


class Author(models.Model):
    name = models.CharField(max_length=100)


class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author, related_name='books')
    publisher = models.ForeignKey(Publisher, related_name='books', db_column="publisher_id_column")
Loading