Django BaseGenericInlineFormSet表单不将FormSet实例作为表单实例related_object继承

时间:2015-09-30 12:09:00

标签: django django-forms inline-formset generic-relations

我正在使用Django 1.8,我有一个Image类,如下所示:

# The `child` class
class Image(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()

    related_object = GenericForeignKey('content_type', 'object_id')

    image = models.ImageField(...)

    def clean(self):
        related_class = self.content_type.model_class()
        # Do some validation that relies on the related_class

"父母"具有GenericRelation的类:

# The `parent` class
class Product(models.Model):
    ...
    images = GenericRelation('Image')

这是我的(简化)视图:

from django.shortcuts import render, get_object_or_404
from django.views.generic import View
from django.contrib.contenttypes.forms import generic_inlineformset_factory

ProductImageInlineFormset = generic_inlineformset_factory(
    Image, extra=1)

class ProductImageView(View):
    ...
    def post(self, request, id):
        product = get_object_or_404(Product.objects.by_id(id))
        image_formset = ProductImageInlineFormset(
            request.POST, request.FILES, instance=product)
        # I SHOULDN'T NEED THE FOLLOWING TWO LINES ->
        # for form in image_formset:
        #     form.instance.related_object = product
        import ipdb; ipdb.set_trace()

        if image_formset.is_valid():
            image_formset.save()

        return render(request, self.template,
                      context={'cid': id, 'formset': image_formset})

当我在ipdb中检查formset时,这就是我得到的:

ipdb> image_formset.forms[0].instance.related_object is None
True

这会导致问题,因为当我到达Image.clean()时出现错误:

django.db.models.fields.related.RelatedObjectDoesNotExist: Image has no content_type.

如果我取消注释我提到的那两行我不需要,它可以工作,我不会再收到错误了。但是,在使用BaseGenericInlineFormSet的整个过程中,表单与模型和相关模型的自动链接是不是?如果我必须手动破解ImageForm实例并将Product实例附加到其related_object,那么我不妨使用一个简单的ModelFormSet。我错过了什么吗?

更新

如果我评论Image.clean,即使没有相关对象的手动附件,代码也能正常工作。这意味着BaseGenericInlineFormSet确实会处理链接,但它会在子模型上调用clean之后这样做,考虑到Model.clean" should be used to provide custom model validation&#34,这真的不行。 ;。我正在查看Django源代码,但尚未确切知道链接的确切位置。欢迎提示。

更新2

显然,链接是在InlineFormSet save_new方法中完成的:

def save_new(self, form, commit=True):
    setattr(form.instance, self.ct_field.get_attname(),
        ContentType.objects.get_for_model(self.instance).pk)
    setattr(form.instance, self.ct_fk_field.get_attname(),
        self.instance.pk)
    return form.save(commit=commit)

https://github.com/django/django/blob/master/django/contrib/contenttypes/forms.py#L46

作为一项实验,我已将该代码移至自定义_construct_form方法:

 def _construct_form(self, i, **kwargs):
     form = super()._construct_form(i, **kwargs)
     setattr(form.instance, self.ct_field.get_attname(),
         ContentType.objects.get_for_model(self.instance).pk)
     setattr(form.instance, self.ct_fk_field.get_attname(),
         self.instance.pk)
     return form

它解决了我的问题。这样我就不必进行手动链接。我没有进行测试或编写补丁,但如果有人决定在将来做这件事(也许我自己在某一点上),这可能是第一步。

现在,我通过手动链接保留我的解决方案。不想使用黑客版的Django。

1 个答案:

答案 0 :(得分:1)

由于我没有反馈意见,我认为这是一个Django错误,而且看起来确实如此。我在这里提交了一张票:https://code.djangoproject.com/ticket/25488

直到解决这个问题的解决方案是我之前建议的(即迭代视图中的表单并手动将它们链接到产品)或使用固定的FormSet类,如:

class FixedBaseGenericInlineFormSet(BaseGenericInlineFormSet):
    def _construct_form(self, i, **kwargs):
        form = super()._construct_form(i, **kwargs)
        setattr(form.instance, self.ct_field.get_attname(),
            ContentType.objects.get_for_model(self.instance).pk)
        setattr(form.instance, self.ct_fk_field.get_attname(),
            self.instance.pk)
        return form

ProductImageInlineFormset = generic_inlineformset_factory(
    Image,
    form=ProductImageForm,
    formset=FixedBaseGenericInlineFormSet,
    extra=1
)