#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.

Reply via email to