Opened 5 days ago

Last modified 38 hours ago

#36295 assigned Bug

Unable to Override GenericForeignKey in Inherited Abstract Class

Reported by: Gagan Deep Owned by: Ahmed Nassar
Component: contrib.contenttypes Version: 5.2
Severity: Normal Keywords:
Cc: Gagan Deep Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Attempting to override a GenericForeignKey field in an inherited abstract model does not work as expected. According to the Django documentation, it should be possible to hide a field inherited from an abstract model by setting it to None.

However, this does not seem to work for GenericForeignKey.

I have created a Django project for quick testing.

Steps to replicate:

  1. Define an abstract model with a GenericForeignKey field.
  2. Create another abstract model that inherits from the first and attempt to override the GenericForeignKey field by setting it to None.
  3. Define a concrete model that inherits from the second abstract model.

Code Example

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models


class AbstractBaseModel(models.Model):
    content_type = models.ForeignKey(
        ContentType, on_delete=models.SET_NULL, null=True, blank=True
    )
    object_id = models.PositiveIntegerField(null=True, blank=True)
    related_object = GenericForeignKey("content_type", "object_id")
    description = models.TextField(null=True, blank=True)

    class Meta:
        abstract = True


class AbstractDerivedModel(AbstractBaseModel):
    related_object = None  # Override GenericForeignKey
    description = None

    class Meta:
        abstract = True


class ConcreteEntity(AbstractDerivedModel):
    name = models.CharField(max_length=255)

    class Meta:
        abstract = False

Expected Behavior:

The related_object field in AbstractDerivedModel should override the GenericForeignKey in AbstractBaseModel, making it effectively absent in ConcreteEntity.

Actual Behavior:

Django still considers the GenericForeignKey field from AbstractBaseModel when defining ConcreteEntity, leading to unexpected behavior.

Additional Notes:

According to the ticket's flags, the next step(s) to move this issue forward are:

  • To provide a patch by sending a pull request. Claim the ticket when you start working so that someone else doesn't duplicate effort. Before sending a pull request, review your work against the patch review checklist. Check the "Has patch" flag on the ticket after sending a pull request and include a link to the pull request in the ticket comment when making that update. The usual format is: [https://github.com/django/django/pull/#### PR].

Change History (5)

comment:1 by Sarah Boyce, 5 days ago

Triage Stage: UnreviewedAccepted

Thank you!
Using the models as defined above, I was able to replicate

  • TabularUnified tests/generic_relations/tests.py

    diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py
    index e0c6fe2db7..c4163f3c87 100644
    a b  
    11from django.contrib.contenttypes.models import ContentType
    22from django.contrib.contenttypes.prefetch import GenericPrefetch
    3 from django.core.exceptions import FieldError
     3from django.core.exceptions import FieldError, FieldDoesNotExist
    44from django.db.models import Q, prefetch_related_objects
    55from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
    66
    from .models import (  
    99    Animal,
    1010    Carrot,
    1111    Comparison,
     12    ConcreteEntity,
    1213    ConcreteRelatedModel,
    1314    ForConcreteModelModel,
    1415    ForProxyModelModel,
    class GenericRelationsTests(TestCase):  
    780781                self.platypus.latin_name,
    781782            )
    782783
     784    def test_overwriting_generic_fk(self):
     785        with self.assertRaises(FieldDoesNotExist):
     786            ConcreteEntity._meta.get_field('description')
     787        with self.assertRaises(FieldDoesNotExist):
     788            ConcreteEntity._meta.get_field('related_object')
     789
    783790
    784791class ProxyRelatedModelTest(TestCase):

comment:2 by Sarah Boyce, 5 days ago

Component: Database layer (models, ORM)contrib.contenttypes

comment:3 by Gagan Deep, 5 days ago

Hello Sarah! Can I get some pointers how can I approach to solve this issue? I would like to attempt a fix.

comment:4 by Senthil Kumar, 4 days ago

[Ignore this fix, unit tests were failing. My bad, for rushing it]

This is my first time working with the Django source code. After reviewing the codebase, I believe the fix below might address the issue. However, I'm unsure whether this is the expected behavior that requires a documentation update or if it's an actual bug.

File db/models/base.py -- line 348

https://github.com/django/django/blob/8ebdd37a0b1755842baae3bd34d388156ad4bf53/django/db/models/base.py#L348

if field.column is not None:  # the exact fix
        field = copy.deepcopy(field)
        if not base._meta.abstract:
               field.mti_inherited = True
               new_class.add_to_class(field.name, field)

Apologies, but I'm unsure how to present this from the codebase. It would be helpful if someone could guide me on how to share the diff, similar to the comments above. Thanks!

Last edited 4 days ago by Senthil Kumar (previous) (diff)

comment:5 by Ahmed Nassar, 38 hours ago

Owner: set to Ahmed Nassar
Status: newassigned
Note: See TracTickets for help on using tickets.
Back to Top