视图(View)

在Uliweb中,视图(view)相当于MVC框架中的Controller。

view模块的定义

在Uliweb中,在一个app的目录下,所有以views开头的文件都将被视为视图模块。当你不使用集中的 URL管理的时候,Uliweb会自动将所有有效的app的视图文件在启动时进行导入,其目的就是为了搜集 所有的URL的定义。因此,只要你按规则进行定义文件名,在其中定义的URL就可以被自动发现。因此像: views.py, views_about.py都是合法的view模块。

基于函数的View方法

view函数的定义

在Uliweb中,一个view函数可以简单地定义为:

def index():

pass

可以看到,它就是一个普通的函数。目前对于view函数,你只能使用普通的函数,而不能使用类。 每个view函数都应与一个或多个URL定义相匹配,一个完整的view定义如下:

@expose('/index')
def index():
    pass

如果一个view函数没有使用expose来修饰的话,它将不会被用户所访问。

expose后面是可以没有参数的,如:

@expose
def index():
    pass

那么这个时候,一个view函数的URL将被定义为:

/Appname/view_module_name/view_function_name

它是由app的名字,view模块名和view函数名组成的。这就是缺省的URL映射,已经很象是MVC的缺 省映射了。

view函数的参数

view函数是可以有参数的,但首先你需要先在它的URL中定义参数,如果URL中有参数,则view中就 要定义参数,如果没有则view中一般也没有。带参数的例子:

@expose('/documents/<lang>/<path:filename>')
def show_document(filename, lang):
    return _show(request, response, filename, env, lang, False)

关于URL的参数定义,参见 URL映射 的文档。这里可以看出,定义了两个参数: lang和filename,所以在下面的view函数中也定义了两个参数。

但如果你的URL中没有那么多的参数怎么办?这个可以在URL的定义中解决,如:

@expose('/documents/<path:filename>', defaults={'lang':''})
@expose('/documents/<lang>/<path:filename>')
def show_document(filename, lang):
    return _show(request, response, filename, env, lang, False)

则在第一个URL的定义中,只有一个filename参数,因此可以使用defaults来定义缺省参数。

view的环境

在Uliweb中,一个view函数是运行在某种环境中的,当需要调用view函数时,在调用前,我会向 函数的func_globals属性中注入一些对象,这些对象就可以直接在函数中使用了,你不再需要导入。 目前可用的对象有:

  • application 这是Uliweb的实例,你可以用它来访问应用的各种属性,如:application.debug 表示是否处于调用状态,还可以通过它来调用一些方法,如:application.template()来处理模 板。当然直接导入template也是可以的。不过application.template()已经预设了环境进去。
  • request 请求对象。
  • response 应答对象。这个对象在传入时是一个空对象,你可以使用它,也可以自行构造一个Response 的对象进行返回。
  • url_for 它是与expose是相反的,它用来生根据view函数生成反向的URL。详情见 URL映射 的文档。
  • redirect 用于重定义处理,后面为一个URL信息。
  • error 用于输出错误信息,它将自动查找出错页面。你只要在任何app下的templates中增加 error.html,然后出错信息可以自已来定制。它也不需要前面加return,也将抛出一个异常。
  • settings 是定义在所有有效的app settings.py文件中的配置项。注意,一个配置项的名称必须是 大写的。
  • json 用于将dict对象包装成json格式并返回。

要注意,以上的环境只能用在view函数中,当view调用其它的方法时,还是需要传入相应的参数。 有些全局性的对象将放在 uliweb/__init__.py 中,因此可以直接导入。详情见 全局环境 的文档。

view环境的扩展

如果你认为上面的环境还不够,那么你可以直接向env中增加新的对象,然后在view方法中可以通过 env.object的方式来使用它。你需要在某个app的settings.py文件中增加相应的插件处理。如:

from uliweb.core.dispatch import bind
@bind('prepare_default_env')
def prepare_default_env(sender, env):
    from uliweb.utils.textconvert import text2html
    env['text2html'] = text2html

