I am using Django 3.2
I have come across a strange behaviour which appears to be a bug; Fields used in constraints defined in a ABC are not inherited by child classes.
class BaseFoo(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,related_name='%(class)s_content_type')
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
user = models.ForeignKey(User, blank=False, null=False, on_delete=models.CASCADE,related_name='%(class)s')
created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
constraints = [
models.UniqueConstraint(fields=['content_object', 'user'], name="'%(class)s_unique"),
]
class Foo(BaseFoo):
class Meta(BaseFoo.Meta):
abstract = False
No problem when I makemigrations
, but, When I attempt to migrate the schema (`python manage.py migrate), I get the following error:
myapp.Foo: (models.E016) 'constraints' refers to field 'content_object' which is not local to model 'Foo'. HINT: This issue may be caused by multi-table inheritance.
Why is Foo not inheriting the fields clearly defined in the parent ABC? - and how do I fix this (assuming it's not a bug)?
1. You can't use GenericForeignKey
in UniqueConstraint
, because a GenericForeignKey isn’t a normal field object. Instead in UniqueConstraint
use content_type
and object_id
, this is equivalent to content_object
:
class Meta:
abstract = True
constraints = [
models.UniqueConstraint(
fields=['content_type', 'object_id', 'user'],
name="%(app_label)s_%(class)s_user_unique"),
]
See Django Docs:
Due to the way
GenericForeignKey
is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because aGenericForeignKey
isn’t a normal field object.Likewise,
GenericForeignKey
does not appear in ModelForms.
2. According to Django Docs in abstract base classes related_name
must contain '%(app_label)s' and '%(class)s'. And you should always specify a unique reverse name and query name for the field.
content_type = models.ForeignKey(
ContentType, on_delete=models.CASCADE,
related_name='%(app_label)s_%(class)s_content_types',
related_query_name='%(app_label)s_%(class)s_content_type',
)
user = models.ForeignKey(
User, blank=False, null=False,
on_delete=models.CASCADE,
related_name='%(app_label)s_%(class)s_users',
related_query_name='%(app_label)s_%(class)s_user',
)
If you are using related_name or related_query_name on a ForeignKey or ManyToManyField, you must always specify a unique reverse name and query name for the field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including related_name and related_query_name) each time.
To work around this problem, when you are using related_name or related_query_name in an abstract base class (only), part of the value should contain '%(app_label)s' and '%(class)s'.