Add variable to Django request object in Middleware (once) - python-3.x

class CustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
request.variable = 1
response = self.get_response(request)
return response
This works but it it processing the request twice. I am unsure of how to set this variable after the view has been processed (for every view), only once. process_template_response is not a valid option because it will not work with every view. Is there a better way to do this?

Related

How can I read response content (that is JSON) from Django middleware so I can convert it to HTML?

From my django view I am returning a serialized JSON as below:
def features(request):
features = db_service.fetch_all_features()
data = serializers.serialize('json', features)
return HttpResponse(data, content_type='application/json')
I have registered a middleware, where I want to fetch this JSON and convert it into HTML
class ReturnResponseAsHTML:
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.
response = self.get_response(request)
# Code to be executed for each request/response after
#return HttpResponse(data, content_type='application/json')
return response
Once I have access to the response object, how may I fetch the JSON from it so I may convert it to HTML
response.getvalue()
where response is an instance of django.http.HttpResponse

How do I apply Django middleware everywhere except for a single path?

I'm using Python 3.9 with Django 3. I have defined this middleware ...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'directory.middleware.extend_token_response.ExtendTokenResponse'
]
However, I don't want the middleware to apply to a certain URL. I have hard-coded this in the middleware like so
class ExtendTokenResponse:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
response = self.get_response(request)
if request.path != '/' + LOGOUT_PATH:
# Code to be executed for each request before
# the view (and later middleware) are called.
is_expired = True
try:
token = request.auth
print("req path: %s" % request.path)
is_expired = is_token_expired(token) if token else True
except Exception as err:
print(err)
if not is_expired:
but this seems a little sloppy and I would think the middleware comes with somethign out of the box to configure that this wouldn't need to be applied to my "/logout" path. Is there a more elegant way to configure this?
Edit: In response to Bernhard Vallant's answer, I changed my middleware to the below
def token_response_exempt(view_func):
# Set an attribute on the function to mark it as exempt
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.token_response_exempt = True
return wraps(view_func)(wrapped_view)
class ExtendTokenResponse:
def init(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def process_view(self, request, view_func, view_args, view_kwargs):
print("in process view method ...\n")
if getattr(view_func, "token_response_exempt", False):
print("returning none ...\n")
return None
# Code to be executed for each request before
# the view (and later middleware) are called.
is_expired = True
try:
token = request.auth
print("req path: %s" % request.path)
is_expired = is_token_expired(token) if token else True
except Exception as err:
print(err)
if not is_expired:
token.delete()
new_token = Token.objects.create(user = token.user)
# Code to be executed for each request/response after
# the view is called.
print("setting new token to %s" % new_token)
request.token = new_token
def __call__(self, request):
response = self.get_response(request)
print("---- in call method ----\n")
if getattr(request, "token", None) is not None:
print("setting refresh token header = %s" % request.token)
response['Refresh-Token'] = request.token
return response
but any call to an endpoint, e.g.,
curl --header "Content-type: application/json" --data "$req" --request POST "http://localhost:8000/login"
results in no token being retrieved from the reqeust. "request.auth" generates the error
'WSGIRequest' object has no attribute 'auth'
Django itself doesn't provide a solution for this. Probably hardcoding/defining paths in your settings/middleware is fine as long it is a middleware that primarly exists for one specific project.
However if you want to mark certain views to exclude them from being processed you could use decorators in the same way Django does with the csrf_exempt decorator.
from functools import wraps
def token_response_exempt(view_func):
# Set an attribute on the function to mark it as exempt
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.token_response_exempt = True
return wraps(view_func)(wrapped_view)
# your middleware
class ExtendTokenResponse:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if getattr(request, "token", None) is not None:
response['Refresh-Token'] = request.token
return response
def process_view(self, request, view_func, view_args, view_kwargs):
if getattr(view_func, "token_response_exempt", False):
return None
# do your token generation here
request.token = token
And then you can use decorator like the following:
# urls.py
urlpatterns = [
path('logout/', token_response_exempt(LogOutView.as_view())),
]
About your case, I have 2 recommendations below:
Method 1: use process_view and define a list func will be excluded with structure "app.module.func" and check to skip in process_view
# In settings.py
EXCLUDE_FROM_MY_MIDDLEWARE =set({'custom_app.views.About'})
# In middlewares.py
class ExtendTokenResponse:
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.
logger.info(f'request hit request {request}')
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
def process_view(self, request, view_func, view_args, view_kwargs):
view_function = '.'.join((view_func.__module__, view_func.__name__))
exclusion_set=getattr(settings,'EXCLUDE_FROM_MY_MIDDLEWARE',set() )
if view_function in exclusion_set:
return None
Method 2: Use decorator_from_middleware and apply middleware to each function needed it.
from django.utils.decorators import decorator_from_middleware
# with function view
#decorator_from_middleware(ExtendTokenResponse)
def view_function(request):
...
#with class view
class SimpleMiddlewareMixin:
#decorator_from_middleware(ExtendTokenResponse)
def dispatch(*args, **kwargs):
return super().dispatch(*args, **kwargs)
class MyClassBasedView(SimpleMiddlewareMixin, ListView):

Call a Django Rest Framework view within another view

Here calling a view within another view but I can not use its result. When attempting to use that data which gives the following result in the terminal,
Response status_code=200, "text/html; charset=utf-8">
view
class CourseFilterView(ResponseViewMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = CourseFilterSerializer
queryset = Course.objects.all()
class CourseFilterListView(ResponseViewMixin, APIView):
def get(self, request, *args, **kwargs):
response_data = {}
a = CourseFilterView.as_view({'get': 'list'})(request._request, *args, **kwargs)
response_data['a'] = a
print('a', a)
Am I following the right path? If not, give me a solution to make it better

python3 - decorator function: Variable referenced before assignment

Im working on a gRPC microservice.
Because every method has to load a JSON string from the request argument first – and then in the end dump it again, I want to use a decorator on the methods of the class, so that the method itself besides the return only contains the ... more stuff to do ... below:
class VPPSetupService(SetupService_pb2_grpc.VPPSetupServiceServicer):
...
def method(self, request, context):
request = json.loads(request.context)
request = request["request"]
header = request["headers"]
... more stuff to do ...
response = json.dumps(response)
return SetupService_pb2.JsonContextResponse(context=response)
So I wrote a decorator function.
However, I could not find a solution for this error:
request = json.loads(request.context)
UnboundLocalError: local variable 'request' referenced before assignment
The error is produced by this: (Just as an example, the real thing is a bit more complex)
from functools import wraps
def json_and_error(func):
#wraps(func)
def args_wrapper(*args, **kwargs): # Problem: This
request = json.loads(request.context) <<# variable is referenced
request = request["request"] # before assignment
header = request["headers"]
func(*args, **kwargs)
return func(*args, **kwargs)
return args_wrapper
class VPPSetupService(SetupService_pb2_grpc.VPPSetupServiceServicer):
...
#json_and_error
def method(self, request, context):
... more stuff to do ...
return SetupService_pb2.JsonContextResponse(context=response)
I tried using request = json.loads(args[1].context) instead. But then I get this error:
if request["source_machine"] and request["dest_machine"]: TypeError:
'JsonContextRequest' object is not subscriptable
The input given as request argument is an object of type <class 'SetupService_pb2.JsonContextRequest'> The JSON String in the request would be accessible through the request.context attribute.
I'm thinking the problem is related to how the decorator function is being called. I suppose if it was called at runtime of the class method the variable request should already have been assigned the request object.
But maybe I'm completely wrong here. So how would you solve this?
There are few errors in your code:
def args_wrapper(*args, **kwargs):
request = json.loads(request.context)
You're using request.context while request variable is undefined (it will be defined only after json.loads(request.context) will be executed). You tried to fix it using request = json.loads(args[1].context), but got JsonContextRequest object is not subscriptable in wrapped function. Look at this line closer:
return func(*args, **kwargs)
You decorator returns wrapped function called with the same params, so this code result isn't used, you don't pass request and header to wrapped function:
request = json.loads(request.context)
request = request["request"]
header = request["headers"]
Also you shouldn't call wrapped function:
func(*args, **kwargs)
I guess you want to do something like this:
def json_and_error(func):
def args_wrapper(this, request):
context = request.context
request = json.loads(context)
request = request["request"]
return func(this, request, context)
return args_wrapper

How to push post parameter into scrapy-redis

I have a post request like
def start_requests(self):
yield FormRequest(url,formdata={'id': "parameter from redis"})
Can I use redis-cli lpush to save post parameter and that my crawler run it?
By default the scrapy-redis queue working only with url as messages.
One message = one url. But you can modify this behavior.
For example you can use some object for your messages/requests:
class ScheduledRequest:
def __init__(self, url, method, body)
self.url = url
self.method = method
self.body = body
Pass it to queue as json encoded dic:
redis.lpush(
queue_key,
json.dumps(
ScheduledRequest(
url='http://google.com',
method='POST',
body='some body data ...'
).__dict__
)
)
And rewrite the make_request_from_data and schedule_next_requests methods:
class MySpiderBase(RedisCrawlSpider, scrapy.Spider):
def __init__(self, *args, **kwargs):
super(MySpiderBase, self).__init__(*args, **kwargs)
def make_request_from_data(self, data):
scheduled = ScheduledRequest(
**json.loads(
bytes_to_str(data, self.redis_encoding)
)
)
# here you can use and FormRequest
return scrapy.Request(url=scheduled.url, method=scheduled.method, body=scheduled.body)
def schedule_next_requests(self):
for request in self.next_requests():
self.crawler.engine.crawl(request, spider=self)
def parse(self, response):
pass

Resources