Generic Foreign Key на несохраненной модели в Django

Я наткнулся на небольшую нестыковку с Foreign Keys и Generic Foreign Keys в Django. Предположим, у нас есть три модели

class Model_One(models.Model):
    name= models.CharField(max_length=255)

class Model_with_FK(models.Model):
    name=models.CharField(max_length=255)
    one=models.ForeignKey(Model_One, on_delete=models.CASCADE)

class Model_With_GFK(models.Model):
    name=models.CharField(max_length=255)
    content_type= models.ForeignKey(
        ContentType, on_delete=models.CASCADE,
    )
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")
    class Meta:
        indexes=[models.Index(fields=["content_type", "object_id"]), ]

Когда мы это сделаем

one = Model_One()
two = Model_Two(model_one=one)
one.save()
two.save()

Все работает нормально. Модель один получает ID, и при сохранении модели два этот ID используется для ссылки на модель один. Однако то же самое не работает с GFK

one = Model_One()
two = Model_With_GFK(content_object=one)
one.save()
two.save()

При попытке сохранения двух моделей возникает ошибка целостности, что колонка «object_id» является null. При проверке отладчиком, как только модель один сохраняется, поле «content_object» на модели два превращается из модели один в None. Это довольно неожиданно, так как с внешним ключом такой проблемы не возникает.

Конечно, вы можете сохранить одну модель перед тем, как использовать ее в GFK-отношении, но зачем это нужно? С Foreign keys я мог инстанцировать все модели, а затем создавать их с помощью bulk_create. С появлением GFK это больше не представляется возможным

Django's GenericForeignKey has been implemented in a bit more «boring» way. Indeed, it works with the .set(…) descriptor [GitHub]:

def __set__(self, instance, value):
    ct = None
    fk = None
    if value is not None:
        ct = self.get_content_type(obj=value)
        fk = value.pk

    setattr(instance, self.ct_field,ct)
    setattr(instance, self.fk_field, fk)
    self.set_cached_value(instance, value)

Таким образом, он делает не так уж много, кроме того, что находит content_type и первичный ключ для выбранного вами значения, а также устанавливает ct_field (тип содержимого) и fk_field (поле, хранящее первичные ключи).

Строго говоря, это можно было бы сделать, переписав ._prepare_related_fields_for_save(…) [GitHub], чтобы отложить сохранение до сохранения объекта в базе данных, но это, таким образом, не было сделано.

N.B.: Я открыл тикет [Django-ticket] для этого.

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