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

Skip to content

Commit fa4b648

Browse files
aericsonloic
authored andcommitted
[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 ed37f7e from master
1 parent dbd52f3 commit fa4b648

File tree

6 files changed

+159
-5
lines changed

6 files changed

+159
-5
lines changed

django/contrib/contenttypes/fields.py

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

526+
def get_or_create(self, **kwargs):
527+
kwargs[self.content_type_field_name] = self.content_type
528+
kwargs[self.object_id_field_name] = self.pk_val
529+
db = router.db_for_write(self.model, instance=self.instance)
530+
return super(GenericRelatedObjectManager, self).using(db).get_or_create(**kwargs)
531+
get_or_create.alters_data = True
532+
533+
def update_or_create(self, **kwargs):
534+
kwargs[self.content_type_field_name] = self.content_type
535+
kwargs[self.object_id_field_name] = self.pk_val
536+
db = router.db_for_write(self.model, instance=self.instance)
537+
return super(GenericRelatedObjectManager, self).using(db).update_or_create(**kwargs)
538+
update_or_create.alters_data = True
539+
526540
return GenericRelatedObjectManager
527541

528542

django/db/models/fields/related.py

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

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

723+
def update_or_create(self, **kwargs):
724+
kwargs[rel_field.name] = self.instance
725+
db = router.db_for_write(self.model, instance=self.instance)
726+
return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
727+
update_or_create.alters_data = True
728+
725729
# remove() and clear() are only provided if the ForeignKey can have a value of null.
726730
if rel_field.null:
727731
def remove(self, *objs, **kwargs):
@@ -963,15 +967,24 @@ def create(self, **kwargs):
963967

964968
def get_or_create(self, **kwargs):
965969
db = router.db_for_write(self.instance.__class__, instance=self.instance)
966-
obj, created = \
967-
super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
970+
obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
968971
# We only need to add() if created because if we got an object back
969972
# from get() then the relationship already exists.
970973
if created:
971974
self.add(obj)
972975
return obj, created
973976
get_or_create.alters_data = True
974977

978+
def update_or_create(self, **kwargs):
979+
db = router.db_for_write(self.instance.__class__, instance=self.instance)
980+
obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
981+
# We only need to add() if created because if we got an object back
982+
# from get() then the relationship already exists.
983+
if created:
984+
self.add(obj)
985+
return obj, created
986+
update_or_create.alters_data = True
987+
975988
def _add_items(self, source_field_name, target_field_name, *objs):
976989
# source_field_name: the PK fieldname in join table for the source object
977990
# 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: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,64 @@ def test_query_content_type(self):
309309
TaggedItem.objects.get(content_object='')
310310

311311

312+
class GetOrCreateAndUpdateOrCreateTests(TestCase):
313+
"""
314+
GenericRelationsTests has changed significantly on master, this
315+
standalone TestCase is part of the backport for #23611.
316+
"""
317+
def setUp(self):
318+
self.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
319+
self.bacon.tags.create(tag="fatty")
320+
self.bacon.tags.create(tag="salty")
321+
322+
def test_generic_update_or_create_when_created(self):
323+
"""
324+
Should be able to use update_or_create from the generic related manager
325+
to create a tag. Refs #23611.
326+
"""
327+
count = self.bacon.tags.count()
328+
tag, created = self.bacon.tags.update_or_create(tag='stinky')
329+
self.assertTrue(created)
330+
self.assertEqual(count + 1, self.bacon.tags.count())
331+
332+
def test_generic_update_or_create_when_updated(self):
333+
"""
334+
Should be able to use update_or_create from the generic related manager
335+
to update a tag. Refs #23611.
336+
"""
337+
count = self.bacon.tags.count()
338+
tag = self.bacon.tags.create(tag='stinky')
339+
self.assertEqual(count + 1, self.bacon.tags.count())
340+
tag, created = self.bacon.tags.update_or_create(defaults={'tag': 'juicy'}, id=tag.id)
341+
self.assertFalse(created)
342+
self.assertEqual(count + 1, self.bacon.tags.count())
343+
self.assertEqual(tag.tag, 'juicy')
344+
345+
def test_generic_get_or_create_when_created(self):
346+
"""
347+
Should be able to use get_or_create from the generic related manager
348+
to create a tag. Refs #23611.
349+
"""
350+
count = self.bacon.tags.count()
351+
tag, created = self.bacon.tags.get_or_create(tag='stinky')
352+
self.assertTrue(created)
353+
self.assertEqual(count + 1, self.bacon.tags.count())
354+
355+
def test_generic_get_or_create_when_exists(self):
356+
"""
357+
Should be able to use get_or_create from the generic related manager
358+
to get a tag. Refs #23611.
359+
"""
360+
count = self.bacon.tags.count()
361+
tag = self.bacon.tags.create(tag="stinky")
362+
self.assertEqual(count + 1, self.bacon.tags.count())
363+
tag, created = self.bacon.tags.get_or_create(id=tag.id, defaults={'tag': 'juicy'})
364+
self.assertFalse(created)
365+
self.assertEqual(count + 1, self.bacon.tags.count())
366+
# shouldn't had changed the tag
367+
self.assertEqual(tag.tag, 'stinky')
368+
369+
312370
class CustomWidget(forms.TextInput):
313371
pass
314372

tests/get_or_create/models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ class Tag(models.Model):
4242
class Thing(models.Model):
4343
name = models.CharField(max_length=256)
4444
tags = models.ManyToManyField(Tag)
45+
46+
47+
class Publisher(models.Model):
48+
name = models.CharField(max_length=100)
49+
50+
51+
class Author(models.Model):
52+
name = models.CharField(max_length=100)
53+
54+
55+
class Book(models.Model):
56+
name = models.CharField(max_length=100)
57+
authors = models.ManyToManyField(Author, related_name='books')
58+
publisher = models.ForeignKey(Publisher, related_name='books', db_column="publisher_id_column")

tests/get_or_create/tests.py

Lines changed: 53 additions & 1 deletion
E377
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from django.utils.encoding import DjangoUnicodeDecodeError
99
from django.test import TestCase, TransactionTestCase
1010

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

1314

1415
class GetOrCreateTests(TestCase):
@@ -199,3 +200,54 @@ def test_error_contains_full_traceback(self):
199200
except IntegrityError:
200201
formatted_traceback = traceback.format_exc()
201202
self.assertIn('obj.save', formatted_traceback)
203+
204+
def test_create_with_related_manager(self):
205+
"""
206+
Should be able to use update_or_create from the related manager to
207+
create a book. Refs #23611.
208+
"""
209+
p = Publisher.objects.create(name="Acme Publishing")
210+
book, created = p.books.update_or_create(name="The Book of Ed & Fred")
211+
self.assertTrue(created)
212+
self.assertEqual(p.books.count(), 1)
213+
214+
def test_update_with_related_manager(self):
215+
"""
216+
Should be able to use update_or_create from the related manager to
217+
update a book. Refs #23611.
218+
"""
219+
p = Publisher.objects.create(name="Acme Publishing")
220+
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
221+
self.assertEqual(p.books.count(), 1)
222+
name = "The Book of Django"
223+
book, created = p.books.update_or_create(defaults={'name': name}, id=book.id)
224+
self.assertFalse(created)
225+
self.assertEqual(book.name, name)
226+
self.assertEqual(p.books.count(), 1)
227+
228+
def test_create_with_many(self):
229+
"""
230+
Should be able to use update_or_create from the m2m related manager to
231+
create a book. Refs #23611.
232+
"""
233+
p = Publisher.objects.create(name="Acme Publishing")
234+
author = Author.objects.create(name="Ted")
235+
book, created = author.books.update_or_create(name="The Book of Ed & Fred", publisher=p)
236+
self.assertTrue(created)
237+
self.assertEqual(author.books.count(), 1)
238+
239+
def test_update_with_many(self):
240+
"""
241+
Should be able to use update_or_create from the m2m related manager to
242+
update a book. Refs #23611.
243+
"""
244+
p = Publisher.objects.create(name="Acme Publishing")
245+
author = Author.objects.create(name="Ted")
246+
book = Book.objects.create(name="The Book of Ed & Fred", publisher=p)
247+
book.authors.add(author)
248+
self.assertEqual(author.books.count(), 1)
249+
name = "The Book of Django"
250+
book, created = author.books.update_or_create(defaults={'name': name}, id=book.id)
251+
self.assertFalse(created)
252+
self.assertEqual(book.name, name)
253+
self.assertEqual(author.books.count(), 1)

0 commit comments

Comments
 (0)
0