Django Rest Framework Request Validation - python

Is there a way to validate params that are passed with the request without writing boilerplate code? Now I've got something like this:
project_id = kwargs['project_id']
try:
project_obj = Project.objects.get(id=project_id)
except Project.DoesNotExist:
return Response(
{'message': 'Requested project does not exist'},
status=status.HTTP_404_NOT_FOUND
)
except ValueError:
return Response(
{'message': 'Project id must be a number'},
status=status.HTTP_400_BAD_REQUEST
)
I've read about Serializers' Validation but I'm not sure it's the right thing. Without handling these exceptions, Django just returns 500, it's not the behavior I actually want.

You are going to wind up writing some boilerplate, but you can clean what you have up by specifying relevant field types like using an IntegerField would spare you checking the type and get_object_or_404 would spare you the try/ catch in those cases.
However, you really want to use the real right field for this job and it's going to be a Relation field, most likely PrimaryKeyRelatedField.

Related

Add a field error in Django Rest Framework validate method?

In Django Rest Framework (and Django), traditionally we check fields in validate_<field> method, and make more global checks in validate method.
However, look at this code snippet:
def validate(self, data):
# ....
try:
customer.activate(data['signup_code'], data['raw_password'])
except BadCodeProvided:
raise ValidationError(MSG_WRONG_ACTIVATION_CODE)
except SomeOtherException:
raise ValidationError(SOME_OTHER_MESSAGE)
Here, I'm forced to use validatemethod because I'm using 2 fields for my validation (signup_code and raw_password).
However, if an error occurs in a BadCodeProvided Exception, I know it's related to the signup_code field (and not the raw_password one) because of the exception raised here.
In the snippet code above, thiw will create a "non_field_error".
Question: is there a way in DRF to raise the same error but related to the "signup_code" field? (like it would be done in a validate_signup_code method).
Thanks
you can use serializers.ValidationError in serializer :
raise serializers.ValidationError({"myField": "custom message error 1",
"myField2": "custom message error 1"})
doc here Validator DRF

How can I use rest serializer.ValidationError in django forms?

I am new to Django and rest_framework. I have a password complexity rules script for 'new user' page.
If my complexity requirements satisfy the needs, it will return true. Else it raises serialiser.ValidationError.
I duplicated Django's forget password mechanism to apply my password rules.
When it raises an error, application crashes like below.
Exception Type: ValidationError
Exception Value:
[u"The two password fields didn't match."]
Is it possible to use serializer errors as form errors {{ form.new_password1.errors }}?
It's possible to write your own custom exception handler to return the error in a preferred format.
The official documentation, at the bottom of the page, says:
The ValidationError class should be used for serializer and field validation, and by validator classes. It is also raised when calling
serializer.is_valid with the raise_exception keyword argument:
The generic views use the raise_exception=True flag, which means that you can override the style of validation error responses globally
in your API. To do so, use a custom exception handler, as described
above.
serializer.is_valid(raise_exception=True)
By default this exception results in a response with the HTTP status
code "400 Bad Request"
Please read here to create your custom handler.

Django Rest Framework - Best way to deal with API parameter validation errors?

