I'm trying to display ValidationErrors using ajax. I've read many posts about this and tried many things but can't get it to work. I thiiinnnkkk ValidationErrors are passed as a dictionary and that they need to have json.dumps or .as_json() called on them before they are passed to ajax.
Here is some of my code:
forms.py
raise forms.ValidationError('Please enter a number greater than 100')
views.py
if form.is_valid():
[...]
else:
# i've tried this
error_dict= {'status':'form-invalid','form-errors':form.errors}
return HttpResponse(json.dumps(error_dict),content_type="application/json", status_code=400)
# and this
data = {'error':form.errors.as_json(), 'is_valid': False}
return JsonResponse(data, status_code=400)
# and more
ajax
error: function (data, xhr, errmsg, err) {
$('.error').html(data.form-errors)
},
--edit--
console.log:
{error: {…}, is_valid: false}
error:
__all__: Array(1)
0: "You cannot lock a lesson when the following lesson is unlocked"
length: 1
__proto__: Object
The error details are actually store in data['error'] not data.form-errors. You data will also have another item is_valid. The response data structure can be inspected by console logging data in ajax error. According to the structure access values and assign to elements.
In ajax error:
error: function (data, xhr, errmsg, err) {
console.log(data)
console.log(data.responseJSON)
// according to the structure of data access error value and assign
$('.error').html(data['error'])
},
in views.py form.errors should be passed. in JsonResponse() we need to pass a dictionary.
The first parameter, data, should be a dict instance. doc
if form.is_valid():
[...]
else:
data = {'error':form.errors, 'is_valid': False}
return JsonResponse(data, status_code=400)
In your script if you console.log('data.responseJSON') you could see something like this
In the above image I can access the error message for the field 'fname' as data.responseJSON.error.fname[0]
Related
I'm trying to pass a few arguments form my view to my additional function, but I get error "Object of type 'Request' is not JSON serializable", I don't why is occured with me, cause I didn't pass request object to Response.
#api_view(['POST', ])
#permission_classes([IsAuthenticated])
def users_upload(request):
if request.user.type != 't':
logger.warning(f'Users upload ERROR: -{request.user.username}- not a teacher')
return Response({'message': 'Not a teacher'}, status=403)
try:
user_file = request.FILES['data']
file_format = user_file.name.split('.')[-1]
if file_format not in ['csv', 'xls', 'xlsx']:
logging.error('User upload ERROR: incorrect file format')
return Response({'message': 'Incorrect file format'}, status=400)
auto_file = AutoFile.objects.create(
description='Temp file for user upload',
file=user_file
)
upload_users_from_file.delay(request, auto_file.pk, file_format)
return Response({'message': 'Task created'}, status=200)
except Exception as e:
logging.error(f'User upload ERROR: unable to evaluate file format: {e}')
return Response({'message': 'Error during file check'}, status=400)
What happened?
I can't show function "upload_users_from_file" because it distributes request object to large functions chain, anyway you can see by logger that ERROR occured actually in user_upload function.
When delay() Celery must to serialize all the delay arguments to store in the task database. When Celery-worker gets this task, it deserializes arguments.
You try to pass the Request object as an argument to delay. Celery doesn't known how to serialize this type of objects.
Some advises:
Try to pass to tasks only objects that serializable by default - lists, dicts, strs, ints, etc.
If you need to pass a lot of arguments, combine them into dicts or tuples.
You may want to make your object serializable. Please pay attention to these topics:
How to make a class serializable
How to register a custom JSON encoder to Celery
I am making a mobile app that uses Django Rest Framework. When one of my models fails validation, it uses one of the model.attributes as a key inside the error message, for example:
{'status_code': 400, 'name': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'password': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'arbitrary_field': [u'Ensure this field has no more than 32 characters.']}
This is very difficult to scale in a mobile, so I want to take the error messages and deliver a consistent 'error' key to the mobile device. For example,
{'status_code': 400, 'error': [u' Name: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Password: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Arbitrary Field: Ensure this field has no more than 32 characters.']}
In Rails, I could do this by saying:
model.errors.full_messages
But I'm not sure what the equivalent in Django is?
Thanks
You can define a property custom_full_errors in your serializer which will return the errors formatted according to your requirement. Doing serializer.custom_full_errors will give you the desired response.
We first call the serializer.errors to get the default errors dictionary. Then we iterate on this dictionary and create our desired response.
class MySerializer(serializers.ModelSerializer):
#property
def custom_full_errors(self):
"""
Returns full errors formatted as per requirements
"""
default_errors = self.errors # default errors dict
errors_messages = []
for field_name, field_errors in default_errors.items():
for field_error in field_errors:
error_message = '%s: %s'%(field_name, field_error)
errors_messages.append(error_message) # append error message to 'errors_messages'
return {'error': errors_messages}
...
For example:
# serializer.errors
{'name': [u'Ensure this field has no more than 32 characters.']}
will translate to
# serializer.custom_full_errors
{'error': [u'Name: Ensure this field has no more than 32 characters.']}
In case of multiple errors for a single field, we will get the following output on doing serializer.custom_full_errors:
# serializer.custom_full_errors
{'error': [u'Name: Error1', u'Name: Error2', u'Password: Error1', u'Password: Error2' ]}
I read this blog article, am yet to test it out
https://masnun.com/2015/11/06/django-rest-framework-custom-exception-handler.html
You create a custom error handler that concatenates the key-value pairs, after passing the exception through the default error handler method
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if response is not None:
data = response.data
response.data = {}
errors = []
for field, value in data.items():
errors.append("{} : {}".format(field, " ".join(value)))
response.data['errors'] = errors
response.data['status'] = False
response.data['exception'] = str(exc)
return response
Then you point DRF to your custom error handler
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'EXCEPTION_HANDLER': 'api.utils.custom_exception_handler'
}
In case somebody is still looking for a solution, here is a great library that helps a lot in dealing with custom error validation: https://pypi.org/project/drf-standardized-errors/, here is the GitHub repo: https://github.com/ghazi-git/drf-standardized-errors
The response will be flat and instead of this:
{
"shipping_address":
{
"non_field_errors": [ErrorDetail("We do not support shipping to the provided address.", code="unsupported")]
}
}
You'll get this:
{
"code": "unsupported",
"detail": "We do not support shipping to the provided address.",
"attr": "shipping_address.non_field_errors"
}
It deals pretty good with List Serializers, changing such response:
{
"recipients": [
{"name": [ErrorDetail("This field is required.", code="required")]},
{"email": [ErrorDetail("Enter a valid email address.", code="invalid")]},
]
}
To this:
{
"type": "validation_error",
"errors": [
{
"code": "required",
"detail": "This field is required.",
"attr": "recipients.0.name"
},
{
"code": "invalid",
"detail": "Enter a valid email address.",
"attr": "recipients.1.email"
}
]
}
Based on this flat response it's much easier to form one string message to represent all the errors if you need to.
Basically, it handles the hardest part - getting a flat response out of a complex Django structure (all the nested fields, dicts, lists, etc.)
I know it is a bit late but I made this one using backtracking in case you have a lot of embedded nested serialisers.
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
stack = [response.data]
response.data = {}
errors = {}
if response is not None:
while len(stack) > 0:
data = stack.pop()
for field, value in data.items():
if type(value) is dict:
stack.append(value)
else:
if value is not list:
errors[field] = value
continue
for val in value:
code = val.code
message = str(val)
errors[field] = {
'message': message,
'code': code
}
response.data['errors'] = errors
return response
I'm trying to send data to the server and use with AJAX
function stats(e){
jQuery.ajax({
type: "POST",
url:"stats",
data:{'csrfmiddlewaretoken': document.getElementsByName('csrfmiddlewaretoken')[0].value,
'test':{}},
success: function(data) {alert("Congratulations!"+data);},
error: function(data) {
alert("Please report this error: "+data.responseText);}
});
}
function in views.py:
def stats(request):
if request.is_ajax():
if request.user.is_authenticated():
if request.POST:
return HttpResponse(request.POST['test'])
else:
return HttpResponse("post_no_exists")
else:
return HttpResponse("no authenticate")
else:
raise Http404
Django raise MultiValueDictKeyError 'key "test" not found in QueryDict'.
When I change "test":{} -> "test":1 it succeds.
Whats my error?
Here:
...
data: {
'csrfmiddlewaretoken': ...,
'test': {}
}
...
You're trying to send an empty object literal as part of the POST request. jQuery can't figure out what value to send for that (empty object? what would that even mean?), so it simply doesn't send the test parameter at all. Thus, on the server side, trying to access request.POST['test'] throws a KeyError.
When you change the test to a different literal (in this case a number), jQuery can easily encode and send that as the value.
You need return answer as json
payload = {'answer': 'post_no_exists'}
return HttpResponse(json.dumps(payload, cls=LazyEncoder), content_type='application/json')
in js
alert("Congratulations!"+data.answer);
So I have this code:
def success_comment_post(request):
if "c" in request.GET:
c_id = request.GET["c"]
comment = Comment.objects.get(pk=c_id)
model = serializers.serialize("json", [comment])
data = {'message': "Success message",
'message_type': 'success',
'comment': model }
response = JSONResponse(data, {}, 'application/json')
return response
else:
data = {'message': "An error occured while adding the comment.",
'message_type': 'alert-danger'}
response = JSONResponse(data, {}, 'application/json')
and back in jQuery I do the following:
$.post($(this).attr('action'), $(this).serialize(), function(data) {
var comment = jQuery.parseJSON(data.comment)[0];
addComment($("#comments"), comment);
})
Now... in the Django function, why do I have to put the comment in [] -->
model = serializers.serialize("json", [comment])
and back in jQuery, why do I have to do jQuery.parseJSON(data.comment)[0]?
Anyway I don't have to do this? I find it weird I have to hardcode the [0]
Thanks a lot!
Well serializers.serialize only takes querysets or iterators with django model instances but using Comment.objects.get will return an object and not an iterator and that is why you will need to put it in [] to make it an iterator.
Since its a list you will have to access it like an array in javascript too. I would suggest not using serializer and using simplejson to convert field values to json.
Sample Code:
from django.utils import simplejson as json
from django.forms.models import model_to_dict
comment = Comment.objects.get(pk=c_id)
data = {'message': "Success message",
'message_type': 'success',
'comment': model_to_dict(comment)}
return HttpResponse(json.dumps(data), mimetype='application/json')
I have only mentioned relevant parts of your code. Hopefully this should solve your problem
I have a form that is throwing a internal server error when i submit it with data
it says it is due to an integrity error:
IntegrityError at /cookbook/createrecipe/
(1048, "Column 'original_cookbook_id' cannot be null")
what is weird is that i dont have a column original_cookbook_id - what i do have is an original_cookbook that is a foreign key to a cookbook model
here is the view that is giving the error:
def createrecipe(request):
print "entering createrecipeview"
if request.method == 'POST':
print "form is a post"
form = RecipeForm(request.POST)
print form.errors
if form.is_valid():
print "form is valid"
form = RecipeForm(initial = {'original_cookbook' : request.user.cookbooks.all()[0]})// this is where the error is being thrown
form.save()
t = loader.get_template('cookbook/create_form.html')
c = RequestContext(request, {
'form': form,
})
data = {
'replace': True,
'form': t.render(c),
'success': True,
}
json = simplejson.dumps(data)
return HttpResponse(json, mimetype='text/plain')
else:
print "form is invalid"
form = RecipeForm(request.POST)
t = loader.get_template('cookbook/create_form.html')
c = RequestContext(request, {
'form':form,
})
data ={
'form': t.render(c),
'success': False,
}
json = simplejson.dumps(data)
return HttpResponse(json, mimetype='text/plain')
i think i might be declaring the original_cookbook the wrong way
anyone else have any experience with _id being appended to a foreign key/ have any idea how i might be able to resolve this issue
thanks a lot
katie
update
here is my javascript function
<script type="text/javascript">
$(document).ready(function(){
function hijack() {
var form = $('form#createrecipeform');
form.each (function(){
this.reset();
});
form.submit(function(e) {
e.preventDefault();
console.log('ajax form submission function called successfully.');
//form = $(this);
console.log(form)
var serialized_form = form.serialize();
$.ajax({ type: "POST",
url: $(this).attr('action'),
data: serialized_form,
success: (function(data) {
console.log('ajax success function called successfully.');
data = $.parseJSON(data);
if (data.success) {
console.log('success');
//right now it is logging success to
//console but nothing else is happening
//what else should happen on data success?
} else {
console.log('failure');
var newForm = data.form;
form.replaceWith(newForm);
hijack();
}
})
});
return false;
});
};
hijack();
});
</script>
You do have an original_cookbook_id field. That's where the pk for the foreign key is store in the table. Django automagically appends the _id bit. All the error means is that that field is set to NOT NULL, and you didn't provide a value to original_cookbook to fill it in with.
UPDATE
There's nothing "wrong" per se, but a few things to note. First, initial only sets the initial value that appears in the form (obviously enough). However, if it's later set back to empty by the user or some other action, it's still empty, i.e. initial has no true effect on the eventual posted data.
You didn't post your javascript, so I can't speak to how you're submitting the form, but that too could potentially be an issue. If you're not submitting the field with the rest of the AJAX post, for whatever reason, it doesn't matter what the actual field may or may not be set to.