30

I am adding custom validation to my forms and custom fields in my Django app. I would like to be able to modify the value of a field when triggering an error. For example, if there is an error, the form should be redisplayed with the field value corrected by clean() and an error message "Data has been corrected below. Click save again to confirm if these changes are OK"

I've tried returning the modified data in cleaned_data[] like this but it doesn't work. It displays the error correctly, but the field value is not updated with the corrected HTML when the form is redisplayed.

class T34AtividadeForm(ModelForm):
    def clean(self):
        # Return cleaned html
        error,html = fix_imgs(cleaned_data.get("a34_descricao"))
        if error:
            msg = u'Data has been corrected below. Click save again to confirm if these changes are OK';
            self._errors['a34_descricao'] = ErrorList([msg])
            # This doesn't work
            cleaned_data["a34_descricao"] = html
            # This doesn't work either
            self.a34_descricao = html

    return cleaned_data

I'd also like to do the same thing with a field, but since the errors are triggered by exception, I don't get a chance to return the corrected value. Like the form clean() method, the error is displayed correctly, but the value is not updated.

class HTMLField(CharField):
    widget = HTMLTextarea

    def clean(self, value):
        value = super(HTMLField,self).clean(value)
        error,html = fix_imgs(value)
        if error:
            # This doesn't work
            self.value = html
            raise forms.ValidationError(u'Data has been corrected below. Click save again to confirm if these changes are OK.')
        return html
gerdemb
  • 11,275
  • 17
  • 65
  • 73

7 Answers7

11

It is possible to modify a value of a field during clean() if you update self.data attribute of a Form. self.data is an instance of the QueryDict class. By default, querydicts are immutable. To make them mutable, you should use .copy() method. From the documentation:

The QueryDicts at request.POST and request.GET will be immutable when accessed in a normal request/response cycle. To get a mutable version you need to use QueryDict.copy()

self.data = self.data.copy()
self.data['your_field'] = 'new value'
bilbohhh
  • 693
  • 1
  • 6
  • 16
7

change self data in the clean method to change the value which gets displayed

user51463
  • 182
  • 2
  • 6
    This works, but is undocumented and it feels ugly to modify the QueryDict object directly. Additionally, I had to write a helper function to modify the read-only QueryDict.... – gerdemb Mar 17 '09 at 15:07
  • 3
    @user27478: Can you post your solution? – Don Feb 02 '11 at 14:44
  • 2
    In Django 2.0, `cleaned_data = super().clean()` (or equivalent), modify the `cleaned_data` dict and make sure to `return cleaned_data` at the end of the function. – Ryan Jul 14 '18 at 22:14
  • 1
    Note that this will only work if editable form fields were changed, else no changes are saved. – GCru Nov 15 '20 at 15:15
4

Way to update value in clean() is update the value in form's data dictionary:

self.data["a34_stuff"] = "html"

This works for sure.

Amit Singh
  • 149
  • 1
  • 6
1

We can't redireclty edit request.data because it's an immutable dict. You need to copy it, do your stuff and return it.

But after differentes solutions I find this way (tested in Django 2.2.6)

class MyForm(ModelForm):

    def clean(self):
        cleaned_data = super(MyForm, self).clean()
        self.instance.field = 'value'
        return cleaned_data

Pyvonix
  • 757
  • 9
  • 22
1

I was setting a value to a specific field in another clean function, but it didnt work because of it was overwrited by the main clean function. In order to set the value and keep it :

  • There didnt work.
    def clean_tipo_cupon(self):
        tipo_cupon = self.cleaned_data['tipo_cupon']
        qscu = mdlcat.TipoCuponVenta.objects.filter(
            id=tipo_cupon, estado_id=1,
        )
        if not qscu.exists():
            wmessage = u'introducido no paso validación'
            raise forms.ValidationError(wmessage)

        qscu = qscu.first()
        if not qscu.default_owner is None:
            self.cleaned_data['default_owner'] = qscu.default_owner.id

        return tipo_cupon

  • Here, it worked like a charm.
    def clean(self):
        cleaned_data = super().clean()
        for item in self.fields.keys():
            if isinstance(cleaned_data.get(item), str):
                cleaned_data[item] = cleaned_data.get(item).upper()

        # if a default owner is configured by coupon type
        # it will be assigned
        tipo_cupon = cleaned_data['tipo_cupon']
        qscu = mdlcat.TipoCuponVenta.objects.filter(
            id=tipo_cupon, estado_id=1,
        )
        qscu = qscu.first()
        if not qscu.default_owner is None:
            self.cleaned_data['default_owner'] = qscu.default_owner.id

        return cleaned_data
Dharman
  • 30,962
  • 25
  • 85
  • 135
Manuel Lazo
  • 745
  • 7
  • 7
0

This is the way I've tried and works for me:

 inst = my_form.save(commit=False)
 if not inst.a34_stuff: # or incorrect
     inst.data["a34_stuff"] = "corrected"
Menda
  • 1,783
  • 2
  • 14
  • 20
-2

If your form is a model form a better approach would be to get an instance and correct that data instead:

inst = my_form.save(commit=False)
if inst.a34_stuff is None: #or incorrect
    inst.a34_stuff = "corrected"
    request.user.message_set.create(message = "Error corrected")
    return HttpResponseRedirect(reverse('your_url_name',
            args=[])

PS: I am not sure if this will actually work... Not sure if form will pickup changes.

Andriy Drozdyuk
  • 58,435
  • 50
  • 171
  • 272