Finding the decorator name using django middleware - django

I had created a decorator to check if a person has session as follows:
(comments added for brevity)
def session_enabled(func=None, prefix='calling: '):
# some preprocessing
def session_enabled_wrapper(*args, **kwargs):
# check for session in request obj
# execute the function and get result onto a variable say result
# some API profiling logic
return result
return session_enabled_wrapper
I have used this on some views as follows:
#session_enabled
#csrf_exempt
def view_function(request):
# some processing
My goal was to check if any function are being accessed without this decorator in place on the logs.
So i wrote a middleware as follows:
class SessionEnabledMiddleware(object):
'''
class that represents middleware called during requests
'''
def process_view(self, request, view_func, view_args, view_kwargs):
'''
called during the request cycle
:param request: request param
:param view_func: view function object
:param view_args: args passed to view function
:param view_kwargs: kwargs passed to view function
:return: None
'''
print('############# Inside session enabled middleware ###################')
if not view_func.__name__ == 'session_enabled_wrapper':
print('Function Not session enabled', request.path)
else:
print('function is session enabled', request.path)
I added the above middleware to tuple of MIDDLEWARE_CLASSES in settings.py file.
I was happy as this worked, but the happiness was short lived as my APIs started to crash with 403 forbidden error.
After some research, i changed the decorator as follows:
import functools
def session_enabled(func=None, prefix='calling: '):
# some preprocessing
#wraps(func)
def session_enabled_wrapper(*args, **kwargs):
# check for session in request obj
# execute the function and get result onto a variable say result
# some API profiling logic
return result
return session_enabled_wrapper
This meant that my earlier middleware logic failed. I needed to overcome this. So changed my middleware and decorator as follows:
decorator:
def session_enabled(func=None, prefix='calling: '):
# some preprocessing
#wraps(func)
def wrapper(*args, **kwargs):
wrapper.session_enabled = True
# check for session in request obj
# actual function execution and get result onto a varible say result
# some API profiling logic
return result
wrapper.session_enabled = False
return wrapper
middleware:
class SessionEnabledMiddleware(object):
'''
class that represents middleware called during requests
'''
def process_view(self, request, view_func, view_args, view_kwargs):
'''
called during the request cycle
:param request: request param
:param view_func: view function object
:param view_args: args passed to view function
:param view_kwargs: kwargs passed to view function
:return: None
'''
print('############# Inside session enabled middleware ###################')
if not view_func.session_enabled:
print('Function Not session enabled', request.path)
else:
print('function is session enabled', request.path)
But i was greeted with function object has no attribute 'session_enabled'.
I even tried:
view_function.__closure__[0].cell_contents.__name__
but couldn't get the name of the decorator.
What am i doing wrong? Is there any possible way to get the name of the decorator to be used as a condition in middleware or a way to find if the function is decorated and take necessary actions?
Note:
Stackoverflow question which almost meet the above requirement:
Detect Decorator in Python
EDIT:
After considering Vitor Freitas recommended link i changed the code as follows:
#register(session_enabled, csrf_exempt)
def view_function(request):
# some processing
middleware:
class SessionEnabledMiddleware(object):
'''
class that represents middleware called during requests
'''
def process_view(self, request, view_func, view_args, view_kwargs):
'''
called during the request cycle
:param request: request param
:param view_func: view function object
:param view_args: args passed to view function
:param view_kwargs: kwargs passed to view function
:return: None
'''
try:
print('############# Inside session enabled middleware ###################')
decorators = [decorator.__name__ for decorator in view_func._decorators]
if 'session_enabled' in decorators:
print('Function is session enabled', request.path)
except:
print('function is not session enabled or decorated using register', request.path)
Work's like a charm.....

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.

How can I add field to request body in django middleware

I want to add new field in request body in middleware and use it in views.
I googled it but the results was not worked.
How can I do it?
Django v2 python 3.6
You need to add a separate custom middleware which will process the request before landing to a particular view. Below is the code for custom middleware:
class ProcessRequestMiddleware(MiddlewareMixin):
"""
This middleware appends new payload in request body
"""
def process_view(self, request, view_func, *view_args, **view_kwargs):
request_data = getattr(request, '_body', request.body)
request_data = json.loads(request_data)
# here you can write the logic to append the payload to request data
request._body = json.dumps(request_data)
return None
Note - The middleware was place inside an app common (common/middleware/custommiddleware.py)
Now add this middleware to settings.MIDDLEWARE list:
"common.middleware.custommiddleware.ProcessRequestMiddleware"
Now you can retrieve the payload, which you had appended in custommiddleware, inside any of the views you want by just calling the json.loads(request.body).
Try following code:
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
my_request = request.GET.copy()
my_request['foo']='bar'
request.GET = my_request
response = self.get_response(request)
return response
I tried this for you: added above code into: example.py Then added 'example.SimpleMiddleware', into MIDDLEWARE
My view method:
def index(request):
for key in request.GET:
print (key, '--->', request.GET[key])
return render(request, 'example.html')
able to print foo ---> bar browser sends the request.

