Как автоматически выбрать связанную модель, используя значение поля

Предположим, у нас есть модель

class BaseModel(models.Model):
    is_a = models.BooleanField()

и две модели, связанные с этой:

class A(models.Model):
    value_1 = models.IntegerField()
    base = models.ForeignKey(BaseModel, related_name='a')

class B(models.Model):
    value_1 = models.IntegerField()
    value_2 = models.IntegerField()
    base = models.ForeignKey(BaseModel, related_name='b')

Мне нужно ссылаться на A или B в зависимости от свойства is_a.

Например,

base = BaseModel.objects.get(id=1)
if base.is_a:
    obj = A.objects.create(value_1=1, base=base)
else:
    obj = B.objects.create(value_1=1, value_2=2, base=base)
return obj

или

if base.is_a:
    queryset = base.a.all()
else:
    queryset = base.b.all()
return queryset

т.е. каждый раз, когда мне приходится проверять свойство is_a.

Есть ли более изящный способ?

Существует две единственные родственные модели, A и B, других в ближайшее время не появится.

Частично проблему можно решить с помощью django-polymorphic, например:

class A(PolymorphicModel):
    ...

class B(A):
    ...

Это позволяет получить все A и B одним запросом, как base.b.all(), но проблема здесь в том, что каждый B создает экземпляр A, что нежелательно.

Я тоже рассматривал GenericForeignKey. Насколько я понял, у него есть ряд ограничений типа "1) нельзя использовать GenericForeignKey в фильтрах запросов; 2) GenericForeignKey не появится в ModelForm" (из GenericForeignKey или ForeignKey).

Одна из идей - добавить варианты выбора к BaseModel, чтобы иметь строковое представление вашего булевого значения. Если вы установите строки равными именам моделей A и B, вы можете использовать метод model.get_foo_display() для возврата имени модели. Затем используйте метод Python getattr() для доступа к атрибутам как к переменным.

class BaseModel(models.Model):

    base_model_choices = (
        (True, 'A'),
        (False, 'B'),
    )

    is_a = models.BooleanField(choices=base_model_choices)

Например,

base = BaseModel.objects.get(id=1)
queryset = base.getattr(models, get_is_a_display()).all()
obj = getattr(models, get_is_a_display()).objects.create(base=base)
Вернуться на верх