Django rest framework custom Response middleware - django

I use Django Rest Framework with rest_auth (/login, /logout/, /register...)
I have a Middleware, normally triggered by user_logged_in or user_logged_out signals.
In my Middleware, I want to use Response object from Rest framework.
My middleware.py
from django.contrib.sessions.models import Session
from django.contrib.auth import logout
from django.contrib.auth.models import AnonymousUser
from rest_framework.response import Response
from rest_framework import status
class OneSessionPerUserMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
if request.user.session_key != Session.objects.filter(session_key__in = request.user.session_key):
logout(request)
return Response(request)
return self.get_response(request)
I suppose that I pass my condition, but I get this error :
The response content must be rendered before it can be accessed
How to use correctly the API Response object in a middleware ?
And I don't understand really what is : self.get_response = get_response ?

return Response(request) is not something django can handle. Ps you are passing request object to Response what does that mean?
Actually Request and Response classes from rest framework are not compatible with django. rest framework wraps WSGIRequest (django's builtin) with Request object in APIView and after getting Response object from view, it creates HttpResponse object by unwrapping it. So Response object can only be used in rest framework views. You can use JsonResponse from django.http in middleware like this
from django.http import JsonResponse
if request.user.is_authenticated:
if request.user.session_key != Session.objects.filter(session_key__in = request.user.session_key):
logout(request)
return JsonResponse({'logout': True}) # or whatever data you want to return
return self.get_response(request)

First of all: if you are using something other than rest_framework.authentication.SessionAuthentication as an authentication_class, then request.user is set outside of middlewares (somewhere during View.dispatch)
If you are sure that request.user is always corresponding to request.user inside rest_framework views and just want to return a response:
A. Look at the APIView.finalize_response: it's relatively "complex" because it can use different renderers (depending on accept-content request header), it may change headers. If you can access instance of your view in the middleware, then you can call view.finalize_response(request, response), if you don't have it then you can try doing rest_framework.views.APIView().finalize_response(...)
B. You can use django.http.HttpResponse: manually generate string for body, and specify appropriate content_type. And django.http.JsonResponse may be handy too.
C. I assumed that instead of return Response(request) you really are doing something else (like Response(data=...)). If you just need to return results from view, then remove return Response(request), so that return self.get_response(request) kicks in. But you are making a logout, so maybe you should return some instance of django.http.HttpResponseRedirect

Related

Django, all views require login except `#login_not_required` decoratored views

Django is centered towards allow access to all the majority of the time, and restricting acces on the exception.
I am making a site whereby I would like access restricted the most of the time, and the rare case is open to all. Ensuring that one has added a #login_required decorate to all views is error prone.
I acheived this by creating a custom middleware like this. To keep track of open URLs, I defined a list, and then whenever I wished to open up a URL, I added it to the list, and the middleware checked the request path against this list, and allowed exceptions accordingly.
This method above works, but I often mess it up with changing urls, and other issues to do with code reuse on different sites.
Ideally I would like to create my own #login_not_requied decorator. How to get the class based view or function the request is going to call within the middleware, so I can check whether the view does not require login?
First create the decorator:
from functools import wraps
def login_not_required(obj):
"""Adds the attrbiute login_not_required = True to the object (func/class).
Use it as follows:
#login_not_required
class FooView(generic.View):
...
#login_not_required
def bar_view(request):
...
"""
#wraps(obj)
def decorator():
obj.login_not_required = True
return obj
return decorator()
Then create your own custom middleware:
from contextlib import suppress
from django.conf import settings
from django.shortcuts import redirect
NONE_AUTH_ACCOUNT_PATHS = [
settings.STATIC_URL,
'/accounts/login/',
'/accounts/password_reset/',
'/accounts/reset/',
'/favicon.ico',
'/terminal/login/',
'/terminal/login_failed/',
]
class RequireLoginCheck:
"""Middleware to require authentication on all views by default, except when allowed.
URLS can be opened by adding them to NONE_AUTH_ACCOUNT_PATHS, or by adding
the #login_not_required decorator.
Must appear below the sessions middleware because the sessions middleware
adds the user to the request, which is used by this middleware.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def _is_none_auth_path(self, path):
for none_auth_path in NONE_AUTH_ACCOUNT_PATHS:
if path.startswith(none_auth_path):
return True
return False
def _is_login_not_required(self, view_func):
with suppress(AttributeError):
# If a class with the #login_not_required decorator, will return True
return view_func.view_class.login_not_required
with suppress(AttributeError):
# If a function with the #login_not_required decorator, will return True
return view_func.login_not_required
return False
def process_view(self, request, view_func, view_args, view_kwargs):
"""https://docs.djangoproject.com/en/stable/topics/http/middleware/#other-middleware-hooks"""
if not (
request.user.is_authenticated
or self._is_login_not_required(view_func)
or self._is_none_auth_path(request.path)
):
if settings.LOGIN_URL != request.path:
# if next URL after login is the same login URL, then cyclic loop
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
else:
return redirect('%s?next=%s' % (settings.LOGIN_URL, '/'))
return None
Ensure you add your middleware to MIDDLEWARE bellow the sessions middleware, in settings.py!
If you have ways it can be improved, please comment below and we make it better for all.

Custom Middleware in Django with Exclusions

I require to check authentication via token during execution of certain views, while some views can be accessed without authentication.
So, how do i make a middleware and exclude some views from it.
Any other idea to solve this is appreciated.
I would suggest taking inspiration from the csrf middleware that Django provides
from django.utils.deprecation import MiddlewareMixin
class MyAuthenticationMiddleware(MiddlewareMixin):
def process_view(self, request, callback, callback_args, callback_kwargs):
if getattr(callback, 'my_exempt_flag', False):
return None
# Authentication goes here
# Return None if authentication was successful
# Return a HttpResponse with some error status if not successful
And create a decorator to wrap your views
from functools import wraps
def exempt_from_my_authentication_middleware(view_func):
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.my_exempt_flag = True
return wraps(view_func)(wrapped_view)
Can be used like so
#exempt_from_my_authentication_middleware
def my_view(request):
# TODO

Parsing POST request - object() takes no parameters

I try to access post request and check if user with email that is in post request already exist. When I try to send data to endpoint, I get error
TypeError: object() takes no parameters
my views.py
#csrf_exempt
class CheckIfEmailAvailable():
#csrf_exempt
def check(request):
email = request.POST.get("email")
if User.objects.filter(email=email).exists():
return Response({'status': 'not available'})
my url.py
url(r'^api/checkmail/', CheckIfEmailAvailable, name='check'),
What am I doing wrong ?
In this case better to use function in the url
from your_app.views import check
url(r'^api/checkmail/', check, name='check')
and your view will be like this (only function)
#csrf_exempt
def check(request):
email = request.POST.get("email")
if User.objects.filter(email=email).exists():
return JsonResponse({'status': 'not available'})
Also FYI if you want to use #csrf_exempt with classes you should use dispatch, you can get more info here
Example of JsonResponse
from django.http import JsonResponse
def your_view(request):
return JsonResponse({'foo':'bar'})
You need to inherit your class your generic django views for this to work. Url patterns expect a callable that can accept request (with args and kwargs) and return response. View.as_view class method returns a callable for you.
# In views file
from django.views.generic import View
...
class CheckIfEmailAvailable(View):
def get(self, request, *args, **kwargs):
return self.check(request) # or something else depends on your use case
# In urls file
from django.views.decorators.csrf import csrf_exempt
...
url(r'^api/checkmail/', csrf_exempt(CheckIfEmailAvailable.as_view()), name='check'),
Moreover csrf_exempt decorator won't work (out of box) on classes, neither on bound methods for that matter. Better way should be to use them in urls.
If you had posted the full traceback, you would see that the error does not come from parsing the request; Django does not even get nearly that far.
You can't use a class as a view like that. You should have check as a standalone function, without a class, and refer to it directly in your urls.
Django does support class-based views, but they have a very specific structure, need to inherit from the proper base class, and be referenced in the urls.py in a specific way.

How to return an rest_framework.response object from a django custom middleware class?

I am trying to write a middleware class that ensures that the user is logged in.
But the problem is this middleware class will only be applicable to a small set of views and these views return a DRF's Response object rather then the HTTPResponse object and these views are also decorated using api_view.
So when I try to return a Response object from the middle ware class it raises this error.
assert renderer, ".accepted_renderer not set on Response"
AssertionError: .accepted_renderer not set on Response
I've searched a bit on SO and I guess that the error is somehow related to api_view decorator. But I am confused on how to solve this problem.
Any help is appreciated. :)
I've just recently hit this problem. This solution doesn't use the Django Rest Framework Response, but if your server just returns JSON this solution might work for you.
New in django 1.7 or greater is the JSONResponse response type.
https://docs.djangoproject.com/en/3.0/ref/request-response/#jsonresponse-objects
In the middleware you can return these responses without having all the "No accepted renderers" and "Response has no attribute encode" errors.
It's very similar format to the DRF Response
The import is as follows:
from django.http import JsonResponse
And how you use it:
return JsonResponse({'error': 'Some error'}, status=401)
Hopefully this helps you out!
I've solved this myself by mimicking how rest frameworks views patch the response object with accepted_renderer, accepted_media_type, and renderer_context. In my case I just wanted to return a 401 response using rest frameworks Response class, partly because my tests expect a rest framework response when I call self.client.get(...) and assert response.data.
Other use cases may require you to provide additional info in the renderer_context or use a different accepted_renderer.
from rest_framework import status
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
class MiddlewareClass(object):
def __init__(self, get_response):
self.get_response = get_response
def unauthorized_response(self, request):
response = Response(
{"detail": "This action is not authorized"},
content_type="application/json",
status=status.HTTP_401_UNAUTHORIZED,
)
response.accepted_renderer = JSONRenderer()
response.accepted_media_type = "application/json"
response.renderer_context = {}
return response
def __call__(self, request: HttpRequest):
if not self.authorized(request):
return self.unauthorized_response(request)
return self.get_response(request)
Down voted the answer above as it doesn't work for me.
The neighbor answer isn't helpful, too.
Both still return ContentNotRenderedError without manual call of the .render() method.
Tested with Python 3.8, Django 2.2, DRF 3.12.1.
The working mimicking middleware method for me is:
from rest_framework.views import APIView
# ... in the middleware class
def __call__(self, request):
try:
# Middleware logic before calling a view.
except APIException as err:
# No DRF auto-wrap out of view, so let's do it manually.
response = some_exception_handler(err, {})
return self.django_response(request, response)
return self.get_response(request)
def django_response(self, request: HttpRequest, resp: Response) -> HttpResponse:
view = APIView()
# copy-pasted from APIView.dispatch
view.headers = view.default_response_headers
return view.finalize_response(request, resp).render()
Docs investigation
To support my doubts about other solutions, here's the problem with an exception in middleware before the view has been called.
I bet the .render() method cannot be called automatically, cause the docs say:
There are three circumstances under which a TemplateResponse will be rendered:
When the TemplateResponse instance is explicitly rendered, using the SimpleTemplateResponse.render() method.
When the content of the response is explicitly set by assigning response.content.
After passing through template response middleware, but before passing through response middleware.
In our case only the 3-rd option matches. So, what is that "template response middleware"? There's only one similar thing in the docs:
process_template_response() is called just after the view has finished executing, if the response instance has a render() method, indicating that it is a TemplateResponse or equivalent.
But we haven't reached the view being executed! That's why the .render() method won't be called in our case.

