19Django开发总结:自带的常用装饰器应用场景及正确使用方法总结

Posted SteveRocket

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了19Django开发总结:自带的常用装饰器应用场景及正确使用方法总结相关的知识,希望对你有一定的参考价值。

装饰器(decorator)可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能。装饰器广泛用于权限校验和缓存等场景。Django项目中使用装饰器可以让代码将变得更干净、更可读、更可维护。

使用装饰器可以很方便地修改对象行为,通过使用类似接口将修改动作封装在装饰对象中.decorator可以动态地修改函数、方法或类的功能,而无需创建子类或修改类的源代码。减少了许多冗余但又不得不写的代码,使我们可以使用单个方法向多个类添加功能。

权限验证装饰器

@login_required

@login_required是Django最常用的一个装饰器。其作用是在执行视图函数前先检查用户是否通过登录身份验证,并将未登录的用户重定向到指定的登录url。其中login_url是可选参数。如果不设置,默认login_url是settings.py里设置的LOGIN_URL。

from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login')
def index(request):
    return HttpResponse("login required login url")

@login_required还可以一个可选参数是redirect_field_name, 默认值是'next'。

from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='self_redirect_field_name')
def index2(request):
    return HttpResponse("login required redirect field name")

注意

login_required装饰器不会检查用户是否是is_active状态。如果想进一步限制登录验证成功的用户对某些视图函数的访问,需要使用更强大的@user_passes_test装饰器。

@user_passes_test

@user_passes_test装饰器的作用是对登录用户对象的信息进行判断,只有通过测试(返回值为True)的用户才能访问视图函数。不符合条件的用户会被跳转到指定的登录url。装饰器有一个必选参数,即对用户对象信息进行判断的函数。该函数必需接收user对象为参数。与@login_required类似,@user_passes_test还有两个可选参数(login_url和redirect_field_name)

user_passes_test(func[,login_url=None, redirect_field_name=REDIRECT_FIELD_NAME])

针对一个匿名用户来访问,没有email地址,显然不能通过测试,需要登录后再来。

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith("@126.com")

@user_passes_test(email_check, login_url='/login/')
def index3(request):
    """
    @user_passes_test装饰器对用户的email地址结尾进行判断,会把未通过测试的用户定向到登录url。
    login_url是可选参数
    :param request:
    :return:
    """
    return HttpResponse("check user email")

注意

@user_passes_test不会自动的检查用户是否是匿名用户, 但是@user_passes_test装饰器还是可以起到两层校验的作用。一来检查用户是否登录,二来检查用户是否符合某些条件,无需重复使用@login_required装饰器。

如果只允许is_active的登录用户访问某些视图,使用@user_passes_test装饰器可以轻松地解决这个问题,如下所示:

@user_passes_test(lambda user: user.is_active)
def index4(request):
    return HttpResponse('check user is active')

@permission_required

 

@permission_required装饰器的作用是检查用户是否有特定权限,第一个参数perm是权限名,为必选, 第二个参数login_url为可选。

permission_required(perm[, login_url=None, raise_exception=False]) 

下例检查用户是否有polls.can_vote的权限,没有的话定向至login_url。如果设置了raise_exception=True, 会直接返回403无权限的错误,而不会跳转到登录页面。不需要先使用@login_required来验证用户是否登录,再使用@permission_required装饰器来查看登录用户是否具有相关权限。如果一个匿名用户来访问这个视图,显然该用户没有相关权限,会自动定向至登录页面。

更多的关于权限验证相关的装饰器,如检查用户是否属于该用户组、想要已登录的用户不允许进入某些视图、只允许superuser访问、只允许超级用户才能访问视图、检测请求是否是Ajax请求、改进某个视图的响应时间,或者只想知道运行需要多长时间等Django常用的自定义装饰器,关注参考下一篇:《20、Django开发总结:自定义Django装饰器以及常用的自定义装饰器》

缓存装饰器

缓存是Django装饰器很重要的一个应用场景。注意: 使用缓存装饰器的前提是已经对缓存进行了相关设置。

详细配置和使用参考:

https://blog.csdn.net/zhouruifu2015/article/details/129784636

类伪装成函数装饰器

@method_decorator

如果需要在基于类的视图上使用装饰器,需要使用到@method_decorator这个装饰器, 它的作用是将类伪装成函数方法。@method_decorator第一个参数一般是需要使用的装饰器名。

from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'
    template_engine = 'jinja2'

限制请求方法装饰器

@require_http_methods

该装饰器的作用是限制用户的请求方法。与此类似的装饰器还有@require_POST, @require_GET和@require_safe。

from django.views.decorators.http import require_http_methods, require_POST, require_GET, require_safe
@require_http_methods(['GET', 'POST'])
# @require_GET()要求视图只接受GET方法
# @require_POST()要求视图只接受POST方法
# @require_safe()要求视图只接受GET和HEAD方法。这些方法通常被认为是“安全的”,因为除了检索所请求的资源之外,它们不应具有采取其他操作的重要性。
def index6(request):
    # 仅接收GET和POST方法
    return HttpResponse("Only accept GET or POST method")

注意:请求方法应为大写。

内容压缩装饰器

@gzip_page

该装饰器可以压缩内容,前提是用户客户端允许内容压缩的话。使用方法如下:

from django.views.decorators.gzip import gzip_page

@gzip_page
def index7(request):
    return HttpResponse("gzip page decorator")

条件视图处理装饰器

@condition, @etag, @last_modified

参考:Conditional View Processing | Django documentation | Django

ETag是一个与Web资源相关联的记号值,Last-Modified保存的则是浏览器请求的资源最后一次被修改的时间。它们都是HTTP头中的内容。

