[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 ed37f7e979
from master
This commit is contained in:
parent
dbd52f339c
commit
fa4b6482df
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`).
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -8,7 +8,8 @@ from django.db import IntegrityError, DatabaseError
|
|||
from django.utils.encoding import DjangoUnicodeDecodeError
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
|
||||
from .models import DefaultPerson, Person, ManualPrimaryKeyTest, Profile, Tag, Thing
|
||||
from .models import (DefaultPerson, Person, ManualPrimaryKeyTest, Profile,
|
||||
Tag, Thing, Publisher, Author, Book)
|
||||
|
||||
|
||||
class GetOrCreateTests(TestCase):
|
||||
|
@ -199,3 +200,54 @@ class UpdateOrCreateTests(TestCase):
|
|||
except IntegrityError:
|
||||
formatted_traceback = traceback.format_exc()
|
||||
self.assertIn('obj.save', formatted_traceback)
|
||||
|
||||
def test_create_with_related_manager(self):
|
||||
"""
|
||||
Should be able to use update_or_create from the related manager to
|
||||
create a book. Refs #23611.
|
||||
"""
|
||||
p = Publisher.objects.create(name="Acme Publishing")
|
||||
book, created = p.books.update_or_create(name="The Book of Ed & Fred")
|
||||
self.assertTrue(created)
|
||||
self.assertEqual(p.books.count(), 1)
|
||||
|
||||
def test_update_with_related_manager(self):
|
||||
"""
|
||||
Should be able to use update_or_create from the related manager to
|
||||
update a book. Refs #23611.
|
||||
"""
|
||||
p = Publisher.objects.create(name="Acme Publishing")
|
||||
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
|
||||
self.assertEqual(p.books.count(), 1)
|
||||
name = "The Book of Django"
|
||||
book, created = p.books.update_or_create(defaults={'name': name}, id=book.id)
|
||||
self.assertFalse(created)
|
||||
self.assertEqual(book.name, name)
|
||||
self.assertEqual(p.books.count(), 1)
|
||||
|
||||
def test_create_with_many(self):
|
||||
"""
|
||||
Should be able to use update_or_create from the m2m related manager to
|
||||
create a book. Refs #23611.
|
||||
"""
|
||||
p = Publisher.objects.create(name="Acme Publishing")
|
||||
author = Author.objects.create(name="Ted")
|
||||
book, created = author.books.update_or_create(name="The Book of Ed & Fred", publisher=p)
|
||||
self.assertTrue(created)
|
||||
self.assertEqual(author.books.count(), 1)
|
||||
|
||||
def test_update_with_many(self):
|
||||
"""
|
||||
Should be able to use update_or_create from the m2m related manager to
|
||||
update a book. Refs #23611.
|
||||
"""
|
||||
p = Publisher.objects.create(name="Acme Publishing")
|
||||
author = Author.objects.create(name="Ted")
|
||||
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
|
||||
book.authors.add(author)
|
||||
self.assertEqual(author.books.count(), 1)
|
||||
name = "The Book of Django"
|
||||
book, created = author.books.update_or_create(defaults={'name': name}, id=book.id)
|
||||
self.assertFalse(created)
|
||||
self.assertEqual(book.name, name)
|
||||
self.assertEqual(author.books.count(), 1)
|
||||
|
|
Loading…
Reference in New Issue