Django BaseGenericInlineFormSet forms not inheriting FormSet instance as form instance related_object

十年热恋 提交于 2019-12-10 10:21:17

问题


I'm using Django 1.8 and I have an Image class that looks like this:

# 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

And a "parent" class that has a GenericRelation to it:

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

This is my (simplified) view:

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})

When I inspect the formset in ipdb, this is what I get:

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

This is causing problems because when I get to Image.clean() I get an error:

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

If I uncomment those two lines I mentioned I don't need, it works and I don't get the error anymore. But isn't the automatic linking of forms to their models and related models the whole point of using the BaseGenericInlineFormSet? If I have to manually hack the ImageForm instances and attach a Product instance to its related_object, then I might as well use a simple ModelFormSet. Am I missing something?

UPDATE

If I comment Image.clean out, the code works even without the manual attachment of related objects. This means the BaseGenericInlineFormSet does handle the linking after all, but it does so AFTER it calls clean on the child model, which is really not ok considering that Model.clean "should be used to provide custom model validation". I'm taking a look at the Django source but haven't yet figured out exactly where it does the linking. Tips are welcome.

UPDATE 2

Apparently the linking is done in the InlineFormSet save_new method:

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

As an experiment, I've moved that code to a custom _construct_form method:

 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

It solved my problem. This way I don't have to do that manual linking. I haven't run the tests or written a patch but this could be a first step if someone decides to do it in the future (perhaps myself at one point).

For now I'm keeping my solution with manual linking though. Don't want to work with a hacked version of Django.


回答1:


Since I've got no feedback, I'll assume it's a Django bug, and it really seems to be the case. I filed a ticket here: https://code.djangoproject.com/ticket/25488

The solution until this is solved is either what I suggested earlier (i.e. iterating over the forms in the view and linking them to the product manually) or using a fixed FormSet class, something like:

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
)


来源:https://stackoverflow.com/questions/32865691/django-basegenericinlineformset-forms-not-inheriting-formset-instance-as-form-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!