What would be an efficient way to construct error messages during HTTP request parameter validation and serialize them?
Currently, as I have it:
except Section.DoesNotExist:
return Response(headers = {'INTERNAL_MSG': 'SECTION_NOT_FOUND',
'INTERNAL_CODE': '400'},
status = status.HTTP_200_OK)
But, it seems to me this is a not a good way to do it, since I am injecting internal error messages into the HTTP protocol that have nothing to do with HTTP topology.
Preferably, I would like to do something like Twitter, Facebook, and others are doing in their APIs:
{"errors":[{"message":"Sorry, that page does not exist","code":34}]}
So, could you please share your approach to dealing with errors and returning them?
Very much appreciated, thanks!
Django REST framework handles errors internally and in general does a pretty good job of converting them into reasonable responses that can be parsed by clients. This is done by the internal exception handling, which is open to being extended for special cases.
Usually when you run into a case where an object was specified that does not exist, you raise a 404 Not Found error. Django actually has a helper for this, called get_object_or_404, as it tends to be a very common bit of logic, especially in APIs. Django REST framework will convert this Http404 error that is raised and convert it in to the following response that has a 404 HTTP error code.
{
"detail": "Not found"
}
Now, this format (and object with a detail key) is not restricted to only Http404 errors, it works for any error that subclasses APIError provided by Django REST framework.
So in your example, you could raise a similar exception by doing
ex = APIError("Section not found")
ex.status_code = 404
raise ex
Or by using get_object_or_404 and letting Django REST framework handle it
from django.shortcuts import get_object_or_404
section = get_object_or_404(Section.objects, pk=1234)
Of course, you can still override the exception handler to format these errors how you want.
def exception_handler(exc):
from django.http import Http404
from rest_framework import status
exc_data = {
"errors": [],
}
if isinstance(exc, Http404):
exc_data["errors"].append({
"message": "Sorry that page does not exist",
"code": 404,
})
else if isinstance(exc.detail, list):
for message in exc.detail:
exc_data["errors"].append({
"message": message,
"code": exc.status_code,
}
else:
exc_data["errors"].append({
"message": exc.detail,
"code": exc.status_code,
})
return Response(exc_data, status=status.HTTP_200_OK)
Note that this will give all error responses a status code of 200, and embed the actual HTTP status code within the body. This is useful for some applications, but it is usually recommended to only use the 200 status code for success responses.

The response content must be rendered before it can be iterated over

I have the following views.py
class FilterView(generics.ListAPIView):
model = cds_composite_csv
serializer_class = cds_compositeSerializer
def get_queryset(self):
filename = self.request.GET.get('filename')
model = get_model('markit', 'cds_composite_csv')
filedate = self.request.GET.get('filedate')
if UserFile.objects.filter(user=self.request.user, file__filename=filename).exists():
queryset = model.objects.using('markitdb').filter(Date__contains=filedate)
return queryset
else:
content = {
'status': 'Request Failed.'
}
return Response(content)
I understand the reason it's failing is because of the else and the fact that it's trying to use the serializer from the class view. How do I get it to ignore the serializer if the else clause is met?
Do you really want explicitly to return 'status':'Request Failed'?
I would rather place raise Http404 in the else block and remove everything else.
If I was supposed to write a client to consume your API I would first of all check if I got status code 200 before I proceed further. Here I would get 'HTTP 200 OK', but not an expected result.
The above is just one possible solution, you could also make try-except or maybe something else, but I would certainly advise against custom Request failed. It's just my opinion.

Django REST Framework: return 404 (not 400) on POST if related field does not exist?

I'm developing a REST API which takes POST requests from some really brain-dead software which can't PATCH or anything else. The POSTs are to update Model objects which already exist in the database.
Specifically, I'm POSTing data for objects with a related field (a SlugRelatedField, as the POSTer knows the 'name' attribute but NOT the 'pk'). However, I need to return a 404 if the POSTer sends data where the 'name' returns nothing on the SlugRelatedField (e.g. the related object does not exist). I've been through this with a debugger but it seems that DRF uses some Django signals magic to do it The Way DRF Does Itâ„¢, which is to return a 400 BAD REQUEST. I don't know how to modify this - only when it's the above condition and not a true 400-worthy POST - into a 404.
By the way, pre_save() in my view is NOT executing during execution of the failing test.
Here's the serializer:
class CharacterizationSerializer(serializers.ModelSerializer):
"""
Work-in-progress for django-rest-framework use. This handles (de)serialization
of data into a Characterization object and vice versa.
See: http://www.django-rest-framework.org/tutorial/1-serialization
"""
creator = serializers.Field(source='owner.user.username')
sample = serializers.SlugRelatedField(slug_field='name',
required=True,
many=False,
read_only=False)
class Meta:
model = Characterization
# leaving 'request' out because it's been decided to deprecate it. (...maybe?)
fields = ('sample', 'date', 'creator', 'comments', 'star_volume', 'solvent_volume',
'solution_center', 'solution_var', 'solution_minimum', 'solution_min_stddev',
'solution_test_len',)
And here's the view where pre_save isn't being run in the given test (but does get run in some others):
class CharacterizationList(generics.ListCreateAPIView):
queryset = Characterization.objects.all()
serializer_class = CharacterizationSerializer
permission_classes = (AnonPostAllowed,) # #todo XXX hack for braindead POSTer
def pre_save(self, obj):
# user isn't sent as part of the serialized representation,
# but is instead a property of the incoming request.
if not self.request.user.is_authenticated():
obj.owner = get_dummy_proxyuser() # this is done for CharacterizationList so unauthed users can POST. #todo XXX hack
else:
obj.owner = ProxyUser.objects.get(pk=self.request.user.pk)
# here, we're fed a string sample name, but we need to look up
# the actual sample model.
# #TODO: Are we failing properly if it doesn't exist? Should
# throw 404, not 400 or 5xx.
# except, this code doesn't seem to be run directly when debugging.
# a 400 is thrown; DRF must be bombing out before pre_save?
obj.sample = Sample.objects.get(name=self.request.DATA['sample'])
And for good measure, here's the failing test:
def test_bad_post_single_missing_sample(self):
url = reverse(self._POST_ONE_VIEW_NAME)
my_sample_postdict = self.dummy_plqy_postdict.copy()
my_sample_postdict["sample"] = "I_DONT_EXIST_LUL"
response = self.rest_client.post(url, my_sample_postdict)
self.assertTrue(response.status_code == 404,
"Expected 404 status code, got %d (%s). Content: %s" % (response.status_code, response.reason_phrase, response.content))
If I put a breakpoint in at the self.rest_client.post() call, the response already has a 400 in it at that point.
You can use a Django Shortcut for that, getting the obj.sample:
from django.shortcuts import get_object_or_404
obj.sample = get_object_or_404(Sample, name=self.request.DATA['sample'])
Instead of using pre_save why not override post in your API view:
def post(self, request, *args, **kwargs):
...other stuff
try:
obj.sample = Sample.objects.get(name=self.request.DATA['sample'])
...or whatever other tests you want to do
except:
return Response(status=status.HTTP_404_NOT_FOUND)
response = super(CharacterizationList, self).post(request, *args, **kwargs)
return response
Make sure you import DRF's status:
from rest_framework import status
Also, note you will likely want to be more specific with the Exceptions you catch. Django's get method will return either DoesNotExist if nothing matches or MultipleObjectsReturned if more than one object matches. The relevant documentation:
Note that there is a difference between using get(), and using
filter() with a slice of [0]. If there are no results that match the
query, get() will raise a DoesNotExist exception. This exception is an
attribute of the model class that the query is being performed on - so
in the code above, if there is no Entry object with a primary key of
1, Django will raise Entry.DoesNotExist.
Similarly, Django will complain if more than one item matches the
get() query. In this case, it will raise MultipleObjectsReturned,
which again is an attribute of the model class itself.

Categories