#34401: Inconsistent behavior for refresh_from_db() with GenericForeignKey -------------------------------------+------------------------------------- Reporter: François Dupayrat | Owner: nobody Type: Bug | Status: new Component: | Version: 4.1 contrib.contenttypes | Severity: Normal | Resolution: Keywords: | Triage Stage: | Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+------------------------------------- Description changed by François Dupayrat:
Old description: > Since Django 4, `refresh_from_db`exhibit an inconsistent behavior for > `GenericForeignKey` fields: sometime, the object field is properly > refreshed, and sometime it isn't. That issue has been introduced with > Django 4 and I've traced it back to this ticket: > https://code.djangoproject.com/ticket/33008 > and in particular those 2 lines in ` > django/contrib/contenttypes/fields.py`: > ``` > if rel_obj is None and self.is_cached(instance): > return rel_obj > ``` > > With those 2 lines removed, the bug doesn't reproduce. I'm unsure if this > is an acceptable solution for Django though. > > Here is a minimal example to reproduce this bug. > > models.py: > ``` > from django.contrib.contenttypes.fields import GenericForeignKey > from django.contrib.contenttypes.models import ContentType > from django.db import models > > class FirstType(models.Model): > pass > > class SecondType(models.Model): > pass > > class FirstOrSecond(models.Model): > > association_type = models.ForeignKey(ContentType, blank=True, > null=True, on_delete=models.SET_NULL, limit_choices_to=( > models.Q(app_label='generic_foreign_key_type', > model='firsttype') | > models.Q(app_label='generic_foreign_key_type', > model='secondtype') > )) > associated_to_id = models.PositiveIntegerField(blank=True, null=True, > db_index=True) > associated_to = GenericForeignKey('association_type', > 'associated_to_id') > > def change_association_to(obj_id, target): > matching_obj = FirstOrSecond.objects.filter(id=obj_id).first() > if matching_obj: > matching_obj.associated_to = target > matching_obj.save() > ``` > > tests.py: > ``` > from django.test import TestCase > from .models import * > > class GenericForeignKeysTests(TestCase): > def setUp(self): > self.first_or_second = FirstOrSecond.objects.create() > self.first = FirstType.objects.create() > self.second = SecondType.objects.create() > > def test_refresh_from_db(self): > change_association_to(self.first_or_second.id, self.first) > self.first_or_second.refresh_from_db() > self.assertEqual(self.first_or_second.associated_to, self.first) > > change_association_to(self.first_or_second.id, None) > self.first_or_second.refresh_from_db() > self.assertEqual(self.first_or_second.associated_to, None) > > change_association_to(self.first_or_second.id, self.first) > self.first_or_second.refresh_from_db() > self.assertEqual(self.first_or_second.associated_to, self.first) > ``` > > The last assertion fails because of this bug. New description: Since Django 4, `refresh_from_db`exhibit an inconsistent behavior for `GenericForeignKey` fields: sometime, the object field is properly refreshed, and sometime it isn't. That issue has been introduced with Django 4 and I've traced it back to this ticket: https://code.djangoproject.com/ticket/33008 and in particular those 2 lines in ` django/contrib/contenttypes/fields.py`: {{{ if rel_obj is None and self.is_cached(instance): return rel_obj }}} With those 2 lines removed, the bug doesn't reproduce. I'm unsure if this is an acceptable solution for Django though. Here is a minimal example to reproduce this bug. models.py: {{{ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models class FirstType(models.Model): pass class SecondType(models.Model): pass class FirstOrSecond(models.Model): association_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.SET_NULL, limit_choices_to=( models.Q(app_label='generic_foreign_key_type', model='firsttype') | models.Q(app_label='generic_foreign_key_type', model='secondtype') )) associated_to_id = models.PositiveIntegerField(blank=True, null=True, db_index=True) associated_to = GenericForeignKey('association_type', 'associated_to_id') def change_association_to(obj_id, target): matching_obj = FirstOrSecond.objects.filter(id=obj_id).first() if matching_obj: matching_obj.associated_to = target matching_obj.save() }}} tests.py: {{{ from django.test import TestCase from .models import * class GenericForeignKeysTests(TestCase): def setUp(self): self.first_or_second = FirstOrSecond.objects.create() self.first = FirstType.objects.create() self.second = SecondType.objects.create() def test_refresh_from_db(self): change_association_to(self.first_or_second.id, self.first) self.first_or_second.refresh_from_db() self.assertEqual(self.first_or_second.associated_to, self.first) change_association_to(self.first_or_second.id, None) self.first_or_second.refresh_from_db() self.assertEqual(self.first_or_second.associated_to, None) change_association_to(self.first_or_second.id, self.first) self.first_or_second.refresh_from_db() self.assertEqual(self.first_or_second.associated_to, self.first) }}} The last assertion fails because of this bug. -- -- Ticket URL: <https://code.djangoproject.com/ticket/34401#comment:1> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/01070186cbeff8e2-ec2876c3-bba3-4197-af77-5cf7fcb82efe-000000%40eu-central-1.amazonses.com.