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")
Вернуться на верх