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.
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