Store additional information in Django session in login response

After a user logs in, they're sent the session cookie inside an HttpResponse object. I want to add an additional field 'foo' to the session, just like I had done
request.session['foo'] = 'bar'
The above doesn't work because the login request itself doesn't have the session object itself, only subsequent requests have the cookie.
Also, doing
response.set_cookie("foo", "bar")
doesn't seem to associate the cookie with session (request.session['foo'] throws an error on subsequent requests).
How can I do this?
If you consider using a custom login view, you can do something like this:
def custom_login(request):
if request.method == 'GET':
if not request.user.is_authenticated
# here, user is not logged in.
request.session['my_data'] = 'my value'
return render(request, 'login.html' , {})
elif request.method == 'POST':
# get login credentials and authenticate user
# see -> https://docs.djangoproject.com/en/2.0/topics/auth/default/#authenticating-users
return HttpResponseRedirect('/homepage')
Or, if you want to use built-in login views, you can manipulate session data at middleware level. Just write a custom middleware like this:
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
if request.path == '/login' and request.method == 'GET' and not request.user.is_authenticated:
request.session['data'] = 123
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
And don't forget to add this SimpleMiddleware to the MIDDLEWARE list in settings.py. You can find more about middlewares here.

request.POST returns old values after updating it in custom middleware - django 1.11.9

I am using django 1.11.9
I want to add client_id and client_secret to the django POST request.
Here is how my middleware.py file looks like:
class LoginMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# auth_header = get_authorization_header(request)
# Code to be executed for each request before
# the view (and later middleware) are called.
#Add Django authentication app client data to the request
request.POST = request.POST.copy()
request.POST['client_id'] = '12345678'
request.POST['client_secret'] = '12345678'
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
Middleware is being successfully processed when I check it with a debugger. Thought when a view is called the 'client_id' and 'client_secret' fields are missing in the request.
After some experimenting i figure out that request is not getting updated and when it is called in a different view, it returns old values.
I am later using request in rest_framework_social_oauth2. And this is the point when 'client_id' and 'client_secret' disappear.
class ConvertTokenView(CsrfExemptMixin, OAuthLibMixin, APIView):
"""
Implements an endpoint to convert a provider token to an access token
The endpoint is used in the following flows:
* Authorization code
* Client credentials
"""
server_class = SocialTokenServer
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = KeepRequestCore
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
import pdb ; pdb.set_trace()
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request.data.items():
request._request.POST[key] = value
url, headers, body, status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=status)
for k, v in headers.items():
response[k] = v
return response
I need to add client_id and client_secret to the request body, so it can be later used by rest_framework_social_oauth2.
What could be the problem? How to properly update the request?
As you're working with request and processing a request, you have to implement process_request method, so the result will be something like:
class LoginMiddleware(object):
def process_request(self, request):
request.session['client_id'] = '12345678'
and then in your view:
def your_view(request):
client_id = request.session['client_id']

Django: how to set content-type header to text/xml within a class-based view?

I'm trying to do it this way, but it doesn't work.
class MyView(View):
def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse()
if self.kwargs.has_key('xml'):
response['Content-Type'] = 'text/xml; charset=utf-8'
return response
You don't need to write additional code. Use TemplateResponseMixin and set content_type attribute to whatever you need:
class MyView(TemplateResponseMixin):
content_type='application/xml'
...
I think the key point is render_to_response in django.views.generic.base , whose code is this:
def render_to_response(self, context, **response_kwargs):
"""
Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type) # key
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
**response_kwargs
)
As for your case, May be you need this code:
class MyView(ListView):
def get(self, request, *args, **kwargs):
context = self.get_context_data()
if self.kwargs.has_key('xml'):
return self.render_to_response(context, content_type="text/xml; charset=utf-8")
return self.render_to_response(context)
I made a middleware class based off of django-cors-headers so I could allow iframe-ing of part of my django app. I keep a middleware.py in my main project directory and save a couple random middleware classes I have made there, like this one here and a ForceResponse Exception for example.
import re
from django import http
from django.conf import settings
class XFrameAllowMiddleware(object):
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
if (self.is_enabled(request) and
request.method == 'OPTIONS' and
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META):
response = http.HttpResponse()
return response
return None
def process_response(self, request, response):
if self.is_enabled(request):
response['X-Frame-Options'] = 'ALLOWALL'
return response
def is_enabled(self, request):
return re.match(settings.XFRAME_URLS_REGEX, request.path)
Add it to your MIDDLEWARE_CLASSES and configure the regex in your settings:
MIDDLEWARE_CLASSES = (
...
'your_django_app.middleware.XFrameAllowMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
XFRAME_URLS_REGEX = r'^/iframe_this_url/.*$'
from the django-cors-headers read.me:
CORS_URLS_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under /api/.
Example:
CORS_URLS_REGEX = r'^/api/.*$'
Default:
CORS_URLS_REGEX = '^.*$'