45

I have a Django form which I'm validating in a normal Django view. I'm trying to figure out how to extract the pure errors (without the HTML formatting). Below is the code I'm using at the moment.

return json_response({ 'success' : False,
                       'errors' : form.errors })

With this, I get the infamous proxy object error from Django. Forcing each error into Unicode won't do the trick either, because then each of the errors' __unicode__ method will be called effectively HTML-izing it.

Any ideas?

EDIT:

For those interested, this is the definition of json_response:

def json_response(x):
    import json
    return HttpResponse(json.dumps(x, sort_keys=True, indent=2),
                        content_type='application/json; charset=UTF-8')
Deniz Dogan
  • 25,711
  • 35
  • 110
  • 162

8 Answers8

37

This appears to have been improved. The following works in Django 1.3:

return json_response({
    'success': False,
    'errors': dict(form.errors.items()),
})

No need for __unicode__ or lazy translation any more. This also gives a full array of errors for each field.

SystemParadox
  • 8,203
  • 5
  • 49
  • 57
32

For Django 1.7+ use Form.errors.as_json() or something like this:

errors = {f: e.get_json_data() for f, e in form.errors.items()}
return json_response(success=False, data=errors)
lampslave
  • 1,431
  • 1
  • 15
  • 20
  • Updated docs link: https://docs.djangoproject.com/en/4.1/ref/forms/api/#django.forms.Form.errors.as_json – Nathaniel Jan 16 '23 at 19:54
26

Got it after a lot of messing around, testing different things. N.B. I'm not sure whether this works with internationalization as well. This also takes the first validation error for each field, but modifying it to get all of the errors should be rather easy.

return json_response({ 'success' : False,
                       'errors' : [(k, v[0].__unicode__()) for k, v in form.errors.items()] })
Deniz Dogan
  • 25,711
  • 35
  • 110
  • 162
  • 5
    you could also try: `form.error_class.as_text(v)` on `v` (which is an ErrorList), instead of calling `__unicode__()` on each item of `v` – tehfink Mar 12 '11 at 06:50
  • 4
    errors = dict([(k, form.error_class.as_text(v)) for k, v in form.errors.items()]) return json_response({"errors":errors}) – digitalPBK Apr 16 '12 at 09:11
  • 2
    I found the following to provide a better structure, and also cater for items with more than 1 error: `{k: v for k, v in context['signup_form'].errors.items()}` – DanH Nov 20 '12 at 08:53
4

The issue here is that error message are lazy translation object. The docs do mention this:

Just make sure you've got ensure_ascii=False and use a LazyEncoder.

Arthur Debert
  • 10,237
  • 5
  • 26
  • 21
1

We can do this:

import simplejson as json

errors = json.dumps(form.errors)
return HttpResponse(errors, mimetype='application/json')
0077cc
  • 61
  • 1
  • 1
1

json.dumps can't serialize django's proxy function (like lazy translations).

As documented you should create a new Encoder class:

import json
from django.utils.functional import Promise
from django.utils.encoding import force_text
from django.core.serializers.json import DjangoJSONEncoder

class LazyEncoder(DjangoJSONEncoder):
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_text(obj)
        return super(LazyEncoder, self).default(obj)

Use the new Encoder like this:

json.dumps(s, cls=LazyEncoder)

That's all :)

bjunix
  • 4,458
  • 4
  • 33
  • 33
1

You should use django in built method.

from django.http import JsonResponse
return JsonResponse(form.errors.as_json())
Sawan Chauhan
  • 621
  • 7
  • 9
1

I solved it using get_json_data() as follows:

data = form.errors.get_json_data()
return JsonResponse(data, status=400, safe=False)

We get a valid json:

{"amount": [{"message": "Ensure this value is greater than or equal to 100.", "code": "min_value"}]}

See: https://docs.djangoproject.com/en/4.1/ref/forms/api/#django.forms.Form.errors.get_json_data


If we use as_json() like this:

data = form.errors.as_json()
return JsonResponse(data, status=400, safe=False)

We get a string formatted as a json:

"{\"amount\": [{\"message\": \"Ensure this value is greater than or equal to 100.\", \"code\": \"min_value\"}]}"
Julian Espinel
  • 2,586
  • 5
  • 26
  • 20