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