I have made a custom middleware and if it throws exception, it is automatically handled by BaseHandler function get_response (django.core.handlers.base). My exception ObjectDoesNotExist, by default, is not handled and therefore a function handle_uncaught_exception takes care of it. But it considers all error as 500 Internal Server (even if it is a 400 Bad request) and returns default django error html template with 500 code. Here is my code:
custom_middleware.py:
class CustomMiddleware(object):
"""
custom middleware
"""
def process_request(self, request):
try:
# if agent doesn't exists then return json response
agent = Agent.objects.get(agent_token=agent_token)
except ObjectDoesNotExist as e:
raise Exception(exception_code=400, detail=e, response_msg="You are not an authorized agent", request= request_data)
This automatically is handled by BaseHandler.get_response()
So to solve the problem what I did was inherited the BaseHandler and override get_response function adding exception handler for my error and which gives a json response rather than HTML template.
base.py
from django.core.handlers.base import BaseHandler
class ExceptionHandler(BaseHandler):
def get_response(self, request):
my code
But this doesn't help as it calls the base class method even after overriding.
Here is the traceback:
File "/webapp/apps/middleware/custom_middleware.py" in process_request
49. agent = Agent.objects.get(agent_token=agent_token)
File "lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
122. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "lib/python3.4/site-packages/django/db/models/query.py" in get
387. self.model._meta.object_name
During handling of the above exception (Agent matching query does not exist.), another exception occurred:
File "lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
123. response = middleware_method(request)
File "webapp/apps/middleware/request_log.py" in process_request
54. raise Exception(exception_code=400, detail=e, response_msg="You are not an authorized agent", request= request_data)
Any help
Related
In our project, we have used django SessionMiddleware to handle users sessions and it's working fine. The only problem here is when PermissionDenied exceptions happens, an error and its traceback will be printed out in the console! However as expected, by raising that exception, the 403 page will show to the user, but I think it doesn't seem rational, because the middleware here is handling the exception! Just like not found exception, I expect no error in the console. Is there anything wrong?!
here is the middleware settings:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'axes.middleware.AxesMiddleware',
]
And here's the printed error:
Forbidden (Permission denied): /the/not_allowed/page
Traceback (most recent call last):
File "/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python3.8/contextlib.py", line 75, in inner
return func(*args, **kwds)
File "/our_project/base/decorators.py", line 88, in wrapper
return view_func(request, *args, **kwargs)
File "/venv/lib/python3.8/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/venv/lib/python3.8/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view
if test_func(request.user):
File "/venv/lib/python3.8/site-packages/django/contrib/auth/decorators.py", line 70, in check_perms
raise PermissionDenied
django.core.exceptions.PermissionDenied
From this comment
In this example the exception is raised from permission_required
decorator in django.contrib.auth.decorators. I passed
raise_exception=True to this decorator to make it raise exception instead of redirecting to login page
So, it is clear that you have set raise_exception=True in your decorator.
and from the doc
If the raise_exception parameter is given, the decorator will raise PermissionDenied, prompting the 403 (HTTP Forbidden) view instead of redirecting to the login page.
So, technically, when the conditions are not met, Django will raise an exception. But, depending on the value of 403.html, Django will show you either the plain 403 page or custom HTML response.
I expect no error in the console. Is there anything wrong?
There is nothing wrong here (or I couldn't see anything).
So, if you want to omit the traceback from the console, you may need to write a error handling middleware to handle this exception.
# error handling middleware
from django.core.exceptions import PermissionDenied
from django.shortcuts import render
class PermissionDeniedErrorHandler:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_exception(self, request, exception):
# This is the method that responsible for the safe-exception handling
if isinstance(exception, PermissionDenied):
return render(
request=request,
template_name="your_custom_403.html",
status=403
)
return None
Note: Do not forgot to bind this middleware in your MIDDLEWARE settings.
and thus, you will not get any error tracebacks in the console.
Cheers!!!
I'm using django-revproxy and Django REST Framework in my project. And I'm exposing an API where users get analytics about their chatbots, and it works as the following:
The user send requests the analytics from the Django project
Django project, checks if the user is authenticated and owns that chatbot
if True it contacts another external service.
My urls.py:
# urls.py
urlpatterns = (
url(r'^analytics/(?P<path>.*)$', api.AnalyticsFunctionsProxyView.as_view()),
)
And in my view:
# views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from revproxy.views import ProxyView
from .permissions import HasChatBotPermission
...
class AnalyticsFunctionsProxyView(ProxyView):
upstream = settings.ANALYTICS_FAAS_URL
def parse_body(self, request):
if isinstance(request, rest_framework.request.Request):
return request.data
return super(AnalyticsFunctionsProxyView, self).parse_body(request)
#classmethod
def as_view(cls, *args, **kwargs):
view = super(AnalyticsFunctionsProxyView, cls).as_view(*args, **kwargs)
view = permission_classes((IsAuthenticated, HasChatBotPermission,))(view)
view = authentication_classes([TokenAuthentication, JSONWebTokenAuthentication])(view)
view = api_view(['GET', 'POST'])(view)
return view
And my HasChatBotPermission permissions
#permissions.py
class HasChatBotPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
bot_name = request.data.get('name')
user = request.user
self.message = 'Permission denied for {}'.format(name)
return ChatBot.objects.filter(user=user, project_name=project_id).exists()
except Exception:
self.message = 'Permission denied, no project_id was defined!'
return False
When the view is called it raises this exception:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 379, in __getattribute__
return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/exception.py", line 42, in inner
response = get_response(request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/fcmam5/anaconda3/lib/python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 477, in dispatch
response = self.handle_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 437, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 474, in dispatch
response = handler(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/decorators.py", line 52, in handler
return func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 204, in dispatch
proxy_response = self._created_proxy_response(request, path)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 139, in _created_proxy_response
request_payload = request.body
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 383, in __getattribute__
return getattr(self._request, attr)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/http/request.py", line 264, in body
raise RawPostDataException("You cannot access body after reading from request's data stream")
django.http.request.RawPostDataException: You cannot access body after reading from request's data stream
The issue is caused by this line bot_name = request.data.get('name') in my permissions.py, when I pass a string directly it passes without any problems.
My question is:
How can I access the request body without having this error? Why I'm having this error?
Is there a better solution for checking the user permission with Django revproxy.
This is my first question in Stackoverflow, sorry if my question is not clea, and for my poor English :)
You're having this error because django-revproxy attempts to read the raw request body so it can create a proxy request to the upstream server.
However, with Django's (and WSGI's, and buffering) semantics, this isn't possible once you've accessed the request body as anything but a raw stream, which you do when you request.data.get('name'). This parses the request body as JSON, HTTP multipart, whatever, depending on DRF's request negotiation configuration, and consumes the stream.
There are two ways you could get around this, as far as I see:
pass bot_name somewhere else than the body; a query string parameter, an HTTP header, part of the URL, for instance, so you don't need to access the body, or
make the backend request yourself using requests instead of reverse-proxying (which is basically the same thing, but with added magic to try and copy the request through as-is).
I have a problem that I don't know how to make it work.
urls.py:
urlpatterns = [
url(r'athletes/search$', SearchAthletes.as_view()),
url(r'athletes/([0-9]+)$', ViewAthlete.as_view())
]
views.py:
class ViewAthlete(APIView):
def get(self, request, id, format=None):
athlete = Athlete.objects.get(id=id)
serializer = AthleteSerializer(athlete)
return Response(serializer.data)
test.py:
def test_view_athlete(self):
tmp = Athlete.objects.order_by('?')[0]
request = self.factory.get('/_api/v1/athletes/' + str(tmp.id))
request.user = AnonymousUser()
response = ViewAthlete.as_view()(request)
self.assertEquals(response.data.id, tmp.id)
I keep getting the following error:
Traceback (most recent call last):
File "/tests.py", line 44, in test_view_athlete
response = ViewAthlete.as_view()(request)
File "/venv/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/rest_framework/views.py", line 474, in dispatch
response = self.handle_exception(exc)
File "/venv/lib/python3.5/site-packages/rest_framework/views.py", line 471, in dispatch
response = handler(request, *args, **kwargs)
TypeError: get() missing 1 required positional argument: 'id'
To my understanding, the problem is that, there is no id parameter passed to get function of ViewAthelete view class. What is the reason for this? In development environment (not testing), it displays the data but testing environment doesn't recognize the arguments from the route.
AFAIK urlpatterns are considered when testing through the full django request stack, e.g.: through django.test.Client, using it's get/post methods
When testing your view directly (MyView.as_view()(request)) the whole url resolver logic is bypassed, and then the args/kwargs need to be supplied by the caller (e.g.: MyView.as_view()(request, 'arg1', 'arg2', id='34'))
As zsepi says, your URLs aren't used here. To avoid repeating the arguments, rather than calling the view directly you could use the test client to "call" the URL: another advantage of doing this is that the middleware runs, so you don't need to assign the user attribute separately.
response = self.client.get('/_api/v1/athletes/' + str(tmp.id))
I have the following view in a Django app:
class VideoCreateView(View):
"""
Handle creating a video
"""
template_name = 'albums/video_create.html'
success_template_name = 'albums/video_created.html'
def get(self, request, *args, **kwargs):
"""
Display the confirmation dialogue
"""
# First, get the album
pk = kwargs['pk']
try:
album = Album.objects.get(id=pk)
except Album.DoesNotExist:
raise Http404
# Render the template
return render(request, self.template_name)
def post(self, request, *args, **kwargs):
"""
Generate the video
"""
# First, get the album
pk = kwargs['pk']
try:
album = Album.objects.get(id=pk)
except Album.DoesNotExist:
raise Http404
# Trigger build
if len(album.photo_set.all()) > 0:
celery_generate_video.delay(album)
# Send response
return render(request, self.success_template_name)
else:
raise HttpResponseBadRequest
This requires an ID for a photo album in the URL, and if the album does not exist, it should raise a 404 error. I've written the following test to check this behaviour:
#mock.patch('storages.backends.s3boto.S3BotoStorage', FileSystemStorage)
def test_get_does_not_exist(self):
"""
Test GET requests
"""
pk = uuid.uuid4()
url = reverse('video_create', kwargs={
'pk': pk.urn[9:]
})
request = self.factory.get(url)
request.user = self.user
response = VideoCreateView.as_view()(request, pk=pk)
self.assertEqual(response.status_code, 404)
Unfortunately, it's raising the following error:
======================================================================
ERROR: test_get_does_not_exist (albums.tests.VideoCreateViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/matthew/Projects/digitallife/albums/views.py", line 200, in get
album = Album.objects.get(id=pk)
File "/home/matthew/Projects/digitallife/venv/lib/python3.4/site-packages/django/db/models/manager.py", line 127, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/matthew/Projects/digitallife/venv/lib/python3.4/site-packages/django/db/models/query.py", line 334, in get
self.model._meta.object_name
albums.models.DoesNotExist: Album matching query does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3.4/unittest/mock.py", line 1125, in patched
return func(*args, **keywargs)
File "/home/matthew/Projects/digitallife/albums/tests.py", line 573, in test_get_does_not_exist
response = VideoCreateView.as_view()(request, pk=pk)
File "/home/matthew/Projects/digitallife/venv/lib/python3.4/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/home/matthew/Projects/digitallife/venv/lib/python3.4/site-packages/django/views/generic/base.py", line 89, in dispatch
return handler(request, *args, **kwargs)
File "/home/matthew/Projects/digitallife/albums/views.py", line 202, in get
raise Http404
django.http.response.Http404
It looks to me like the exception isn't being caught. I have used this method for catching invalid objects in Django before, but that was in Python 2.7, so I wondered if it was something in Python 3 (this is my first Python 3 Django project). What am I doing wrong?
You're not doing anything wrong in the view. The test result shows it is doing exactly what you want: catching a DoesNotExist, and raising Http404 in response.
The issue is in your test. You're calling the view directly, which means you skip all the middleware that usually does things like handle exceptions and translate them into 404/500 responses. Instead of creating a request via the factory, you should use the built-in test client to call the view:
url = ...
response = self.client.get(url)
I have a very basic Django class-based view as follows. It doesn't really even do anything:
from django.views.generic import View, TemplateView
class index(TemplateView):
template_name = 'myApp/index.html'
def dispatch(self, request, *args, **kwargs):
return super(index, self).dispatch(request, *args, **kwargs)
However, when I load this view through the browser, I get the following error:
Exception Type: TypeError
Exception Value:
__init__() takes exactly 1 argument (2 given)
Exception Location: /Library/Python/2.7/site-packages/Django-1.6.5-py2.7.egg/django/core/handlers/base.py in get_response, line 112
Python Executable: /usr/bin/python
Python Version: 2.7.5
Python Path:
Why am I getting this error? And how to fix it please?
Below is more context around the location of the error:
/Library/Python/2.7/site-packages/Django-1.6.5-py2.7.egg/django/core/handlers/base.py in get_response
106. response = middleware_method(request, callback, callback_args, callback_kwargs)
107. if response:
108. break
109. if response is None:
110. wrapped_callback = self.make_view_atomic(callback)
111. try:
112. response = wrapped_callback(request, *callback_args, **callback_kwargs) ...
113. except Exception as e:
114. # If the view raised an exception, run it through exception
115. # middleware, and if the exception middleware returns a
116. # response, use that. Otherwise, reraise the exception.
117. for middleware_method in self._exception_middleware:
118. response = middleware_method(request, e)