8000 Fixed #23611 -- update_or_create failing from a related manager · alex-python/django@ed37f7e · GitHub
[go: up one dir, main page]

Skip to content

Commit ed37f7e

Browse files
committed
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.
1 parent c1ef234 commit ed37f7e

File tree

5 files changed

+134
-5
lines changed

5 files changed

+134
-5
lines changed

django/contrib/contenttypes/fields.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,20 @@ def create(self, **kwargs):
537537
return super(GenericRelatedObjectManager, self).using(db).create(**kwargs)
538538
create.alters_data = True
539539

540+
def get_or_create(self, **kwargs):
541+
kwargs[self.content_type_field_name] = self.content_type
542+
kwargs[self.object_id_field_name] = self.pk_val
543+
db = router.db_for_write(self.model, instance=self.instance)
544+
return super(GenericRelatedObjectManager, self).using(db).get_or_create(**kwargs)
545+
get_or_create.alters_data = True
546+
547+
def update_or_create(self, **kwargs):
548+
kwargs[self.content_type_field_name] = self.content_type
549+
kwargs[self.object_id_field_name] = self.pk_val
550+
db = router.db_for_write(self.model, instance=self.instance)
551+
return super(GenericRelatedObjectManager, self).using(db).update_or_create(**kwargs)
552+
update_or_create.alters_data = True
553+
540554
return GenericRelatedObjectManager
541555

542556

django/db/models/fields/related.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,17 @@ def create(self, **kwargs):
733733
create.alters_data = True
734734

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

741+
def update_or_create(self, **kwargs):
742+
kwargs[rel_field.name] = self.instance
743+
db = router.db_for_write(self.model, instance=self.instance)
744+
return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
745+
update_or_create.alters_data = True
746+
743747
# remove() and clear() are only provided if the ForeignKey can have a value of null.
744748
if rel_field.null:
745749
def remove(self, *objs, **kwargs):
@@ -999,15 +1003,24 @@ def create(self, **kwargs):
9991003

10001004
def get_or_create(self, **kwargs):
10011005
db = router.db_for_write(self.instance.__class__, instance=self.instance)
1002-
obj, created = \
1003-
super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
1006+
obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
10041007
# We only need to add() if created because if we got an object back
10051008
# from get() then the relationship already exists.
10061009
if created:
10071010
self.add(obj)
10081011
return obj, created
10091012
get_or_create.alters_data = True
10101013

1014+
def update_or_create(self, **kwargs):
1015+
db = router.db_for_write(self.instance.__class__, instance=self.instance)
1016+
obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
1017+
# We only need to add() if created because if we got an object back
1018+
# from get() then the relationship already exists.
1019+
if created:
1020+
self.add(obj)
1021+
return obj, created
1022+
update_or_create.alters_data = True
1023+
10111024
def _add_items(self, source_field_name, target_field_name, *objs):
10121025
# source_field_name: the PK fieldname in join table for the source object
10131026
# target_field_name: the PK fieldname in join table for the target object

docs/releases/1.7.1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,6 @@ Bugfixes
100100

101101
* Fixed ``UnicodeDecodeError`` crash in ``AdminEmailHandler`` with non-ASCII
102102
characters in the request (:ticket:`23593`).
103+
104+
* Fixed missing ``get_or_create`` and ``update_or_create`` on related managers
105+
causing ``IntegrityError`` (:ticket:`23611`).

tests/generic_relations/tests.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,54 @@ def setUp(self):
3535
obj.tag, obj.content_type.model_class(), obj.object_id
3636
)
3737