浏览器访问服务的一次事务过程如下:

  1. 首先,浏览器向服务器发出请求,请求访问资源A;
  2. 服务器收到请求并作出响应,因为这是浏览器第一次访问资源A,因此服务器不仅会把资源A发送给浏览器,还会附带一个ETag或Last-Modified。
  3. 浏览器在接收到响应后,结束了本次事务。之后浏览器第二次请求访问资源A,这次浏览器会将ETag或Last-Modified的值一同封装进HTTP请求头中发送给服务器。
  4. 服务器通过核对ETag或Last-Modified,来决定如何响应服务器。

Last-Modified(ETag类似)

在服务器第一次响应时,Last-Modified的值为time-a,该值被存储进浏览器第二次请求的表头中,当它被送向服务器后,服务器会判断在time-a之后的时间里资源A是否被更新过,如果被更新过,则返回给浏览器新的资源A数据以及新的Last-Modified,如果在这段时间资源A未被更新过,则服务器只返回304状态码,该状态码用于告诉浏览器,在这段时间资源A未被修改,浏览器使用缓存中的资源A即可。这样不仅可以节省传输数据量,还能保证浏览器每次得到的都是最新的资源A。

(1)condition(etag_func=None, last_modified_func=None)

该装饰器用于计算ETag或Last-Modified的值,并以此判断视图是否有必要做出响应。它接受两个参数,etag_func用于计算ETag的值,last_modified_func用于计算Last-Modified。如果无法准确快速的得到这两个值,那么该装饰器也允许只接收一个参数。

from django.db import models

class Blog(models.Model):
    pass

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    published = models.DateTimeField(default=datetime.datetime.now)

def latest_entry(request, pk):
    """
    获取最新的博客内容
    :param request:
    :param pk:
    :return:
    """
    # 如果每次都想在前端页面中得到最新博客的写法
    return Entry.objects.filter(blog=pk).latest('published').published

之后应用于condition装饰器中:

from django.views.decorators.http import condition, etag, last_modified
@condition(last_modified_func=latest_entry)
def latest_entry(request, pk):
    """
    可用于生成ETag和Last-Modified 请求头
    ETag是一个与Web资源相关联的记号值,Last-Modified保存的则是浏览器请求的资源最后一次被修改的时间。它们都是HTTP头中的内容。
    :param request:
    :param pk:
    :return:
    """
    return HttpResponse('最新博客')

注意:如果在浏览器第二次请求中,请求资源未被更改,服务器会响应304状态码,在Django中,这会直接结束掉。或者说跳过condition装饰器之后的内容,因此如果一个view处理函数除condition装饰器之外有多个装饰器,则所有装饰器都必须位于condition装饰器的上面,尤其是对于 vary_on_cookie(), vary_on_headers()以及cache_control()来说,RFC 7232要求它们设置的报头必须在304响应之上存在。

(2)etag(etag_func)和last_modified(last_modified_func)

如果没有提供同时计算出Etag和Last-Modified的方法情况下使用etag(etag_func)和last_modified(last_modified_func)将更加方便,功能与condition()类似。

唯一需要注意的一点是,以下这种写法是错误的:

# Bad code. Don't do this!

@etag(etag_func)

@last_modified(last_modified_func)

def my_view(request):

    # ...

(3)不仅仅适用于Get和Head

对于Put、Post、Delete方法也同样适用,如下:

  1. 客户端请求/foo/;
  2. 服务器进行响应,其ETag为"abcd1234";
  3. 客户端发送HTTPPUT请求/foo/若要更新资源,请执行以下操作。它还发送一个If-Match:
  4. "abcd1234"标头指定它要更新的版本。
  5. 服务器检查是否更改了资源,方法是按照对GET请求(使用相同的函数)。如果资源有更改后,它将返回412状态代码,意味着precondition failed(先决条件失败)。
  6. 客户端发送GET请求/foo/,在接收412响应后,在更新内容之前检索更新的内容版本。

(4)与中间件的比较

Django提供的中间件django.middleware.http.ConditionalGetMiddleware提供了与以上类似的功能。虽然该中间件适用于许多情况,但它对于高级使用有限制:

  1. 它只适用于项目中的视图。
  2. 它并不能避免生成响应。
  3. 它只适用于HTTP GET请求。

如果有一种快速计算ETag和Last-Modified的方法,并且某些视图需要一段时间才能生成内容,则应该考虑使用condition装饰器。如果一切都运行得相当快,坚持使用中间件即可。

注意:

condition装饰器只会为Head和Get方法设置ETag和Last-Modified。

condition装饰器只适用于Django的函数视图,不适用于类视图,如果想在类视图中也这么做,可以参考method_decorator装饰器的使用或其他的方法。

使用多重装饰器

可以在一个函数或基于类的视图上使用多重装饰器,但一定要考虑装饰器执行的先后顺序。比如下例中会先执行@never_cache, 再执行@login_required。

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView2(TemplateView):
    template_name = 'secret.html'

# 等同于

@method_decorator([never_cache, login_required], name='dispatch')
class ProtectedView3(TemplateView):
    template_name = 'secret.html'

参考资料:

View decorators | Django documentation | Django

输入才有输出,吸收才能吐纳。——码字不易

以上是关于19Django开发总结:自带的常用装饰器应用场景及正确使用方法总结的主要内容,如果未能解决你的问题,请参考以下文章

python Django自带的类装饰器

python使用装饰器@函数式化django开发

函数装饰器的总结

小小装饰器

django之基于cookie和装饰器实现用户认证

使用Django自带的登录访问限制login_required