summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndré Ericson <de.ericson@gmail.com>2014-10-08 03:27:31 +0700
committerLoic Bistuer <loic.bistuer@gmail.com>2014-10-08 04:55:47 +0700
commitfa4b6482df08d308fe88044b8c8bf981c6225fb8 (patch)
treec988171c48c77266e42a09d483681796ec07c806
parentdbd52f339cdae7fb57526cb86d45792438799e5b (diff)
[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
-rw-r--r--django/contrib/contenttypes/fields.py14
-rw-r--r--django/db/models/fields/related.py21
-rw-r--r--docs/releases/1.7.1.txt3
-rw-r--r--tests/generic_relations/tests.py58
-rw-r--r--tests/get_or_create/models.py14
-rw-r--r--tests/get_or_create/tests.py54
6 files changed, 159 insertions, 5 deletions
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index b70a947ddf..a25aa64cb9 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -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
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 868f43090d..d87a9229ef 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -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
diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt
index 1604e75da8..6511074542 100644
--- a/docs/releases/1.7.1.txt
+++ b/docs/releases/1.7.1.txt
@@ -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`).
diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py
index 3e05bb7225..3508a1096b 100644
--- a/tests/generic_relations/tests.py
+++ b/tests/generic_relations/tests.py
@@ -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
diff --git a/tests/get_or_create/models.py b/tests/get_or_create/models.py
index 1a85de2e74..915243a9c6 100644
--- a/tests/get_or_create/models.py
+++ b/tests/get_or_create/models.py
@@ -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")
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index 1ddb2447fe..3a9a5a5a42 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -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)