Is it possible to write a class based (not function) custom error page handler?
for example this:
def handler404(request, *args, **argv):
response = render_to_response('404.html', {},
context_instance=RequestContext(request))
response.status_code = 404
return response
but with class
Related
I'm trying to request swagger of Django app from Django view. To clarify what I mean, this is an example:
#action(methods=['GET'], detail=False, permission_classes=[AllowAny])
def get_swagger(self, request, *args, **kwargs):
try:
be_url = "http://localhost:8000/?format=openapi"
r = requests.get(be_url)
except Exception as e:
return Response('data': str(e))
return Response('data': r.json())
But when I request this endpoint response goes very long and then fails with a 404.
How can I return a different error code with django rest framework's serializers?
I have in my serializer.py file:
def create(self, validated_data):
if 'Message' not in validated_data:
# If message is blank, don't create the message
return PermissionDenied()
But when I do that, it just returns 201 with the body {"deleted":null} instead of returning a 403 error.
How can I make it return the 403 error?
You can override the validate_message method as follow:
from rest_framework.exceptions import ValidationError
def validate_message(self, message):
if not message:
raise ValidationError('error message here')
return message
Note that the ValidationError will return a 400 Bad Request status code which is better for when a required field is missing from the POST data
#Marcell Erasmus's answer is nice, but it's not covered the status code part (how to return a HTTP 403 status code)
First, you need to add a Custom exception class as below,
from rest_framework import exceptions
from rest_framework import status
class CustomAPIException(exceptions.APIException):
status_code = status.HTTP_403_FORBIDDEN
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and use the class wherever you want by ,
if some_condition:
raise CustomAPIException({"some": "data"})
One of the most advantage of this particular class is that you could raise API exception with custom status codes by specifying the status_code parameter
Ex.
if some_condition:
raise CustomAPIException({"some": "data"},status_code=status.HTTP_409_CONFLICT)
I have a generic class based view:
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
# Rest of definition
And in my urls.py, I have:
urlpatterns = [
url(r'^(?P<pk>[0-9]+)/$', views.ProjectDetails.as_view())
]
When the API is called with a non-existent id, it returns HTTP 404 response with the content:
{
"detail": "Not found."
}
Is it possible to modify this response?
I need to customize error message for this view only.
This solution affect all views:
Surely you can supply your custom exception handler: Custom exception handling
from rest_framework.views import exception_handler
from rest_framework import status
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)
# Now add the HTTP status code to the response.
if response.status_code == status.HTTP_404_NOT_FOUND:
response.data['custom_field'] = 'some_custom_value'
return response
Sure you can skip default rest_framework.views.exception_handler and make it completely raw.
Note: remember to mention your handler in django.conf.settings.REST_FRAMEWORK['EXCEPTION_HANDLER']
Solution for specific view:
from rest_framework.response import Response
# rest of the imports
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def handle_exception(self, exc):
if isinstance(exc, Http404):
return Response({'data': 'your custom response'},
status=status.HTTP_404_NOT_FOUND)
return super(ProjectDetails, self).handle_exception(exc)
It's possible by overriding specific methods like update, retrieve as:
from django.http import Http404
from rest_framework.response import Response
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def retrieve(self, request, *args, **kwargs):
try:
return super().retrieve(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
def update(self, request, *args, **kwargs):
try:
return super().update(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
I have written
class CustomApiException(APIException):
#public fields
detail = None
status_code = None
# create constructor
def __init__(self, status_code, message):
#override public fields
CustomApiException.status_code = status_code
CustomApiException.detail = message
CustomApiException.message = message
which is working for APIView but for TemplateView it gives error. What is the proper way to write custom API Exception which will work for both views.
In your views define the template view as following
from django.http import HttpResponseForbidden
#rest of the views
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException:
return HttpResponseForbidden()
This will raise a Error 403
If you want to render your own template instead of raising HttpResponseForbidden, you could pass the exception variables to the view in following way and write the response of the view appropriately
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException as e:
status_code = e.status_code
detail = e.message
message = e.message
#Handle the response here.
I have a middleware from secretballot
class SecretBallotMiddleware(object):
def process_request(self, request):
request.secretballot_token = self.generate_token(request)
def generate_token(self, request):
raise NotImplementedError
class SecretBallotIpMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
return request.META['REMOTE_ADDR']
class SecretBallotIpUseragentMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
s = ''.join((request.META['REMOTE_ADDR'], request.META.get('HTTP_USER_AGENT', '')))
return md5(s.encode('utf8')).hexdigest()
and I use this in my view (e.g. 'different_view'):
token = request.secretballot_token
How can I change this token form request in my tests?
class BasicTest(TestCase):
def test_one(self):
self.client.request['secretballot_token']='asd' #??
response = self.client.post('/different_view/')
And I want to send post in this test to /different_view/ but with my own, changed token.
If you're looking to the test the view without running through the middleware, you can use RequestFactory to generate a request and pass it directly into your view.
def test_one(self):
# create a request
request = RequestFactory().post('/different_view')
request.secretballot_token = 'asd'
# function based view
response = different_view(request)
# class based view
response = DifferentView.as_view()(request)
If you need to test the middleware along with the view, you should pass HTTP headers in your tests instead
def test_one(self):
# pass http headers
response = self.client.post(path='/different_view'/,
REMOTE_ADDR='12.34.56.78',
HTTP_USER_AGENT='...'
)