关于视图和路由的进阶技巧

关于视图和路由的进阶技巧

视图装饰器

Python装饰器让我们可以用其他函数包装特定函数。 当一个函数被一个装饰器"装饰"时,那个装饰器会被调用,接着会做额外的工作,修改变量,调用原来的那个函数。我们可以把我们想要重用的代码作为装饰器来包装一系列视图。

装饰器的语法看上去像这样:

@decorator_function
def decorated():
    pass

如果你看过Flask入门指南,那么对这个语法应该不敢到陌生。@app.route正是用于在Flask应用中给视图函数设定路由URL的装饰器。

让我们看一下在你的Flask应用中用得上的一些别的装饰器。

认证

Flask-Login使得用户认证系统的实现不再困难。 除了处理用户认证的细节之外,Flask-Login允许我们使用@login_required这个装饰器来验证用户对某些资源的访问权限。

下面是从一个用到Flask-Login和@login_required装饰器的一个示范应用中获取的例子:

from flask import render_template
from flask.ext.login import login_required, current_user

@app.route(‘/‘)
def index():
    return render_template("index.html")

@app.route(‘/dashboard‘)
@login_required
def account():
    return render_template("account.html")

注意 @app.route必须是最外面的视图装饰器。

只有已经验证的用户能够接触到/dashboard路由。你可以配置Flask-Login来重定向未验证用户到登录页面,返回HTTP 401状态码或别的你乐意的事。

参见 通过官方文档可以读到更多关于Flask-Login的内容

缓存

意淫一下,假如你的应用突然有一天在微博/朋友圈或网上别的地方火了。 于是秒秒钟会有成千上万的请求涌向你的应用。你的主页在每个请求中都要从数据库跑上一大趟,结果海量的请求导致网站慢得像教务系统一样。 你能做什么来加速这一过程,以免用户以为你的应用挂掉了?

答案不止一个,不过就本章主旨而言,标准答案是实现缓存。 特别的,我们将要用到Flask-Cache拓展。这个拓展给我们提供一个可以用来缓存某个响应一段时间的装饰器。

你可以将Flask-Cache配置成跟你想用的后台缓存一起使用。一个普遍的选择是Redis,一个容易配置和使用的软件。 假设Flask-Cache已经配置好了,下面是我们的被装饰的视图的例子:

from flask.ext.cache import Cache
from flask import Flask

app = Flask()

# 通过这个方式获取相关配置
cache = Cache(app)

@app.route(‘/‘)
@cache.cached(timeout=60)
def index():
    [...] # 进行一些数据库调用来获取所需信息
    return render_template(
        ‘index.html‘,
        latest_posts=latest_posts,
        recent_users=recent_users,
        recent_photos=recent_photos
    )

现在这个函数将会在每60秒最多运行一次。响应的结果会被保存在缓存中,并可以让期间的每一个请求获取。

注意 Flask-Cache同时允许我们记住函数 - 或缓存通过给定的参数调用的某个函数。你甚至可以缓存过于复杂的Jinja2模板片段!

自定义装饰器

在这个例子中,让我们假设我们有一个应用,每个月要求用户定期付费。如果一个用户的账户已经过期,我们要重定向他们到账单页面并把悲伤的现实告知。

myapp/util.py

from functools import wraps
from datetime import datetime

from flask import flash, redirect, url_for

from flask.ext.login import current_user

