In order to interact with slack, a server needs to be able to validate requests based on some cryptographic hashing. If this check returns false, the server should respond with a 400. It seems sensible to do this as a mixin:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
This gives the error "accepted_renderer not set on Response" Based on a SO question, I added the following:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
response = Response(status=status.HTTP_400_BAD_REQUEST)
response.accepted_renderer = JSONRenderer
response.accepted_media_type = "application/json"
response.renderer_context = {}
return response
But this gives the error: AttributeError: 'NoneType' object has no attribute 'get_indent'
Why does it need an accepted_renderer, given that it is only responding with an HTTP status code, with no additional data? What is the easiest way of getting around this?
Following suggestion in answer to make EmptyResponse object inheriting from Response:
Traceback (most recent call last):
File "path/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "path/lib/python3.8/site-packages/django/utils/deprecation.py", line 96, in __call__
response = self.process_response(request, response)
File "path/lib/python3.8/site-packages/django/middleware/common.py", line 106, in process_response
if response.status_code == 404:
AttributeError: 'dict' object has no attribute 'status_code'
At first the solution: your second approach is fine, you only need to instantiate the JSONResponse
class (DRF does this in the get_renderers
method of views.APIView
):
response.accepted_renderer = JSONRenderer()
Background:
WSGIHandler
(inherited from Basehandler
) calls response.render()
to render the responseResponse
(inherited from SimpleTemplateResponse
) object has a render
method that gets the rendered content via the rendered_content
property (which calls the render
method of the renderer with the passed data, media type and context)DEFAULT_RENDERER_CLASSES
/APIView.renderer_classes
setting and the Aceept
header passed by client; the selected renderer is set in the HttpRequest
object as accepted_renderer
and the media type as request.accepted_media_type
attributesResponse
object also needs the renderer_context
attribute; for example, views.APIView
sets the current view, request, and arguments as renderer_context
dictNow it should be clear why you need the attributes with Response
object -- to get the renderer, media type and to pass any extra context that might be needed by the selected renderer.
You've added an answer, where you're setting the above mentioned attributes and then from the renderer returning an empty dict as response. If you want to follow that route, a much easier and cleaner option would be to create a subclass of Response
and return an empty dict from the render
method e.g.:
class EmptyResponse(rest_framework.response.Response):
def render(self):
# You can have your own rendered content here
self.content = b''
return self
Now only returning the EmptyResponse
object would do, no need to add the renderer related attributes:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return EmptyResponse(status=status.HTTP_400_BAD_REQUEST)
Now, unless you're adding some custom content, the deferred rendering is not needed; you can directly return HttpResponse
object:
from django.http import HttpResponse
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
And if you want, you can pass the content
(as bytes) while initializing HttpResponse
. But if for some reason, you need lazy rendering, you need to use Response.render
.