Django: How can i get the logged user outside of view request?

I have a class method (outside of a view) who needs the logged user's information. How can i retrieve the logged in user without passing the request to the method? Unfortunately i can't find nowhere a nice solution and it's not logical just not exist such a way to do it, because Django should store somewhere the logged in user. Otherwise (i believe) it would be impossible to use #login_required decorator from django.contrib.auth.decorators. Right?
So if it's not possible why it's not? Why Django works like this if the only think i want is the logged in user and not all the informations inside request?
Thanks in advance.
About decorators, it is wrong. Actually decorators called with request argument.
I believe better way is that passing user or request object to class's method. But there are other ways to access request.
Here is the code that we use. You need to add this middleware to MIDDLEWARES. And import & calling get_request function.
Update July 2017: Tested with Python 3.6.1 and Django 1.10.7, based in the original code from this answer and in the Writing your own middleware documentation.
First create a new app, ie. startapp request_middleware.
Then add "request_middleware" to your INSTALLED_APPS in settings.py.
After that paste the code bellow in, ie. request_middleware.middleware.py.
Finally add "request_middleware.middleware.RequestMiddleware" to your MIDDLEWARE in settings.py (In my case I've placed it in between 'debug_toolbar.middleware.DebugToolbarMiddleware' and 'django.middleware.security.SecurityMiddleware' as far above the list as I could).
# request_middleware.middleware.py
from threading import current_thread
_REQUESTS = {}
class RequestNotFound(Exception):
def __init__(self, message):
self.message = message
def get_request():
thread = current_thread()
if thread not in _REQUESTS:
raise RequestNotFound('global request error')
else:
return _REQUESTS[thread]
class RequestMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def process_request(self, request):
_REQUESTS[current_thread()] = request
def __call__(self, request):
self.process_request(request)
response = self.get_response(request)
return response
After that simply do from request_middleware.middleware import get_request in order to use get_request in your code.