Django GenericRelation не сохраняет идентификатор связанного объекта - это ошибка или я делаю это неправильно? - PullRequest
0 голосов
/ 02 января 2011

У меня есть модель с родовым отношением (назовите его A), при создании экземпляра этого объекта я передаю экземпляр другой модели (назовем его B) в качестве инициализатора поля content_object (через kwargs конструктора) .

Если я не сохраняю B до создания A, то при сохранении A content_object_id сохраняется в БД как NULL. Если я сохраню B , прежде чем передать его конструктору A , тогда все в порядке.

Это не логично. Я предположил, что идентификатор связанного объекта (B) выбирается при выполнении A.save (), и он должен выдавать какое-то исключение, если B еще не сохранен, но просто молча завершается сбоем. Мне не нравится текущее решение (сохраняя B заранее), потому что мы еще не знаем, всегда ли я буду сохранять объект, а не просто удалять его, и есть соображения производительности - что если я добавлю некоторые другие данные и сохраните его еще раз вскоре после этого.

class BaseNodeData(models.Model):
    ...
    extnodedata_content_type = models.ForeignKey(ContentType, null=True)
    extnodedata_object_id = models.PositiveIntegerField(null=True)
    extnodedata = generic.GenericForeignKey(ct_field='extnodedata_content_type', fk_field='extnodedata_object_id')

class MarkupNodeData(models.Model):
    raw_content = models.TextField()

Предположим, мы делаем:

markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
markup.save()
base.save()
# both records are inserted to the DB but base is stored with extnodedata_object_id=NULL

markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
base.save()
markup.save()
# no exception is thrown and everything is the same as above

markup = MarkupNodeData(raw_content='...')
markup.save()
base = BaseNodeData(..., extnodedata=markup)
base.save()
# this works as expected

Конечно, я могу сделать это таким образом, но это ничего не меняет:

base = BaseNodeData(...)
base.extnodedata = markup

Мой вопрос - это ошибка в django, о которой я должен сообщить, или, возможно, я делаю что-то не так. Документы на GenericRelations не совсем подробны.

Ответы [ 3 ]

0 голосов
/ 03 января 2011

Экземпляр B имеет pk None перед сохранением, а ваше поле extnodedata_object_id допускает нулевые значения, поэтому сохранение экземпляра A допустимо.

Звучит так, что вам может пригодиться переопределение сохранения A для правильной обработки новых экземпляров B; например (Непроверенные):

def save(self, *args, **kwargs):
    b = self.extnodedata
    if b and b.pk is None:
        b.save()
        self.extnodedata = b
    return super(BaseNodeData, self).save(*args, **kwargs)
0 голосов
/ 03 января 2011

Спасибо за ваши ответы. Я решил потратить некоторое время на изучение источников Django и сам придумал решение. Я вложил в подкласс GenericForeignKey. Код должен быть понятен.

from django.contrib.contenttypes import generic
from django.db.models import signals

class ImprovedGenericForeignKey(generic.GenericForeignKey):
    """
    Corrects the behaviour of GenericForeignKey so even if you firstly
    assign an object to this field and save it after its PK gets saved.

    If you assign a not yet saved object to this field an exception is 
    thrown upon saving the model.
    """

    class IncompleteData(Exception):
        message = 'Object assigned to field "%s" doesn\'t have a PK (save it first)!'

        def __init__(self, field_name):
            self.field_name = field_name

        def __str__(self):
            return self.message % self.field_name

    def contribute_to_class(self, cls, name):
        signals.pre_save.connect(self.instance_pre_save, sender=cls, weak=False)
        super(ImprovedGenericForeignKey, self).contribute_to_class(cls, name)

    def instance_pre_save(self, sender, instance, **kwargs):
        """
        Ensures that if GenericForeignKey has an object assigned
        that the fk_field stores the object's PK.
        """

        """ If we already have pk set don't do anything... """
        if getattr(instance, self.fk_field) is not None: return

        value = getattr(instance, self.name)

        """
        If no objects is assigned then we leave it as it is. If null constraints
        are present they should take care of this, if not, well, it's not my fault;)
        """
        if value is not None:
            fk = value._get_pk_val()

            if fk is None:
                raise self.IncompleteData(self.name)

            setattr(instance, self.fk_field, fk)

Я думаю, что это следует считать ошибкой в ​​django, поэтому я сообщу об этом и посмотрю, как это получается.

0 голосов
/ 03 января 2011

Я согласен, что странно, что вы можете сохранить A без сохранения B заранее.

Но я не могу вспомнить случай, когда вы установите отношение к объекту, который вы не хотите сохранять. Не имеет смысла иметь отношение к объекту, который не существует ;-) Таким образом, сохранение B заранее хорошо для меня.

Не уверен, поможет ли это, но метод create, опубликованный в этом вопросе , может дать вам представление о том, что вы также можете сделать с родовыми отношениями.

...