Django reqired parameters in url query - python

Is there any chances to specify which parameters are required in url query and automatically pass them into view function?
In urls.py I would like to have something like this:
path('get_part_info?<part>', views.get_part_info, name='get_part_info'),
And in views.py to have something like this:
def get_part_info(request, part):
# do something with part
return JsonResponse({'result': part})
Idea is to avoid ugly construction like: part= request.GET.get('part')
URL path is not a solution, because "part" value can have various extra characters like slashes etc.

You can write a decorator:
from functools import wraps
from django.http import HttpResponseBadRequest, JsonResponse
def query_params(*param_names):
def decorator(func):
#wraps(func)
def inner(request, *args, **kwargs):
try:
params = {name: request.GET[name] for name in param_names}
except KeyError:
return HttpResponseBadRequest("Missing Parameter")
kwargs.update(params)
return func(request, *args, **kwargs)
return inner
return decorator
#query_params("part")
def get_part_info(request, part):
# do something with part
return JsonResponse({"result": part})
This decorator returns a 400 if a parameter is missing, but that could be changed any way you want, for example, redirect to another URL or to use default values.

Related

Can I get URL parameter (set in url rule) with flask request?

I have this url route, can I get user_id with flask.request?
I want to create a wrapper, and get the user_id here.
def test_required(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
user_id = ?????
return fn(*args, **kwargs)
return wrapper
#app.route('/api/test/<int:user_id>', methods=['GET'])
#test_required
def jwt_routes_test(user_id):
request.args, request.form, or request.values not return this value.
Can I access it somehow?
This code is only valid with a view function that accepts user_id as an argument. So, you simply get it from there.
Use it as a parameter in your route:
#app.route('/api/test/<int:user_id>', methods=['GET'])
def myroute(user_id: int):
# do something
https://flask.palletsprojects.com/en/1.0.x/quickstart/#variable-rules
Looks like you're asking for view_args dictionary.
A dict of view arguments that matched the request. If an exception
happened when matching, this will be None.

Calling a decorator from another view

I have a decorator defined in users/views.py and I want to use the same in profile/views.py in django
Decorator function in users/views.py
def is_active_consult(f):
def wrap(request, *args, **kwargs):
try:
usrid = request.session['id']
user = CustomUser.objects.get(id=usrid)
usercons = Userconsultation.objects.get(doctor=user.doctor,status='InProgress')
except ObjectDoesNotExist:
usercons = ''
if usercons:
url = '/encounter_notes/'+str(usercons.userconsultationid)
return HttpResponseRedirect(url)
else:
return f(request, *args, **kwargs)
return wrap
When I try to import like
from users.views import is_active_consult
It gives an import error "cannot import name is_active_consult"
Is it right to define a decorator in a view, if not where do I put it and for now how do I fix the issue.
Regards
A decorator is a function like any other so you can define it wherever you want to, the problems is not the decorator but the import itself: import loop, incorrect path or not set, etc.
1) Did you succeed in importing anything from users/views.py to profile/views.py ?
2) Can you paste your project structure ? ( # ls -R)

Flask pass POST parameters to custom decorator

I've seen the posts on passing GET parameters and hardcoded parameters here and here.
What I am trying to do is pass POST parameters to a custom decorator. The route is not actually rendering a page but rather processing some stuff and sending the results back through an AJAX call.
The decorator looks like this:
# app/util.py
from functools import wraps
from models import data
# custom decorator to validate symbol
def symbol_valid():
def decorator(func):
#wraps(func)
def decorated_function(symbol, *args, **kwargs):
if not data.validate_symbol(symbol):
return jsonify({'status': 'fail'})
return func(*args, **kwargs)
return decorated_function
return decorator
The view looks something like this:
# app/views/matrix_blueprint.py
from flask import Blueprint, request, jsonify
from ..models import data
from ..util import symbol_valid
matrix_blueprint = Blueprint('matrix_blueprint', __name__)
# routing for the ajax call to return symbol details
#matrix_blueprint.route('/route_line', methods=['POST'])
#symbol_valid
def route_line():
symbol = request.form['symbol'].upper()
result = data.get_information(symbol)
return jsonify(**result)
I understand that I can actually call #symbol_valid() when I pass a parameter through GET like this /quote_line/<symbol> but I need to POST.
The question then is how can my decorator access the POSTed variable?
Simple solution. Imported Flask's request module into the util.py module which contains the decorator. Removed the outer function as well.
See code:
# app/util.py
from flask import request # <- added
from functools import wraps
from models import data
# custom decorator to validate symbol
def symbol_valid(func):
#wraps(func)
def decorated_function(*args, **kwargs): # <- removed symbol arg
symbol = request.form['symbol'] # <- paramter is in the request object
if not data.validate_symbol(symbol):
return jsonify({'status': 'fail'})
return func(*args, **kwargs)
return symbol_valid
The decorator accept a func parameter. You must use your decorator like #symbol_valid() or make the function symbol_valid accept a func parameter.
If you are doing it right, you can access the request object anywhere during the request cycle. It just works.

Going to another view

Say I have the following view:
def show(request):
protect(request)
... some more code here...
return render_to_response
...
"protect" is another app view which I am importing like this: from watch.actions import protect
In protect, I make some checks and if a condition is met, I want to use render_to_response right from "protect" and prevent returning to show. If the condition is not met, I want to normally return to "show" and continue the code execution.
How may I do that?
Thanks.
If its only purpose is what you've described, you should consider writing protect as a view decorator. This answer provides one example of how to do so.
Based on view decorators that I have written, your protect decorator could look something like:
from functools import wraps
from django.utils.decorators import available_attrs
def protect(func):
#wraps(func, assigned=available_attrs(func))
def inner(request, *args, **kwargs):
if some_condition:
return render_to_response('protected_template')
return func(request, *args, **kwargs)
return inner
Which would allow you to then use it like:
#protect
def show(request):
...
return render_to_response(...)

Conditional Django Middleware (or how to exclude the Admin System)

I want to use some middleware I wrote across the whole of my site (large # of pages, so I chose not to use decorators as I wanted to use the code for all pages). Only issue is that I don't want to use the middleware for the admin code, and it seems to be active on them.
Is there any way I can configure the settings.py or urls.py perhaps, or maybe something in the code to prevent it from executing on pages in the admin system?
Any help much appreciated,
Cheers
Paul
The main reason I wanted to do this was down to using an XML parser in the middleware which was messing up non-XML downloads. I have put some additional code for detecting if the code is XML and not trying to parse anything that it shouldn't.
For other middleware where this wouldn't be convenient, I'll probably use the method piquadrat outlines above, or maybe just use a view decorator - Cheers piquadrat!
A general way would be (based on piquadrat's answer)
def process_request(self, request):
if request.path.startswith(reverse('admin:index')):
return None
# rest of method
This way if someone changes /admin/ to /django_admin/ you are still covered.
You could check the path in process_request (and any other process_*-methods in your middleware)
def process_request(self, request):
if request.path.startswith('/admin/'):
return None
# rest of method
def process_response(self, request, response):
if request.path.startswith('/admin/'):
return response
# rest of method
You don't need to muck around with paths.
If you want to exclude a single middleware from a view, you must first import that middleware and do:
from django.utils.decorators import decorator_from_middleware
from your.path.middlewares import MiddleWareYouWantToExclude
#decorator_from_middleware(MiddleWareYouWantToExclude)
def your_view(request):
....
If you want to exclude ALL middleware regardless of what they are/do, do this:
from django.conf import settings
from django.utils.module_loading import import_string
from django.utils.decorators import decorator_from_middleware
def your_view(request):
...
# loop over ALL the active middleware used by the app, import them
# and add them to the `decorator_from_middleware` decorator recursively
for m in [import_string(s) for s in settings.MIDDLEWARE]:
your_view = decorator_from_middleware(m)(your_view)
middleware functions are basically called for every request, including image src, api, form, ajax calls etc. somehow people reckon it is only called before and after view. This not only can cause performance concern, but also is hard to change.
I do not recommend path comparison unless using reverse, on the one hand, reference modification may create more trouble; on the other hand, if conditionals seems messy.
here I provide a solution for converting middleware to decorator, this method requires you to add decorator manually to all views. Fully optimised, you can make decisions which right middleware is for the right view
before I had:
class AjaxMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
def is_ajax(self):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
request.is_ajax = is_ajax.__get__(request)
response = self.get_response(request)
return response
class DeletionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
from staff.models import Deletion
response = self.get_response(request)
Deletion.auto_clean_up()
return response
MIDDLEWARE = [
'common.middleware.DeletionMiddleware',
'common.middleware.AjaxMiddleware',
]
now I created a decorators.py:
from django.contrib.auth.models import User
class Shape:
#staticmethod
def before(request):
pass
#staticmethod
def after(request):
pass
# All default to call, pass in as excluder param to escape
# Part default not to call, pass in as includer param to execute
class All(Shape): pass
class Part(Shape): pass
class Mixins:
class Online(Part):
#staticmethod
def after(request):
if request.user.is_authenticated:
request.user.profile.visit()
class Deletion(All):
#staticmethod
def after(request):
from staff.models import Deletion
Deletion.auto_clean_up()
class Ajax(All):
#staticmethod
def before(request):
def is_ajax(self):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
request.is_ajax = is_ajax.__get__(request)
def common_dec(excluders=[], includers=[]):
def common(function):
# def wrapper_func(*args, **kwargs):
# return function(*args, **kwargs)
# return wrapper_func
"""
common view that returns a template
does not incldue images and cdn access
"""
def wrapper_func(request, *args, **kwargs):
# iterate all classes in Mixins
alls = All.__subclasses__()
parts = Part.__subclasses__()
list(map(lambda cls:cls.before(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.before(request) if cls.__name__ in includers else 0, parts))
response = function(request, *args, **kwargs)
list(map(lambda cls:cls.after(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.after(request) if cls.__name__ in includers else 0, parts))
return response
return wrapper_func
return common
def common_mix(excluders=[], includers=[]):
class CommonMixin:
def dispatch(self, request, *args, **kwargs):
alls = All.__subclasses__()
parts = Part.__subclasses__()
list(map(lambda cls:cls.before(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.before(request) if cls.__name__ in includers else 0, parts))
response = super().dispatch(request, *args, **kwargs)
list(map(lambda cls:cls.after(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.after(request) if cls.__name__ in includers else 0, parts))
return response
return CommonMixin
which I can do
# common_dec implement all Shape.All middleware
#common_dec()
def index(request):
return render(request, 'index.html')
# this will except Deletion middleware, and include Online middleware, and execute all Shape.All
#common_dec(['Deletion'], ['Online'])
def index(request):
return render(request, 'index.html')
for class based view:
# this will execute all Shape.All, and include Online which is a Shape.Part
class ArticleDetailView(common_mix(excluders=['Online']), LoginRequiredMixin, DetailView):
pass
I have give up middleware since it lacks flexibility, this method largely saves performance and is implementable for large scopes view definition, will not causing any bug.
if you want to add a middleware, simply add a class to Mixin inheriting Shape.All, then it will be called for all decorated view function and classes. if you have a new type of view function that needs a new middleware, define it as Part and include it in decorator .it is also extremely convenient to exclude some middleware in particular view.

Categories