I am having difficulty raising a validation error using the Django Rest Framework.
I have an owner field. The owner field needs to access the request object. The documentation suggests using the following approach:
def pre_save(self, obj):
obj.owner = self.request.user
My problem is how to raise a validation error if this code fails. I tried raising an error inside the pre_save method, but this doesn't work properly. It actually results in an HTML response coming back from Django, rather than a message from the Django Rest Framework.
Use the django rest framework exceptions. For example:
from rest_framework.exceptions import ParseError
...
parsed_data = self.parse(some_data)
if not parsed_data:
raise ParseError('Some error occurred')
Also note that you won't see a 404 in there, that's because it uses the django.http.Http404 exception. These are returned by the API in a nice way.
Note:
If you are doing a significant amount of validation you might want to look at placing your logic in the serializer.
Related
Lately, I found out the elementary truth related to exception handling - our server should rarely (preferably, never) through 5XX error.
Having said that, let's consider a REST view with some parameters, and a respective URL.
urlpatterns = [
url(r'^some_view/', some_view),
]
#api_view(['POST'])
def some_view(request):
# ...
param1 = request.data['param1']
In this code, I have to manually handle the exception where the developer calls some_view without assigning value to param1, otherwise - if I don't handle this case explicitly - they will get 500 error (MultipleKeyValueError) which is bad. And we have a dilemma:
Handling it will cause irritating and repetitive try-except blocks, especially when we have multiple params
Not handling will lead to 500 error
The solution is to rewrite the view this way:
urlpatterns = [
url(r'^some_view/(?P<param1>\w+/', some_view),
]
#api_view(['POST'])
def some_view(request, param1):
# ...
Here Django will throw 400 exception - as opposed to 500 - saying that url is not found. But on the other hand, the first option (where I use request.data['param1']) gives the pleasant benefit that I can call the REST resource not only from an outside app, but also from my web app submitting params by serializing HTML form.
So here I am asking about the best practice. How do you, guys, handle this situation? Do you explicitly write try-except blocks watching for missing params, or do you use the url-parameters options, or maybe there's some third option that I didn't mention here ?
If you're writing REST views, you should be using Django Rest Framework. One of the benefits of that framework is that you define serializers in which you declare which fields you accept and which are mandatory, just as you do with forms in vanilla Django. You can therefore return validation errors rather than 500s.
So here I am asking about the best practice. How do you, guys, handle
this situation? Do you explicitly write try-except blocks watching for
missing params, or do you use the url-parameters options, or maybe >there's some third option that I didn't mention here ?
If I am trying to handle a common pattern for exception handling across views, I typically us a “third option” of writing a decorator that handles the exception and returns a JSON response with success = False:
from functools import wraps
def handle_missing_key(func)
#wraps(func)
def _decorator(*args, **kwargs):
try:
func(*args, **kwargs)
except KeyError as ex:
return JsonResponse({
'success': False,
'error': '%s' % ex
})
return _decorator
Then for your methods you can just do:
#handle_missing_key
#api_view(['POST'])
def some_view(request, param1):
pass
This way you're just sending back a 200 response but the JSON defines the error message and lets any calling application know what is missing. You can find the missing key using KeyError.args or the error message in the JsonResponse. If you want to send back a response other than a JsonResponse (i.e., status response 400 or the like), you can, of course, use something like this:
response = HttpResponse(status=400)
response.reason_phrase = 'Key %s is missing' % ex
return response
I have a similar issue as this question about validating data in the Django REST Framework outside of a serializer:
Raise Validation Error In Pre_Save Using Django Rest Framework
My code:
def pre_save(self, obj):
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError('An unpermitted user has been included')
From the trace it looks like it's trying to send the response but it fails with:
"" needs to have a value for field before this many-to-many relationship can be used.
UPDATE:
I moved raising the ParseError into a get_serializer_class() method like so:
def get_serializer_class(self):
if 'users' in self.request.DATA:
# make sure the users are allowed
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError(detail='Unpermitted user')
return serializer
And this raises the exception, however, it does not return it using the REST framework's JSON response. Rather I get the django stack trace and a 500 error, which is not good.
Thanks!
Have a look at APIView's handle_exception — this is where DRF processes exceptions raised during the request.
From the docs:
The default implementation handles any subclass of rest_framework.exceptions.APIException, as well as Django's Http404 and PermissionDenied exceptions, and returns an appropriate error response.
If you need to customize the error responses your API returns you should subclass this method.
So you need to override this to handle ParseError exceptions too.
Also check out the DRF docs on Exceptions.
I hope that helps.
When the exception is raised in the pre_save method(), post_save(), or even in a post() method for the viewclass, it was being handled correctly by Django-REST-Framework. Had I been using curl or similar, the error would have been returned correctly.
This actually is a bug in the browsable API, which is what I was using to test - sending the data using the "Raw data" form. When trying to render the html response, DRF apparently tries to capture the "context" of the post. In this case, it wanted the saved/completed post.
That did not exist, so a Django rendering error was being thrown and that confused me.
When testing using curl, the response was accurate.
Note that putting it in the get_serializer_class() like I did caused it to go outside of the DRF exception handler so Django rendered it correctly and showed the error was being thrown correctly.
Is there any way to launch custom errors in the admin site like that?:
Currently I throw the error with
raise forms.ValidationError('error')
but shows the debug error screen
Where are you putting the raise forms.ValidationError('error')?
In your form clean() method is a good place to raise custom errors. You can even do def clean_fieldname() to preform specific validation for one field. From the django docs
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise forms.ValidationError("You have forgotten about Fred!")
# Always return the cleaned data, whether you have changed it or
# not.
return data
This link also could help
i have something like this in my views.py
instance = get_object_or_404(register,pk=request.user.id)
Now if there is no related object against this user i receive i standard django 404 eror
saying no matches found.
what i want here is instead of receiving this 404 error redirect it to another page say "something.html". but i dont know how. i am using method = "POST"
is there any way to redirect it to other page instead of receiving a 404 error
using a try/except block you can redirect if the object is not found
try:
instance = register.get(pk=request.user.id)
except register.DoesNotExist:
return HttpResponseRedirect('url that renders something.html')
FYI, definition of django get_object_or_404 function looks like this
def get_object_or_404(klass, *args, **kwargs):
"""
Uses get() to return an object, or raises a Http404 exception if the object
does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
from the docs, If you raise Http404 at any point in a view function, Django will catch it and return the standard error page for your application, along with an HTTP error code 404.
look at customizing error views if you want to render a custom 404.html based on the context variables
Depending on what the Big Picture is, you might want to look at django.contrib.flatpages and see what they are doing. Basically they are dealing with the 404 in middleware and then looking at the path to decided if there is something they can return. I have used variations on this on a couple of sites.
How do I add errors to the top of a form after I cleaned the data? I have an object that needs to make a REST call to an external app (google maps) as a pre-save condition, and this can fail, which means I need my users to correct the data in the form. So I clean the data and then try to save and add to the form errors if the save doesn't work:
if request.method == "POST":
#clean form data
try:
profile.save()
return HttpResponseRedirect(reverse("some_page", args=[some.args]))
except ValueError:
our_form.errors.__all__ = [u"error message goes here"]
return render_to_response(template_name, {"ourform": our_form,},
context_instance=RequestContext(request))
This failed to return the error text in my unit-tests (which were looking for it in {{form.non_field_errors}}), and then when I run it through the debugger, the errors had not been added to the forms error dict when they reach the render_to_response line, nor anywhere else in the our_form tree. Why didn't this work? How am I supposed to add errors to the top of a form after it's been cleaned?
You really want to do this during form validation and raise a ValidationError from there... but if you're set on doing it this way you'll want to access _errors to add new messages. Try something like this:
from django.forms.util import ErrorList
our_form._errors["field_name"] = ErrorList([u"error message goes here"])
Non field errors can be added using the constant NON_FIELD_ERRORS dictionary key (which is __all__ by default):
from django import forms
errors = my_form._errors.setdefault(forms.forms.NON_FIELD_ERRORS, forms.util.ErrorList())
errors.append("My error here")
In Django 1.7 or higher, I would do:
form.add_error(field_name, "Some message")
The method add_error was added in 1.7. The form variable is the form I want to manipulate and field_name is the specific field name or None if I want an error that is not associated with a specific field.
In Django 1.6 I would do something like:
from django.forms.forms import NON_FIELD_ERRORS
errors = form._errors.setdefault(field_name, form.error_class())
errors.append("Some message")
In the code above form is the form I want to manipulate and field_name is the field name for which I want to add an error. field_name can be set to NON_FIELD_ERRORS to add an error not associated with a specific field. I use form.error_class() to generate the empty list of error messages. This is how Django 1.6 internally creates an empty list rather than instantiate ErrorList() directly.
You should raise the validationerror.
Why not put the verification within the form's clean method
class ProfileForm(forms.Form):
def clean(self):
try:
#Make a call to the API and verify it works well
except:
raise forms.ValidationError('Your address is not locatable by Google Maps')
that way, you just need the standard form.is_valid() in the view.
You're almost there with your original solution. Here is a base Form class I built which allows me to do the same thing, i.e. add non-field error messages to the form:
from django import forms
from django.forms.util import ErrorDict
from django.forms.forms import NON_FIELD_ERRORS
class MyBaseForm(forms.Form):
def add_form_error(self, message):
if not self._errors:
self._errors = ErrorDict()
if not NON_FIELD_ERRORS in self._errors:
self._errors[NON_FIELD_ERRORS] = self.error_class()
self._errors[NON_FIELD_ERRORS].append(message)
class MyForm(MyBaseForm):
....
All my forms extend this class and so I can simply call the add_form_error() method to add another error message.
I'm not sure how horrible of a hack this is (I've only really worked on two Django projects up until this point) but if you do something like follows you get a separate error message that is not associated with a specific field in the model:
form = NewPostForm()
if something_went_horribly_wrong():
form.errors[''] = "You broke it!"
If the validation pertains to the data layer, then you should indeed not use form validation. Since Django 1.2 though, there exists a similar concept for Django models and this is certainly what you shoud use. See the documentation for model validation.