Prologue

As a Web backend framework, Django is easy to use. However, simplicity is at the same time one of its drawbacks, which means it may not be that graceful for more complex requests. So in this post, I’m going to introduce a more elegant way to handle request parameters.


Write Your Own Decorator

1. Basic Decorator

Decorators in Django may seem to be mysterious when you first use it, just a simple @decorator and some magic happens. So how can we implement ours?

A basic decorator can be as simple as this.

1
2
3
4
5
6
7
8
9
from functools import wraps

def my_decorator():
def decorator(view_func):
def wrapper(request, *args, **kwargs):
# Some logics here...
return view_func(request, *args, **kwargs)
return wraps(view_func)(wrapper)
return decorator

A little wired? Well, you can just focus on the logic, and take others as a template. If you want IDE to recognize the type of request, you can add WSGIRequest type hint.

1
2
3
4
5
6
from django.core.handlers.wsgi import WSGIRequest

# ...
# ...
def wrapper(request: WSGIRequest, *args, **kwargs):
# ...

For this decorator, you can use it like this.

1
2
3
@my_decorator()
def my_view_func(request):
# logics here...

Not that bad, huh? 😃 It is essentially a function, that’s it.

2. Response Before View

You may not want some illegal requests go into your view function, thus return Bad Request response in decorator can be a good choice. To achieve this, simply return such response instead of view_func.

1
2
3
4
5
6
7
8
9
10
11
12
from http import HTTPStatus
from django.http import HttpResponse

def varification_decorator():
def decorator(view_func):
def wrapper(request: WSGIRequest, *args, **kwargs):
if not verify(request):
return HttpResponse("Bad Response", status=HTTPStatus.BAD_REQUEST)
return view_func(request, user=user, *args, **kwargs)
return wraps(view_func)(wrapper)
return decorator

3. Decorator with Parameters

To add parameters to our decorator, we simply add parameters to our decorator. 😳

1
2
3
4
5
6
7
def message_decorator(message):
def decorator(view_func):
def wrapper(request, *args, **kwargs):
print("Message in decorator: " + message)
return view_func(request, *args, **kwargs)
return wraps(view_func)(wrapper)
return decorator

See, that’s it, no difference from other ordinary functions.

4. Inject Parameters to View Function

You may want to get some data from the request in decorator, so that you won’t bother get it again and again in view function. To achieve this, you need *args and *kwargs in Python.

Previously, when we call view_func, we pass only request, *args, **kwargs. These are inherited from parent wrapper.

Notice that, decorators are like middlewares, they will process the request one by one. The parameters you set in one decorator will continue exists in the following decorators. Only make sure the first parameter is request, and remember to inherit *args and **kwargs.

So, to inject a parameter, you can simply write this.

1
2
3
4
5
6
def inject_msg():
def decorator(view_func):
def wrapper(request: WSGIRequest, *args, **kwargs):
return view_func(request, msg="hello", *args, **kwargs)
return wraps(view_func)(wrapper)
return decorator

Then, in the view function, you can get this msg by adding a parameter.

1
2
3
4
@inject_msg()
def my_view(request, msg):
print(msg)
# More logics...

Or you can inject multiple parameters using a dict object.

1
2
3
4
5
6
7
8
9
10
def inject_multiple():
def decorator(view_func):
def wrapper(request: WSGIRequest, *args, **kwargs):
params = {
"a": "A",
"b": "B"
}
return view_func(request, **params, *args, **kwargs)
return wraps(view_func)(wrapper)
return decorator

Then, the same, you can get these parameters.

1
2
3
4
@inject_multiple()
def my_view(request, a, b):
print(a, b)
# More logics...

That’s it. Now you can gracefully handle parameters in requests! 😁


Epilogue

Although we use decorator to achieve a better request processing, Django itself is not that elegant. It’s time to use a more powerful and promising framework - ASP.NET Core! 💫

See: Thoughts on Basic Structure of ASP.NET Core 😉