Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check view method parameter name in Django class based views

I have created a decorator in my Django project to inject parameter values to the decorated method's parameters.

I do this by using inspect.getargspec to check which parameters are present in the method and place them in kwargs. Otherwise I get an error due to the incorrect number of parameters in the method.

While this works properly in individual view methods, it fails when it comes to Django's class based views.

I believe this might be because the decorators are applied using @method_decorator at the class level to the dispatch method instead of the individual get and post methods.

I'm a python newbie and might be overlooking something obvious here.

Is there a better way to do what I'm doing? Is it possible to get the method parameter names in a class based view?

I'm using Python 2.7 and Django 1.11

The Decorator

def need_jwt_verification(decorated_function):
    @wraps(decorated_function)
    def decorator(*args, **kwargs):
        request = args[0]
        if not isinstance(request, HttpRequest):
            raise RuntimeError(
                "This decorator can only work with django view methods accepting a HTTPRequest as the first parameter")

        if AUTHORIZATION_HEADER_NAME not in request.META:
            return HttpResponse("Missing authentication header", status=401)

        jwt_token = request.META[AUTHORIZATION_HEADER_NAME].replace(BEARER_METHOD_TEXT, "")

        try:
            decoded_payload = jwt_service.verify_token(jwt_token)

            parameter_names = inspect.getargspec(decorated_function).args

            if "phone_number" in parameter_names or "phone_number" in parameter_names:
                kwargs["phone_number"] = decoded_payload["phone"]
            if "user_id" in parameter_names:
                kwargs["user_id"] = decoded_payload["user_id"]
            if "email" in parameter_names:
                kwargs["email"] = decoded_payload["email"]

            return decorated_function(*args, **kwargs)
        except JWTError as e:
            return HttpResponse("Incorrect or expired authentication header", status=401)

    return decorator

A class based view

@method_decorator([csrf_exempt, need_jwt_verification], name="dispatch")
class EMController(View):
    def get(self, request, phone_number, event_id):
        data = get_data()

        return JsonResponse(data, safe=False)

    def post(self, request, phone_number, event_id):


        return JsonResponse("Operation successful", safe=False)

EDIT:

The obvious solution of applying the decorator at the method level, doesn't work with Django's class based views. You need apply the decorator at the url configuration or apply the decorator to the dispatch method.

EDIT: I've posted code that was related to a workaround I was exploring, passing the parameter names as an argument into the decorator.

like image 357
Thihara Avatar asked May 10 '17 08:05

Thihara


People also ask

How do you call class-based views in django?

Asynchronous class-based viewsimport asyncio from django. http import HttpResponse from django. views import View class AsyncView(View): async def get(self, request, *args, **kwargs): # Perform io-blocking view logic using await, sleep for example.

Which is better class-based view or function based view django?

Class based views are excellent if you want to implement a fully functional CRUD operations in your Django application, and the same will take little time & effort to implement using function based views.

What is the difference between class-based views and function based views?

Class-based views are the alternatives of function-based views. It is implemented in the projects as Python objects instead of functions. Class-based views don't replace function-based views, but they do have certain advantages over function-based views.

How do you use decorators in class-based view in django?

To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class. The decorators will process a request in the order they are passed to the decorator.


1 Answers

I found this post: Function decorators with parameters on a class based view in Django

which may provide the answer to your problem:

If you want to pass a decorator with parameters, you only need to:

  • Evaluate the parameters in the decorator-creator function.

  • Pass the evaluated value to @method_decorator.

The above mentioned and the code provided in the linked answer taken under consideration, you should:

injectables=[inject_1, inject_2, ..., inject_n]
decorators = [csrf_exempt, need_jwt_verification(injectables)]

@method_decorator(decorators, name="dispatch")
class EMController(View):
    ...


Leaving my previous mistaken answer here for legacy reasons, don't try this at home (or anywhere, in django, for that matter!!)

If we observe the "decorating a class" docs, we can see the following:

Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument name:

so you have to change the name argument of your @method_decorator to match the method that will apply to:

decorators = [csrf_exempt, need_jwt_verification(injectables=[])]

@method_decorator(decorators, name='get')
@method_decorator(decorators, name='post')
class EMController(View):

Personally I prefer to place my decorators on top of the specific method they will apply to:

class EMController(View):
    @method_decorator(decorators)
    def get(self, request, phone_number, event_id):
        ...

    @method_decorator(decorators)    
    def post(self, request, phone_number, event_id):
        ...

like image 178
John Moutafis Avatar answered Oct 31 '22 22:10

John Moutafis