38+
def test_generic_update_or_create_when_created(self):
39+
"""
40+
Should be able to use update_or_create from the generic related manager
41+
to create a tag. Refs #23611.
42+
"""
43+
count = self.bacon.tags.count()
44+
tag, created = self.bacon.tags.update_or_create(tag='stinky')
45+
self.assertTrue(created)
46+
self.assertEqual(count + 1, self.bacon.tags.count())
47+
48+
def test_generic_update_or_create_when_updated(self):
49+
"""
50+
Should be able to use update_or_create from the generic related manager
51+
to update a tag. Refs #23611.
52+
"""
53+
count = self.bacon.tags.count()
54+
tag = self.bacon.tags.create(tag='stinky')
55+
self.assertEqual(count + 1, self.bacon.tags.count())
56+
tag, created = self.bacon.tags.update_or_create(defaults={'tag': 'juicy'}, id=tag.id)
57+
self.assertFalse(created)
58+
self.assertEqual(count + 1, self.bacon.tags.count())
59+
self.assertEqual(tag.tag, 'juicy')
60+
61+
def test_generic_get_or_create_when_created(self):
62+
"""
63+
Should be able to use get_or_create from the generic related manager
64+
to create a tag. Refs #23611.
65+
"""
66+
count = self.bacon.tags.count()
67+
tag, created = self.bacon.tags.get_or_create(tag='stinky')
68+
self.assertTrue(created)
69+
self.assertEqual(count + 1, self.bacon.tags.count())
70+
71+
def test_generic_get_or_create_when_exists(self):
72+
"""
73+
Should be able to use get_or_create from the generic related manager
74+
to get a tag. Refs #23611.
75+
"""
76+
count = self.bacon.tags.count()
77+
tag = self.bacon.tags.create(tag="stinky")
78+
self.assertEqual(count + 1, self.bacon.tags.count())
79+
tag, created = self.bacon.tags.get_or_create(id=tag.id, defaults={'tag': 'juicy'})
80+
self.assertFalse(created)
81+
self.assertEqual(count + 1, self.bacon.tags.count())
82+
# shouldn't had changed the tag
83+
self.assertEqual(tag.tag, 'stinky')
84+
85+
3886
def test_generic_relations_m2m_mimic(self):
3987
"""
4088
Objects with declared GenericRelations can be tagged directly -- the

tests/get_or_create/tests.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.test import TestCase, TransactionTestCase
1010

1111
from .models import (DefaultPerson, Person, ManualPrimaryKeyTest, Profile,
12-
Tag, Thing, Publisher, Author)
12+
Tag, Thing, Publisher, Author, Book)
1313

1414

1515
class GetOrCreateTests(TestCase):
@@ -239,6 +239,57 @@ def test_error_contains_full_traceback(self):
239239
formatted_traceback = traceback.format_exc()
240240
self.assertIn('obj.save', formatted_traceback)
241241

242+
def test_create_with_related_manager(self):
243+
"""
244+
Should be able to use update_or_create from the related manager to
245+
create a book. Refs #23611.
246+
"""
247+
p = Publisher.objects.create(name="Acme Publishing")
248+
book, created = p.books.update_or_create(name="The Book of Ed & Fred")
249+
self.assertTrue(created)
250+
self.assertEqual(p.books.count(), 1)
251+
252+
def test_update_with_related_manager(self):
253+
"""
254+
Should be able to use update_or_create from the related manager to
255+
update a book. Refs #23611.
256+
"""
257+
p = Publisher.objects.create(name="Acme Publishing")
258+
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
259+
self.assertEqual(p.books.count(), 1)
260+
name = "The Book of Django"
261+
book, created = p.books.update_or_create(defaults={'name': name}, id=book.id)
262+
self.assertFalse(created)
263+
self.assertEqual(book.name, name)
264+
self.assertEqual(p.books.count(), 1)
265+
266+
def test_create_with_many(self):
267+
"""
268+
Should be able to use update_or_create from the m2m related manager to
269+
create a book. Refs #23611.
270+
"""
271+
p = Publisher.objects.create(name="Acme Publishing")
272+
author = Author.objects.create(name="Ted")
273+
book, created = author.books.update_or_create(name="The Book of Ed & Fred", publisher=p)
274+
self.assertTrue(created)
275+
self.assertEqual(author.books.count(), 1)
276+
277+
def test_update_with_many(self):
278+
"""
279+
Should be able to use update_or_create from the m2m related manager to
280+
update a book. Refs #23611.
281+
"""
282+
p = Publisher.objects.create(name="Acme Publishing")
283+
author = Author.objects.create(name="Ted")
284+
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
285+
book.authors.add(author)
286+
self.assertEqual(author.books.count(), 1)
287+
name = "The Book of Django"
288+
book, created = author.books.update_or_create(defaults={'name': name}, id=book.id)
289+
self.assertFalse(created)
290+
self.assertEqual(book.name, name)
291+
self.assertEqual(author.books.count(), 1)
292+
242293
def test_related(self):
243294
p = Publisher.objects.create(name="Acme Publishing")
244295
# Create a book through the publisher.

0 commit comments

Comments
 (0)
0