﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
34401	Inconsistent behavior for refresh_from_db() with GenericForeignKey	François Dupayrat	nobody	"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.
"	Bug	closed	contrib.contenttypes	4.1	Normal	duplicate			Unreviewed	0	0	0	0	0	0
