Динамическое определение атрибутов модели / полей базы данных в Django

Я хотел бы определить модель Django, выглядящую следующим образом:

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

class Foo(models.Model):
    object_id_1 = models.UUIDField()
    content_type_1 = models.ForeignKey(ContentType)
    object_id_2 = models.UUIDField()
    content_type_3 = models.ForeignKey(ContentType)
    object_id_3 = models.UUIDField()
    content_type_3 = models.ForeignKey(ContentType)
    # etc.
    d1 = GenericForeignKey("content_type_1", "object_id_1")
    d2 = GenericForeignKey("content_type_2", "object_id_2")
    d3 = GenericForeignKey("content_type_3", "object_id_3")
    # etc.

Очевидно, что чем больше измерений (d означает "dimension") я добавляю, тем беспорядочнее все становится: это не очень DRY, тем более что в этом примере я убрал множество вариантов полей.

Есть ли способ динамически определить эти атрибуты модели, например, в цикле for, не используя eval? Если нет, то каким будет самый чистый и безопасный способ прибегнуть к eval здесь?

Я нашел очень похожий вопрос на Stackoverflow здесь , но он более конкретный и не имеет общего ответа.

Мое решение

Я не совсем доволен этой частью кода, потому что это означает полагаться на недокументированные внутренности, которые не имеют никакой гарантии стабильности, но вот что я наконец-то сделал, для будущих читателей:

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

class Foo(models.Model):
    NB_DIMENSIONS = N
    pass

# outside of the class
for i in range(1, Foo.NB_DIMENSIONS + 1):
    Foo.add_to_class(f"object_id_{i}", models.UUIDField())
    Foo.add_to_class(f"content_type_{i}", models.ForeignKey(ContentType))
    Foo.add_to_class(f"d{i}", GenericForeignKey(f"content_type_{i}", f"object_id_{i}"))

Здесь я косвенно использую хук Managers contribute_to_class, как описано в этом старом вопросе Stackoverflow . Он также упоминается здесь. Следует обратить внимание на то, что этот метод является недокументированным, приватным, внутренним API. Django не предоставляет гарантий обратной совместимости для него, и если вы используете его, вы принимаете риск того, что он может измениться или сломаться в любое время.

Альтернатива 1

Следующий код, кажется, тоже работает, но обратите внимание, что это плохая идея модифицировать locals в Python:

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

class Foo(models.Model):
    NB_DIMENSIONS = N
    for i in range(1, NB_DIMENSIONS + 1):
        locals()[f"object_id_{i}"] = models.UUIDField()
        locals()[f"content_type_{i}"] = models.ForeignKey(ContentType)
        locals()[f"d{i}"] = GenericForeignKey(f"content_type_{i}", f"object_id_{i}")

Позвольте мне настоять на следующем: использование locals() может быть плохой идеей по разным причинам, даже если оно кажется более читабельным.

Альтернатива 2

Как предложил @Abdul Aziz Barkat выше, должно быть возможно создать промежуточный класс с type() (ср. с этим Stackoverflow thread). У этого есть свои минусы: это подразумевает еще один класс для управления, и я считаю, что это труднее понять и поддерживать для менее опытных разработчиков

Вернуться на верх