def check_expired(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        if datetime.utcnow() > current_user.account_expires:
            flash("Your account has expired. Please update your billing information.")
            return redirect(url_for(‘account_billing‘))
        return func(*args, **kwargs)

    return decorated_function
  1. 当用@check_expired装饰一个函数时,check_expired()被调用,被装饰的函数作为一个参数被传递进来。
  2. @wraps是一个装饰器,告知Python函数decorated_function()包装了视图函数func()。严格来说这不是必须的,但是这么做会使得装饰函数更加自然一些,更有利于文档和调试。
  3. decorated_function将截取原本传递给视图函数func()的args和kwargs。在这里我们检查用户的账户是否过期。如果是,我们将闪烁一则信息,并重定向到账单页面。
  4. 既然已经处理好自己的事情,我们把原来的参数交由视图函数func()去继续执行。

位于最顶部的装饰器将最先运行,然后调用下一个函数:一个视图函数或下一个装饰器。装饰器语法只是一个语法糖而已。

# 这样
@foo
@bar
def one():
    pass
# 等同于这样:
def two():
    pass
two = foo(bar(two))
r2 = two()

r1 == r2 # True

下面这个例子用到了我们自定义的装饰器和来自Flask-Cache拓展的@login_required装饰器。我们可以将多个装饰器堆成栈来一起使用。

myapp/views.py

from flask import render_template

from flask.ext.login import login_required

from . import app
from .util import check_expired

@app.route(‘/use_app‘)
@login_required
@check_expired
def use_app():
    """欢迎光临"""

    return render_template(‘use_app.html‘)

@app.route(‘/account/billing‘)
@login_required
def account_billing():
    """拿账单来"""
    # [...]
    return render_template(‘account/billing.html‘)

当一个用户试图访问/use_app时,check_expired()将在执行视图函数之前确保相关的账户资料不会泄漏。

参见 在Python文档中可以读到更多关于wraps()的内容:http://docs.python.org/2/library/functools.html#functools.wraps

URL转换器

内建转换器

当你在Flask中定义一个路由时,你可以将指定的一部分转换成Python变量并传递给视图函数。

@app.route(‘/user/<username>‘)
def profile(username):
    pass

在URL中作为的那一部分内容将作为username参数传递给视图函数。你也可以指定一个转换器过滤出特定的类型。

@app.route(‘/user/id/<int:user_id>‘)
def profile(user_id):
    pass

在这个代码块中,http://myapp.com/user/id/tomato 这个URL会返回一个404状态码 -- 此物无处觅。 这是因为URL中预期是整数的部分却遇到了一串字符串。

我们可以有另外一个接受一个字符串的视图函数。/usr/id/tomato/将调用它,而前一个函数只会被/user/id/124所调用。

下面是来自Flask文档的关于默认转换器的表格:

类型 作用
string 接受任何没有斜杠/的文本(默认)
int 接受整数
float 类似于int,但是接受的是浮点数
path 类似于string,但是接受斜杠/

自定义转换器

我们也可以按照自己的需求打造自定义的转换器。 Reddit - 一个知名的链接分享网站 - 用户在此可以创建和管理基于主题和链接分享的社区。 比如/r/python/r/flask,分别由URLreddit.com/r/pythonreddit.com/r/flask表示。 Reddit有一个有趣的特性是,通过在URL中用一个+隔开各个社区名,你可以同时看到来自多个社区的帖子。比如reddit.com/r/python+flask

我们可以使用一个自定义转换器来实现这种特性。 我们可以接受由加号隔离开来的任意数目参数,通过我们的ListConverter转换成一个列表,并传递给视图函数。

util.py

from werkzeug.routing import BaseConverter

class ListConverter(BaseConverter):

    def to_python(self, value):
        return value.split(‘+‘)

    def to_url(self, values):
        return ‘+‘.join(BaseConverter.to_url(value)
                        for value in values)

我们需要定义两个方法:to_python()to_url()。 一如其名,to_python()用于转换路径成一个Python对象,并传递给视图函数。而to_url()url_for()调用,来转换参数成为符合URL的形式。

为了使用我们的ListConverter,我们首先得将它的存在告知Flask。

/myapp/init.py

from flask import Flask

app = Flask(__name__)

from .util import ListConverter

app.url_map.converters[‘list‘] = ListConverter

注意 假如你的util模块有一行from . import app,那么有可能陷入循环import的问题。这就是为什么我等到app初始化之后才import ListConverter。

现在我们可以一如使用内建转换器一样使用我们的转换器。我们在字典中指定它的键为"list",所以我们可以在@app.route()中这样使用:

views.py

from . import app

@app.route(‘/r/<list:subreddits>‘)
def subreddit_home(subreddits):
    """显示给定subreddits里的所有帖子"""
    posts = []
    for subreddit in subreddits:
        posts.extend(subreddit.posts)

    return render_template(‘/r/index.html‘, posts=posts)

这应该会像Reddit的子社区系统一样工作。这样的方法可以用来实现你能想到的URL转换器。

总结

  • Custom URL converters can be a great way to implement creative features involving URL’s.
  • 来自Flask-Login的@login_required装饰器可以帮助你限制验证用户对视图的访问。
  • Flask-Cache插件为你提供一组装饰器来实现多种方式的缓存。
  • 我们可以开发自定义视图装饰器来帮助我们组织自己的代码,并坚守DRY(Don‘t Repeat Yourself 不重复你自己)原则。
  • 自定义的URL转换器将会让你很嗨地玩转URL。
时间: 2024-10-08 02:12:30

关于视图和路由的进阶技巧的相关文章

[Angularjs]视图和路由(四)

写在前面 关于angularjs的路由的概念基本上这篇就要结束了,通过学习,以及在实际项目中的实践,还是比较容易上手的.自己也通过angularjs做了一个在app上的一个模块,效果还是可以的. 系列文章 [Angularjs]ng-select和ng-options [Angularjs]ng-show和ng-hide [Angularjs]视图和路由(一) [Angularjs]视图和路由(二) [Angularjs]视图和路由(三) 路由模式 不同的路由模式在浏览器的地址栏中会以不同的UR

AngularJs多重视图和路由的使用

使用AngularJs来做多重视图和路由是在方便不过了,在开发过程中,都有许多的页面,而这些页面都有相同的部分,比如页面的头部和尾部通常都是一样的,变化的都是主体部分,还有就是一些后端管理的一些项目,通常不变的都是头部,尾部和菜单部分,变化的都是右边的内容部分,使用AngularJs的多重视图和路由就可以很方便的实现这样的效果.在实现之前需要准备两个文件,一个是angular的主JS文件,另一个是angular的路由JS文件,如下: <script src="js/angular.min.

[精通Objective-C]进阶技巧:使用运行时系统API

[精通Objective-C]进阶技巧:使用运行时系统API 参考书籍:<精通Objective-C>[美] Keith Lee 什么是运行时系统? 目录 精通Objective-C进阶技巧使用运行时系统API 目录 动态加载可选包 创建命令行程序 创建可选包 传入包路径 使用可选包 运行时系统API 动态代理 创建实现横切功能的协议和类 编写代理类 添加代理的目标类 测试动态代理程序 动态加载可选包 下面是使用NSbundle API动态加载自己编写的框架包的示例,共需要创建两个工程,一个命

[Angularjs]视图和路由(二)

写在前面 上篇文章主要介绍了视图和路由的基本概念,并在文章最后举了一个简单的使用案例.这篇文章将继续学习路由的配置,及相关参数的说明. 系列文章 [Angularjs]ng-select和ng-options [Angularjs]ng-show和ng-hide [Angularjs]视图和路由(一) 参数 when方法,上篇文章也提到了配置路由用到的when方法,对when方法的第二个参数可以接收哪些属性,这里将一一列举.1.controller controller:'MyControlle

[Angularjs]视图和路由(三)

写在前面 上篇文章主要介绍了路由中when方法的第二个参数,常见的几个属性,以及作用.本篇文章,将介绍和路由相关的几个常见的服务. 系列文章 [Angularjs]ng-select和ng-options [Angularjs]ng-show和ng-hide [Angularjs]视图和路由(一) [Angularjs]视图和路由(二) $routeParams 有这样一种情况,在设计路由的时候,我们希望在路由上面传递参数,但是当跳转到指定的路由的时候,我们该如何接收呢?这就用到$routePa

Django框架的使用教程--视图和路由[二]

视图和路由 1.创建一个django_test应用 2.setting中设置django_test INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_test.apps.D

AngularJS 视图和路由

在AngularJS之后引用angular-route  路由   ngRoute模块加载声明 AngularJS提供的when和otherwise两个方法来定义应用的路由 otherwise方法会在没有任何路由匹配时被调用,我们用它设置了一个默认跳转到'/'路径的路由 ng-view是一个优先级为 1000的终极指令. AngularJS不会运行同一个元素上的低优先级指令 每次触发$routeChangeSuccess事件,视图都会更新. 如果某个模板同当前的路由相关联:创建一个新的作用域:移

1.2、Django 视图与网址__进阶

Django 视图与网址进阶 1.1.简单使用: 把我们新定义的app加到settings.py中的INSTALL_APPS中 修改 HelloDjango/HelloDjango/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.message

Django Restful Framework【第五篇】分页、视图、路由、渲染器

一.分页 试问如果当数据量特别大的时候,你是怎么解决分页的? 方式a.记录当前访问页数的数据id 方式b.最多显示120页等 方式c.只显示上一页,下一页,不让选择页码,对页码进行加密 1.基于limit offset 做分页 from rest_framework.pagination import LimitOffsetPagination urls.py urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^app01/(?P<v