Django: переопределение абстрактного класса с общим отношением, динамически ограничивающим выбор ContentType?
<<<Следуя текущей документации https://docs.djangoproject.com/en/5.0/ref/contrib/contenttypes/#generic-relations, я создал GenericReferModel
, который является абстрактным классом, определяющим общее отношение к одной или нескольким моделям. Пока все хорошо. Теперь я хотел бы ограничить ContentType
выбор, используя limit_choices_to
атрибут models.ForeignKey
.
from typing import Any
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import gettext_lazy as _
class GenericReferModel(models.Model):
refer_type_choices: models.Q | dict[str, Any] | None = None
# https://docs.djangoproject.com/en/5.0/ref/contrib/contenttypes/#generic-relations
refer_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
null=True,
blank=True,
limit_choices_to=refer_type_choices,
verbose_name=_("refer type"),
)
refer_id = models.PositiveIntegerField(
_("refer id"),
null=True,
blank=True,
)
refer = GenericForeignKey("refer_type", "refer_id")
class Meta:
abstract = True
indexes = [
models.Index(fields=["refer_type", "refer_id"]),
]
class Chat(GenericReferModel):
nome = models.CharField(_("nome"), max_length=255)
refer_type_choices = (
models.Q(app_label="core", model="studio")
)
class Meta:
verbose_name = _("chat")
verbose_name_plural = _("chat")
Очевидно, что переопределение refer_type_choices
не работает, и оно всегда считается значением по умолчанию для абстрактного класса. Есть ли способ добиться динамического назначения выбора для каждого подкласса?
Вдохновляясь Django Model Utils - StatusField, я определил ReferTypeField
, который опционально принимает атрибут класса refer_type_choices
и устанавливает его в self.remote_field.limit_choices_to
во время подготовки класса. Я не знаю, правильный ли это подход и есть ли у него скрытые побочные эффекты, но пока что он работает хорошо.
from django.db import models
DEFAULT_REFER_TYPE_CHOICES_NAME = "refer_type_choices"
class ReferTypeField(models.ForeignKey):
def __init__(
self,
*args,
refer_type_choices_name=DEFAULT_REFER_TYPE_CHOICES_NAME,
**kwargs,
):
self.refer_type_choices_name = refer_type_choices_name
super().__init__(*args, **kwargs)
def prepare_class(self, sender, **kwargs):
if not sender._meta.abstract: # noqa: SLF001
if hasattr(sender, self.refer_type_choices_name):
limit_choices = getattr(sender, self.refer_type_choices_name)
self.remote_field.limit_choices_to = limit_choices
def contribute_to_class(self, cls, name, *args, **kwargs):
models.signals.class_prepared.connect(self.prepare_class, sender=cls)
super().contribute_to_class(cls, name, *args, **kwargs)
class GenericReferModel(models.Model):
# https://docs.djangoproject.com/en/5.0/ref/contrib/contenttypes/#generic-relations
refer_type = ReferTypeField(
ContentType,
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name=_("refer type"),
)
refer_id = models.PositiveIntegerField(
_("refer id"),
null=True,
blank=True,
)
refer = GenericForeignKey("refer_type", "refer_id")
class Meta:
abstract = True
indexes = [
models.Index(fields=["refer_type", "refer_id"]),
]
class Chat(GenericReferModel):
nome = models.CharField(_("nome"), max_length=255)
refer_type_choices = (
models.Q(app_label="core", model="studio")
)
class Meta(GenericReferModel.Meta):
verbose_name = _("chat")
verbose_name_plural = _("chat")