I have a pluggable view defined as follows
class ListView(View):
methods = ["GET", "POST"]
def __init__(self, model, template_name="list_view.html"):
self.model = model
self.template_name = template_name
def dispatch_request(self, *args, **kwargs):
if request.method == "GET":
objects = self.model.query.all()
return render_template(self.template_name,
objects=objects)
else:
#do post request
I'm trying to create a pluggable view that'll handle both get and post requests. When I try the above, I get the following error however
NameError: global name 'request' is not defined
According to the Flask docs, the request should be present in the dispatch_request method, but it isn't in my case. I'm using Flask 0.10.1
request is always a global context variable; you need to import it in your module:
from flask import request
See the Quickstart documentation on accessing request data; this is no different for pluggable views.
Related
I understand that we can set up authentication classes in class based viewsets like this:
class ExampleViewSet(ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication)
However, is there a way to dynamically change the authentication class based on the request method? I tried overriding this function in my ExampleViewSet:
def get_authenticators(self): # Found in
if self.request.method == "POST":
authentication_classes.append(authentication.MyCustomAuthentication)
return authentication_classes
However, django rest does not have the request object setup at this point:
'ExampleViewSet' object has no attribute 'request'
Note: not real variable names - just for example purpose.
Based on the previous answer, it works on django 1.10
#detail_route(methods=['post', 'get'])
def get_authenticators(self):
if self.request.method == "GET":
self.authentication_classes = [CustomAuthenticationClass]
return [auth() for auth in self.authentication_classes]
You can use detail_route decorator from rest_framework like this for getting requests,
detail_route can be used to define post as well as get,options or delete options
So,the updated code should be like :
from rest_framework.decorators import detail_route
class ExampleViewSet(ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication)
#detail_route(methods=['post','get'])
def get_authenticators(self, request, **kwargs): # Found in
if request.method == "POST":
authentication_classes.append(authentication.MyCustomAuthentication)
return authentication_classes
For further reading,Read from here.
I have a blueprint and some url functions,
admin_bp = Blueprint('admin', __name__)
#admin_bp.route('/dashboard', methods=['GET', ])
#flask_login.login_required
def dashboard():
context = {}
page = 'admin/dashboard.html'
return render_template(page, **context)
#admin_bp.route('/deny', methods=['GET', ])
#flask_login.login_required
def deny():
return 'hey bro you dont belong here'
I don't want to copy paste #flask_login.login_required decorator for all url functions under this blueprint. Is there a better way that I can apply decorator for all blueprint urls?
You can add before_request() as a function that will run before each request in a view.
You will then want to add decorators to inject additional functionality to the before_request function. You will want to import the login_required decorator to ensure each endpoint requires a logged in user. This decorator is part of the flask_login library.
Since it looks like your views are part of an admin, I'd also recommend adding a custom decorator to your before_request function with something like #role_required('admin'). The functionality for that decorator will live somewhere else and be imported.
#admin_bp.before_request
#login_required
def before_request():
""" Protect all of the admin endpoints. """
pass
Subclass Blueprint and override the route method.
import flask
class MyBlueprint(flask.Blueprint):
def route(self, rule, **options):
def decorator(f):
# these lines are copied from flask.Blueprint.route
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
# At this point flask.Blueprint.route simply returns f.
# But you can nest a decorator.
def inner(*args, **kwargs):
# stuff you want to do before each request goes here
try:
result = f(*args, **kwargs)
# stuff you want to do on successful responses (probing status, headers, etc.) goes here
except Exception as e:
# stuff you want to do on error responses goes here
raise
return inner
Now use the new subclass in your blueprints:
-v1_blueprint = Blueprint('v1', __name__)
+v1_blueprint = MyBlueprint('v1', __name__)
No changes needed to individual routes.
The drawback of this approach is that it copies code from inside Flask. If the implementation of flask.Blueprint.route were to change in a future version, you'd need to sync MyBlueprint with it when you upgrade Flask.
How about checking the user first:
from flask.ext.login import current_user
#admin_bp.before_request
def check_user():
if not current_user.is_authenticated():
abort(401)
# your other functions without `#flask_login.login_required`
Currently I am working on an Oauthlib-Flask implementation for a non-REST API. But I have two scenarios where I want to change/add a value of the flask request object. Since it is immutable this doesn't come with ease. I tried making a duplicate as it is suggested in Changing values on a werkzeug request object. But since the #oauth.authorize_handler uses the given request object I would have to replace it, which resolves in an UnboundLocalError: local variable 'request' referenced before assignment error. Here is my sample code (it is a part of the implicit grant):
#app.route('/oauth/authorize', methods=['GET', 'POST'])
#login
#oauth.authorize_handler
def authorize(*args, **kwargs):
if request.method == 'GET':
client_id = kwargs.get('client_id')
client = Client.query.filter_by(client_id=client_id).first()
kwargs['client'] = client
return render_template('authorize.html', **kwargs)
r = make_duplicate_request(request)
#Change/add values of r
request = r
return True
Am I doing something wrong or is there another possibility to change the request object?
Thanks for your help!
Update:
The code above describes the situation where I want to pass information to the tokensetter function. This could be done with a global variable, but I wanted to avoid that.
In my signup routine the client sends a request like this to the API:
params_signup = {
"schemas":["urn:scim:schemas:core:2.0:User"],
"expireIn":3600,
"username":"test#web.de",
"password":"123",
"access_token":"",
"externalId":"tmeinhardt",
"grant_type":"password",
"client_id":"1",
"params":{
"age":"20-30",
"gender":"m",
}
}
I need the grant_type and client_id part only for the tokenhandler and wanted to add it manually to the request object. But since this object is immutable...
Writing this for those who will come across this and are trying to implement an OAuth flow.
Don't use decorators use middleware instead
I believe you should handle this in a middleware. In the middleware, you can set the authorization property of the 2nd parameter of the call function which contains the current wsgi app environment variables you have passed in your init function.
Look at code below:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
cookie = Request(environ).cookies.get('access_token')
if cookie is not None:
environ['HTTP_AUTHORIZATION']='Bearer '+cookie
return self.app(environ, start_response)
you can create a new request and splat the headers into a new dict of an HttpRequest (eg. Authlib's HttpRequest)
from flask import request as _req
from authlib.oauth2.rfc6749 import HttpRequest
request = HttpRequest(
_req.method, _req.full_path, _req.data, {**_req.headers, **auth}
)
request.req = _req
then you can work with this new request instead of the Flask request
So this is a simple view that I have written.
class PostTestView(View):
def post(self, request, *args, **kwargs):
print request.POST
return HttpResponse("Hello there")
my urls.py has this line for the above view :
url(r'^test/create$',PostTestView.as_view(), name='app.views.create_test')
But I get an 405 Http error when I try to hit http://127.0.0.1:8000/app/test/create
This apparently means that my method post is not in the defined methods list . But I have defined it as above in my view.
What could possibly be wrong here ? I am clueless
Try defining the get method.
The "post" method is commonly used in forms, but when you just point your browser to an url the used method is "get"
I have a custom session class that I've built to extend the Django SessionBase. I did this in order to reuse a legacy Session table, so that sessions can pass between our Django pages and our PHP pages without having the user to log in and back out.
Everything's working perfectly so, far with one huge BUT.
I wrote some custom middleware in order to let the SessionStore.start() function have access to the Request Object. Unfortunately, in order to do that I used this answer: Access request.session from backend.get_user in order to remedy my problem.
I have learned that using the above answer (Essentially binding the request object to the settings, so you can access using import settings* and then settings.request) is totally horrible and the absolutely worst way to do this.
My core problem, is I don't understand how I can access the request from within the custom session backend I've written.
Maybe in middleware you could pass request to your custom SessionStore like this:
request.session = engine.SessionStore(session_key,request)
and in SessionStore:
class SessionStore(SessionBase):
def __init__(self, session_key=None, request):
self.request = request
super(SessionStore, self).__init__(session_key)
Later you can access request as self.request.
Django's SessionMiddleware does this:
class SessionMiddleware(object):
def process_request(self, request):
engine = import_module(settings.SESSION_ENGINE)
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)
can't you do this?
import mycustomsessionbackend as myengine
class MyCustomSessionMiddleware(object):
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = myengine.SessionStore(session_key, request)
...
# mycustomsessionbackend.py
class SessionStore(SessionBase):
def __init__(self, session_key=None, request=None):
super(SessionStore, self).__init__(session_key)
self.request = request