Django BaseGenericInlineFormSet 表单不继承 FormSet 实例作为表单实例 related_object
Django BaseGenericInlineFormSet forms not inheriting FormSet instance as form instance related_object
我正在使用 Django 1.8,我有一个图像 class 看起来像这样:
# 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
还有一个 "parent" 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 中的表单集时,这是我得到的:
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”,这确实不行。我正在查看 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 版本。
由于我没有收到任何反馈,我假设这是一个 Django 错误,而且看起来确实如此。我在这里提交了一张票:https://code.djangoproject.com/ticket/25488
解决此问题之前的解决方案是我之前建议的(即遍历视图中的表单并手动将它们链接到产品)或使用固定的 FormSet class,例如:
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
)
我正在使用 Django 1.8,我有一个图像 class 看起来像这样:
# 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
还有一个 "parent" 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 中的表单集时,这是我得到的:
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”,这确实不行。我正在查看 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 版本。
由于我没有收到任何反馈,我假设这是一个 Django 错误,而且看起来确实如此。我在这里提交了一张票:https://code.djangoproject.com/ticket/25488
解决此问题之前的解决方案是我之前建议的(即遍历视图中的表单并手动将它们链接到产品)或使用固定的 FormSet class,例如:
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
)