#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 [email protected].
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