Динамическое определение атрибутов модели / полей базы данных в 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). У этого есть свои минусы: это подразумевает еще один класс для управления, и я считаю, что это труднее понять и поддерживать для менее опытных разработчиков