Uliweb中已经定义了 prepare_default_env 这个plugin的插入点,你可以直接使用它。它的 作用就是向env中增加新的对象,如上面是增加了一个新的函数可以用来将文本转为HTML代码。

view的返回

在Uliweb中,view函数可以返回多种类型的结果。可能为:

  • dict 变量。如果返回一个dict的变量,说明你希望由Uliweb自动套用一个模板,这个模板需要在 templates目录下,并且模板的文件名需要与view函数名相同,后缀为.html。如果你希望使用指 定的模板文件,则需要利用response对象,将指定的模板名赋给response.template属性就行了。
  • response 对象。记得上面说过的吗?你可以直接使用response对象,比如调用它的response.write() 方法来写入返回的内容。
  • 字符串。你可以直接返回一个字符串,这样将被封装为一个普通的文本返回。
  • json 对象,使用前面讲的json函数对dict对象进行包装。
  • Reseponse实例。你可以主动创建一个Response的实例并返回。

在某些情况下,你可以调用象redirect, error来中止view的运行。

view模块的入口处理

我建议将不同的view函数按照功能和处理分为不同的文件来存放。

Uliweb支持一种view模块的入口和出口的处理。即你可以在view模块中定义名为 __begin____end__ 的特殊的方法,它没有参数,但是就象普通的view函数一样,也是在view环境中运行的。 一旦view模块中存在这个特殊的方法,在执行每个view函数之前都会先调用这个函数。因此你可以 把它理解为初始化处理,比如给一些对象赋值。举例如下:

def __begin__():
    from uliweb.contrib.auth.views import login

    if not request.user:
        return redirect(url_for(login) + '?next=%s' % url_for(doto_index))

基于类的View方法

目前Uliweb也支持类的方式来定义view,这样可以有更好的封装性。举个例子:

@expose('/user')
class UserView(object):
    def __begin__(self):
        if not request.user:
            return redirect('/login?next=%s' % request.path)

    @expose('/login')
    def login(self):
        #login process
        #URL = /login

    def register(self, name):
        #register process
        #URL = /user/register/<name>

    @expose('add')
    def add_user(self):
        #add process
        #URL = /user/add

    @expose('')
    def list(self):
        #URL = /user here '' will just user UserView class URL prefix '/user'

    def _common(self):
        #this function will not be exposed

以上只是一个示例。使用基于类的view除了是以类的方式进行组织外,与一般的view方法 没有本质的区别,但同时又有一些其它的特性。

  • 建议使用New Style Class,即从object派生。不需要从特殊的基类派生。
  • Class-View也支持类似模块级别的__begin__的处理,但它是一个方法。Uliweb在处理 Class-View会自动调用。
  • 不要使用staticmethod对类方法进行处理。因此类方法可以是一般的方法或classmethod。
  • 如果方法名开始为'_',则这个方法将不会被自动exposed,客户端将不能进行访问。这种 方式比较适合定义内部的函数。
  • 如果使用Class-View并且要在Class上使用expose,你需要安装Python 2.6。
  • 在简单情况下,你在类上使用expose('/url'),而类的方法上不使用expose,则会自动对有效的 方法生成形如:/url/method_name 的链接形式,如果还带参数,则自动生成字符串形式 的参数。例如上面的register函数。在注释中可以看到。
  • 在上面的例子中已经演示了大部分的情况:

    • 类上使用expose
    • 覆盖自动URL的生成,如login()方法。因为这里使用了'/login',相当于绝对路径。
    • 定义相对URL,如add()方法。
    • 使用缺省expose方式,同时定义了参数,如register()方法。
    • expose('')将直接使用类上的URL。
    • 内部函数,不会被客户端访问到,如_common()方法。
    • 定义了__begin__()方法,可以在执行类方法前先被处理。

注意,一般不要定义__init__()。因为Uliweb在调用Class-View时,会自动创建类的 实例,如果定义__init__()则不要带参数或全部使用缺省值。

注意,如果在方法上还想使用decorator来进行修饰,如果方法无参数,则顺序不影响 最终的URL生成。如果方法有参数,建议不要使用缺省URL的生成方式,而是主动定义 expose,并且将expose放在所有其它的decorator之上。