I have formset of modelforms contains ImageField. After click on delete checkbox near initial form, and submit button - I have same number of forms in formset, and same number of initial forms, but those which have been "deleted" now are without value and without delete checkbox (checkbox is enable in admin panel, I assume it's widget render). What I am doing wrong?
Mixin to combine create and update views:
class HumanAddEditMixin(object):
u"""
"""
form_class = HumanForm
model = Human
template_name = 'human/add_edit.html'
object = None
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(HumanAddEditMixin, self).dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if self.object:
form = self.form_class(request.POST, instance=self.object)
sign_formset = SignFormSet(request.POST, request.FILES, instance=self.object)
description_formset = DescriptionFormSet(request.POST, request.FILES, instance=self.object)
doc_formset = DocFormSet(request.POST, instance=self.object)
else:
form = self.form_class(request.POST)
sign_formset = SignFormSet(request.POST, request.FILES)
description_formset = DescriptionFormSet(request.POST, request.FILES)
doc_formset = DocFormSet(request.POST)
if form.is_valid() and sign_formset.is_valid() and description_formset.is_valid() and doc_formset.is_valid():
return self.form_valid(form, sign_formset, description_formset, doc_formset)
else:
return self.form_invalid(form, sign_formset, description_formset, doc_formset)
def form_valid(self, form, sign_formset, description_formset, doc_formset):
if not self.object:
self.object = form.save()
sign_formset.instance = self.object
description_formset.instance = self.object
doc_formset.instance = self.object
sign_formset.save()
description_formset.save()
doc_formset.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, sign_formset, description_formset, doc_formset):
return self.render_to_response(
self.get_context_data(
form=form,
sign_formset=sign_formset,
description_formset=description_formset,
doc_formset=doc_formset
)
)
def get_success_url(self):
return reverse('human:list')
CreateView looks like it works fine:
class HumanAddView(HumanAddEditMixin, CreateView):
def get_context_data(self, **kwargs):
context = super(HumanAddView, self).get_context_data(**kwargs)
context['sign_formset'] = SignFormSet(instance=self.model())
context['description_formset'] = DescriptionFormSet(instance=self.model())
context['doc_formset'] = DocFormSet(instance=self.model())
return context
UpdateView:
class HumanUpdateView(HumanAddEditMixin, UpdateView):
pk_url_kwarg = 'human_id'
def dispatch(self, request, *args, **kwargs):
self.object = get_object_or_404(self.model, pk=kwargs[self.pk_url_kwarg])
return super(HumanUpdateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(HumanUpdateView, self).get_context_data(**kwargs)
context['sign_formset'] = SignFormSet(instance=self.object)
context['description_formset'] = DescriptionFormSet(instance=self.object)
context['doc_formset'] = DocFormSet(instance=self.object)
return context
Formsets declaration:
SignFormSet = inlineformset_factory(Human, HumanSignImage, form=HumanSignImageForm, extra=1)
DescriptionFormSet = inlineformset_factory(Human, HumanDescriptionImage, form=HumanSignImageForm, extra=1)
DocFormSet = inlineformset_factory(Human, HumanDocImage, form=HumanDocImageForm, extra=1)
One of models with image (that in SignFormSet) and it's modelform (just with crispy-forms helper):
class HumanSignImage(models.Model):
human = models.ForeignKey('Human', related_name='sign_images')
image = models.ImageField(upload_to=get_file_path, verbose_name=u'', blank=True, null=True)
def __unicode__(self):
return self.image.name
class HumanSignImageForm(CommonImageModelFormWithHelper):
class Meta:
model = HumanSignImage
Finally I've figured out my problem was that there are two checkbox types on Admin panel: clear and delete. And for some reason (with can_delete=True crispy-forms doesn't render delete checkbox here is an answer) I mistook that clear was delete.
Or (without crispy-forms) override ClearableFileInput widget (without clear but with delete), something like that:
class ImageFileInput(ClearableFileInput):
template_with_initial = '{initial} {input} {delete}'
def delete_checkbox_name(self, name):
"""
Given the name of the file input, return the name of the DELETE checkbox
input.
"""
tmp_list = name.split('-')
del tmp_list[-1]
delete_checkbox_name = '-'.join(tmp_list)
return delete_checkbox_name + '-DELETE'
def delete_checkbox_id(self, name):
"""
Given the name of the delete checkbox input, return the HTML id for it.
"""
return 'id_' + name
def render(self, name, value, attrs=None):
substitutions = {}
template = '{input}'
substitutions['input'] = FileInput().render(name, value, attrs)
if value and hasattr(value, "url"):
template = self.template_with_initial
substitutions['initial'] = format_html(
self.url_markup_template,
value.url,
force_text(value)
)
checkbox_name = self.delete_checkbox_name(name)
checkbox_id = self.delete_checkbox_id(checkbox_name)
substitutions['delete'] = CheckboxInput().render(checkbox_name, False, attrs={'id': checkbox_id})
return mark_safe(template.format(**substitutions))