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] для этого.