这是用户在 2024-10-19 8:09 为 https://web2py.com/books/default/chapter/35/03//#More-on-admin 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

 第 3 章:概述

在Windows和Mac OS系统上可以直接采用web2py的二进制包进行安装,该二进制包中已经存在Python的解释器,所以不用再单独安装Python的解释器。如果在Linux或者其他类Unix操作系统下科一采用源码进行安装,当然在Windows和Mac OS下也可以采用源码进行安装。当采用源码安装时,默认已经存在Python解释器了。

首先解压下载的软件包,得到下面的文件 web2py file.

在Windows操作系统上, 执行:

web2py.exe

在OS X操作系统上, 执行:

open web2py.app

在Unix和Linux操作系统上, 执行下面的命令:

python2.5 web2py.py

如果在Windows上采用源码安装,可以先看下Mark Hammond's "Python for Windows extensions, 然后执行:

python2.5 web2py.py

web2py程序后面可以跟很多参数,这个我们后面再讨论。

默认情况下,当执行完命令后,web2py会显示开始界面和一个控件让你设置administrator密码和网站启动的IP和端口地址。默认情况下web2py会启动在127.0.0.1:8000这个地址上(就是本机的8000端口),但你可以选择启动在可用的IP和端口上。你可以命令行查询可用的IP地址,在Windows上运行ipconfig,在OS X和Linux上运行ifconfig。当前我们假设web2py已经启动在本机的8000端口上(127.0.0.1:8000)。如果web2py启动在0.0.0.0:8000上则使用所有可用的网卡。

image

如果你没有提供administrator密码,则administration接口是不可用的。这样外网的用户就不能通过admin来对网站进行操作,比较安全。

默认情况(不使用Apache代理)administrative接口, admin,只能通过本机访问。如果admin检测到存在代理,则cookie被设置为安全模式,只能用https方式访问。所有客户端与 admin的连接都必须是本地或者加密的,不然攻击者可以采用man-in-the middle攻击或者replay攻击在服务器上执行任意代码。

当administration设置好时,可以通过下面的网页访问web2py程序:

http://127.0.0.1:8000/

打开一个web浏览器,输入上面的URL

image

点击"administrative interface"将会进入admin的登录界面.

image

adminstrator的密码是启动web2py时你填写的那个。

注意这里只能配置一个administrtor,处于安全考虑,开发者每次启动web2py时都会被要求选择一个新的admin密码,除非<recycle>选项被选中。

当用admin登录到web2py时,浏览器直接跳转到"site"页面

image

这个页面显示所有安装在web2py上安装的应用,并且可以用admin账户管理。 web2py默认包含3个应用:

  • 一个admin应用,就是你正在使用的。
  • 一个examples应用,包含在线的文档和一个web2py主页的仿制品。
  • 一个welcome应用,这是一个基本的模板用来制作其他应用。如果你创建新的应用,这个应用就是模板,包含欢迎界面。

可以参考appliances,这个地方有很多别人已经做好的应用,你可以免费下载 [appliances] . 我们鼓励web2py用户提交新的应用,不管是开源的还是闭源的,编译后的或者打包的都可以。

admin应用的site页面,你可以进行如下操作:

  • 导入一个应用,在页面的右下角有相关按钮,你需要填入应用的名称、文件的位置或者URL。
  • 卸载 一个应用. 当卸载应用时会弹出提示信息.
  • 创建 一个应用通过点击"create"按钮.
  • 打包 一个应用. 打包的应用包含所有的信息,如数据库等信息(打包格式为tar). admin导入到处的文件时,你不用解压,程序会自动解压。
  • 删除 一个应用的临时文件,如果错误的页面、会话、缓存等。
  • 编辑 一个应用.

当你用admin创建一个应用时,它是以“welcome”为框架的,默认包含一个SQLite数据库,可以通过修改"models/db.py"来进行数据的鉴权、操作、和配置他们,默认还包含"controller/default.py"文件,用来管理“index”、“deownload”、“user”和调用服务。下面我们假设我们的工程中不存在这些文件,我们将一步步创建我们的应用。

web2py也包含一个向导,用来替换默认的布局等,在本章后续部分会进行介绍。

我们首先创建一个简单的web应用,显示”Hello from MyApp“,和统计每个用户的访问次数。

首先我们用adminsite创建一个简单的应用。

image

当你点击”create“按钮后,我们的应用程序就创建出来了,它基于”welcome“应用的,所以我们需要对他进行改造。

image

如果要查看应用的结果,可以访问:

http://127.0.0.1:8000/myapp

现在你已经拥有一个”welcome“应用的框架了。

在编辑应用,点击”修改“按钮。

修改页面显示了应用中包含的所有文件。

每个web2py应用程序都由某些文件组成,其中大多数属于isx类别之一:

  • models:描述数据表示形式。
  • 控制器:描述应用程序逻辑和工作流。
  • views:描述数据表示。
  • languages:描述如何将应用程序演示文稿翻译成其他语言。
  • modules:属于应用程序的 Python 模块。
  • 静态文件:静态图片、CSS 文件[css-w,css-o,css-school]、JavaScript 文件[js-w,js-b]等。
  • plugins:旨在协同工作的文件组。

一切都按照 Model-View-Controller 设计模式整齐地组织起来。编辑页面中的每个部分都对应于应用程序文件夹中的一个子文件夹。

请注意,章节标题将切换其内容。静态文件下的文件夹名称也是可折叠的。

该部分中列出的每个文件都对应于物理上位于子文件夹中的文件。通过管理界面对文件执行的任何操作(创建、编辑、删除)都可以使用您最喜欢的编辑器直接从 shell 执行。

该应用程序包含其他类型的文件(数据库、会话文件、错误文件等),但它们未在编辑页面上列出,因为它们不是由管理员创建或修改的;它们由应用程序本身创建和修改。

控制器包含应用程序的逻辑和工作流。每个 URL 都映射到对控制器 (actions) 中某个函数的调用。有两个默认控制器:“appadmin.py”和“default.py”。appadmin 提供数据库管理界面;我们现在不需要它。“default.py” 是您需要编辑的控制器,当 URL 中未指定控制器时,默认情况下会调用该控制器。按如下方式编辑 “index” 函数:

def index():
    return "Hello from MyApp"

在线编辑器如下所示:

image

保存它并返回编辑页面。单击索引链接以访问新创建的页面。

当您访问 URL 时

http://127.0.0.1:8000/myapp/default/index

调用 myApp 应用程序的默认控制器中的 index 操作。它返回浏览器为我们显示的字符串。它应该看起来像这样:

image

现在,按如下方式编辑 “index” 函数:

def index():
    return dict(message="Hello from MyApp")

此外,在编辑页面中,编辑视图 “default/index.html” (与操作关联的视图文件),并将该文件的现有内容完全替换为以下内容:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
   </body>
</html>

现在,该操作返回定义消息的字典。当一个动作返回一个字典时,web2py 会查找名称为

[controller]/[function].[extension]

并执行它。此处 [extension] 是请求的扩展。如果未指定扩展名,则默认为 “html”,这就是我们在这里将假设的内容。在此假设下,视图是一个 HTML 文件,它使用特殊的 {{ }} 标签嵌入 Python 代码。特别是,在这个例子中,{{=message}} 指示 web2py 将标记的代码替换为操作返回的消息值。请注意,这里的 message 不是 web2py 关键字,而是在操作中定义的。到目前为止,我们还没有使用任何 web2py 关键字。

如果 web2py 没有找到请求的视图,它会使用每个应用程序附带的 “generic.html” 视图。

如果指定了 “html” 以外的扩展名(例如 “json”),并且找不到视图文件 “[controller]/[function].json”,web2py 会查找视图 “generic.json”。web2py 附带 generic.html、generic.json、generic.xml 和 generic.rss。可以为每个应用程序单独修改这些通用视图,并且可以轻松添加其他视图。

泛型视图是一种开发工具。在 production 中,每个 action 都应该有它自己的 view。事实上,默认情况下,通用视图仅从 localhost 启用。

您还可以使用 response.view = 'default/something.html'

在第 10 章中阅读有关此主题的更多信息。

如果您返回 “EDIT” 并单击 index,您现在将看到以下 HTML 页面:

image

出于调试目的,您始终可以将

{{=response.toolbar()}}

到视图中的代码中,它将向你显示一些有用的信息,包括 Request、Response 和 Session 对象,并列出所有数据库查询及其时间。

现在,让我们向此页面添加一个计数器,该计数器将计算同一访客显示该页面的次数。

web2py 使用会话和 cookie 自动透明地跟踪访问者。对于每个新访客,它会创建一个会话并分配一个唯一的“session_id”。会话是存储在服务器端的变量的容器。唯一 ID 通过 Cookie 发送到浏览器。当访问者从同一应用程序请求另一个页面时,浏览器会将 cookie 发回,web2py 会检索它,并恢复相应的会话。

要使用会话,请修改默认控制器:

def index():
    if not session.counter:
        session.counter = 1
    else:
        session.counter += 1
    return dict(message="Hello from MyApp", counter=session.counter)

请注意,counter 不是 web2py 关键字,但 session 是。我们要求 web2py 检查会话中是否有 counter 变量,如果没有,则创建一个并将其设置为 1。如果计数器在那里,我们要求 web2py 将计数器增加 1。最后,我们将 counter 的值传递给视图。

编写相同函数的更紧凑的方法是:

def index():
    session.counter = (session.counter or 0) + 1
    return dict(message="Hello from MyApp", counter=session.counter)

现在修改视图以添加一行显示 counter 的值:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
      <h2>Number of visits: {{=counter}}</h2>
   </body>
</html>

当您一次又一次地访问索引页面时,您应该得到以下 HTML 页面:

image

计数器与每个访客关联,并且每次访客重新加载页面时都会递增。不同的访客看到不同的计数器。

现在创建两个页面(第一个和第二个),其中第一个页面创建一个表单,询问访客的姓名,然后重定向到第二个页面,该页面按姓名问候访客。

yUML diagram

在默认控制器中编写相应的 action:

def first():
    return dict()

def second():
    return dict()

然后为第一个操作创建视图 “default/first.html”,并输入:

{{extend 'layout.html'}}
What is your name?
<form action="second">
  <input name="visitor_name" />
  <input type="submit" />
</form>

最后,为第二个操作创建一个视图 “default/second.html”:

{{extend 'layout.html'}}
<h1>Hello {{=request.vars.visitor_name}}</h1>

在这两个视图中,我们扩展了 web2py 附带的基本 “layout.html” 视图。布局视图使两个页面的外观保持一致。布局文件可以很容易地编辑和替换,因为它主要包含 HTML 代码。

如果您现在访问第一页,请键入您的姓名:

image

并提交表格,您将收到问候语:

image

我们之前使用的表单提交机制非常常见,但这不是好的编程实践。所有输入都应该经过验证,在上面的示例中,验证的负担将落在第二个操作上。因此,执行验证的操作与生成表单的操作不同。这往往会导致代码中的冗余。

表单提交的更好模式是将表单提交到生成表单的同一操作,在我们的示例中为 “first”。“第一个”操作应接收变量、处理它们、在服务器端存储它们,并将访问者重定向到“第二个”页面,该页面将检索变量。此机制称为回发

yUML diagram

修改默认 controller 实现自提交:

def first():
    if request.vars.visitor_name:
        session.visitor_name = request.vars.visitor_name
        redirect(URL('second'))
    return dict()

def second():
    return dict()

然后修改 “default/first.html” 视图:

{{extend 'layout.html'}}
What is your name?
<form>
  <input name="visitor_name" />
  <input type="submit" />
</form>

并且 “default/second.html” 视图需要从 session 而不是 request.vars 中检索数据:

{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

从访客的角度来看,自行提交的行为与之前的实施完全相同。我们尚未添加验证,但现在很明显,验证应由第一个操作执行。

这种方法更好,因为访客的姓名保留在会话中,并且应用程序中的所有操作和视图都可以访问该名称,而不必显式传递。

请注意,如果在设置访客名称之前调用了“第二个”操作,则会显示“Hello anonymous”,因为session.visitor_name返回 None。或者,我们可以在控制器中添加以下代码(在第二个函数内):

if not request.function=='first' and not session.visitor_name:
    redirect(URL('first'))

这是一种通用机制,可用于在 controller 上强制执行授权,但有关更强大的方法,请参见 Chapter 9。

使用 web2py,我们可以更进一步,要求 web2py 为我们生成表单,包括验证。web2py 提供了与等效 HTML 标签相同的名称的帮助程序(FORM、INPUT、TEXTAREA 和 SELECT/OPTION)。它们可用于在控制器或视图中构建表单。

例如,以下是重写第一个操作的一种可能方法:

def first():
    form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
              INPUT(_type='submit'))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

其中,我们说 FORM 标签包含两个 INPUT 标签。input 标签的属性由以下划线开头的命名参数指定。requires 参数不是 tag 属性(因为它不以下划线开头),但它为 visitor_name 的值设置验证器。

这是另一个创建相同表单的更好的 wat:

def first():
    form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

通过将表单对象嵌入到 “default/first.html” 视图中,可以很容易地将其序列化为 HTML。

{{extend 'layout.html'}}
What is your name?
{{=form}}

form.process() 方法应用验证器并返回表单本身。如果表单已处理并通过验证,则 form.accepted 变量设置为 True。如果自行提交的表单通过验证,它会将变量存储在会话中,并像以前一样进行重定向。如果表单未通过验证,则会将错误消息插入表单中并向用户显示,如下所示:

image

在下一节中,我们将展示如何从模型自动生成表单。

这里,作为另一个示例,我们希望创建一个 Web 应用程序,该应用程序允许管理员发布图像并为其命名,并允许 Web 站点的访问者查看命名图像并提交注释。

和以前一样,从 admin站点页面,创建一个名为 images 的新应用程序,然后导航到编辑页面:

image

我们首先创建一个模型,该模型表示应用程序中的持久数据(要上传的图像、其名称和注释)。首先,您需要创建/编辑一个模型文件,由于缺乏想象力,我们称之为 “db.py”。我们假设下面的代码将替换 “db.py” 中的任何现有代码。模型和控制器必须具有 .py 扩展,因为它们是 Python 代码。如果未提供扩展,则由 web2py 附加。相反,视图具有 .html 扩展,因为它们主要包含 HTML 代码。

通过单击相应的 “edit” 按钮编辑 “db.py” 文件:

image

 并输入以下内容:

db = DAL("sqlite://storage.sqlite")

db.define_table('image',
   Field('title', unique=True),
   Field('file', 'upload'),
   format = '%(title)s')

db.define_table('comment',
   Field('image_id', db.image),
   Field('author'),
   Field('email'),
   Field('body', 'text'))

db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.comment.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.comment.author.requires = IS_NOT_EMPTY()
db.comment.email.requires = IS_EMAIL()
db.comment.body.requires = IS_NOT_EMPTY()

db.comment.image_id.writable = db.comment.image_id.readable = False


让我们逐行分析一下。


第 1 行定义了一个名为 db 的全局变量,它表示数据库连接。在这种情况下,它是与存储在文件“applications/images/databases/storage.sqlite”中的 SQLite 数据库的连接。在 SQLite 情况下,如果数据库不存在,则创建它。您可以更改文件的名称以及全局变量 db 的名称,但为它们指定相同的名称很方便,以便于记忆。


第 3-5 行定义表 “image”。define_tableDB 对象的一个方法。第一个参数 “image” 是我们定义的表的名称。其他参数是属于该表的字段。此表具有一个名为“title”的字段、一个名为“file”的字段和一个名为“id”的字段,该字段用作表主键(“id”未显式声明,因为默认情况下所有表都有一个 id 字段)。字段 “title” 是一个字符串,字段 “file” 的类型为 “upload”。“upload” 是 web2py 数据抽象层 (DAL) 使用的一种特殊类型的字段,用于存储已上传文件的名称。web2py 知道如何上传文件(如果文件很大,则通过流式),安全地重命名它们,并存储它们。


定义表后,web2py 会采取以下几种可能的操作之一:


  • 如果表不存在,则创建表;

  • 如果表存在且与定义不对应,则表会相应地更改,如果字段具有不同的类型,web2py 会尝试转换其内容;

  • 如果表存在并且对应于定义,则 web2py 不执行任何操作。


此行为称为 “迁移”。在 web2py 中,迁移是自动的,但可以通过传递 migrate=False 作为 define_table 的最后一个参数来禁用每个表。


第 6 行定义表的格式字符串。它确定如何将记录表示为字符串。请注意,format 参数也可以是获取记录并返回字符串的函数。例如:

format=lambda row: row.title


第 8-12 行定义了另一个名为 “comment” 的表。评论具有“author”、“email”(我们打算存储评论作者的电子邮件地址)、“text”类型的“body”(我们打算使用它来存储作者发布的实际评论)和一个 reference 类型的“image_id”字段,该字段通过“id”字段指向 db.image


在第 14 行中,db.image.title 表示表 “image” 的字段 “title”。属性 requires 允许你设置将由 web2py 表单强制执行的要求/约束。这里我们要求 “title” 是唯一的:

IS_NOT_IN_DB(db, db.image.title)


请注意,这是可选的,因为它是在给定 Field('title', unique=True) 的情况下自动设置的。


表示这些约束的对象称为验证器。多个验证人可以分组到一个列表中。验证器按其出现的顺序执行。IS_NOT_IN_DB(a, b) 是一个特殊的验证器,用于检查新记录的字段 b 的值是否尚未在 a 中。


第 15 行要求表 “comment” 的字段 “image_id” 为 db.image.id。就数据库而言,我们在定义表 “comment” 时已经声明了这一点。现在我们明确地告诉模型,当发布新评论时,web2py 也应该在表单处理级别强制执行此条件,以便无效值不会从输入表单传播到数据库。我们还要求“image_id”由相应记录的“title”“'%(title)s'表示。


第 20 行表示表 “comment” 的字段 “image_id” 不应显示在表单中,writable=False,甚至不应以只读形式 readable=False 显示。


第 15-17 行中验证器的含义应该是显而易见的。


请注意,验证器

db.comment.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')


如果我们为引用表指定格式,则可以省略(并且是自动的):

db.define_table('image', ..., format='%(title)s')


其中,格式可以是字符串或获取记录并返回字符串的函数。


定义模型后,如果没有错误,web2py 会创建一个应用程序管理界面来管理数据库。您可以通过编辑页面中的“数据库管理”链接或直接访问它:

http://127.0.0.1:8000/images/appadmin


以下是 appadmin 界面的屏幕截图:

image


这个接口在控制器中被编码为“appadmin.py”,相应的视图为“appadmin.html”。从现在开始,我们将这个接口简称为 appadmin。它允许管理员插入新的数据库记录、编辑和删除现有记录、浏览表以及执行数据库联接。


首次访问 appadmin 时,将执行模型并创建表。web2py DAL 将 Python 代码转换为特定于所选数据库后端(本例中为 SQLite)的 SQL 语句。您可以通过单击 “models” 下的 “sql.log” 链接从编辑页面查看生成的 SQL。请注意,在创建表之前,该链接不存在。

image


如果你要编辑模型并再次访问 appadmin,web2py 将生成 SQL 来更改现有表。生成的 SQL 将登录到 “sql.log”。


现在返回 appadmin 并尝试插入新的映像记录:

image


web2py 已将 db.image.file 的 “upload” 字段转换为该文件的上传表单。提交表单并上传图像文件时,将以保留扩展名的安全方式重命名文件,以新名称保存在应用程序“uploads”文件夹下,新名称存储在 db.image.file 字段中。此过程旨在防止目录遍历攻击。


请注意,每个字段类型都由一个小组件呈现。默认 widget 可以被覆盖。


当你在 appadmin 中点击一个表名时,web2py 会选择当前表上的所有记录,由 DAL 查询标识

db.image.id > 0

 并呈现结果。

image


您可以通过编辑 SQL 查询并按 [Submit] 来选择不同的记录集。


要编辑或删除单个记录,请单击记录 ID 号。


由于 IS_IN_DB 验证器,参考字段 “image_id” 由下拉菜单呈现。下拉列表中的项目存储为键 (db.image.id),但由验证程序指定的 db.image.title 表示。


验证器是功能强大的对象,它们知道如何表示字段、筛选字段值、生成错误以及格式化从字段中提取的值。


下图显示了当您提交未通过验证的表单时会发生什么情况:

image


appadmin 自动生成的相同表单也可以通过 SQLFORM 帮助程序以编程方式生成,并嵌入到用户应用程序中。这些表单对 CSS 友好,并且可以自定义。


每个应用程序都有自己的 appadmin;因此,可以修改 AppAdmin 本身,而不会影响其他应用程序。


到目前为止,该应用程序知道如何存储数据,我们已经看到了如何通过 appadmin 访问数据库。对 appadmin 的访问仅限于管理员,它不用作应用程序的生产 Web 界面;因此,本演练的下一部分。具体来说,我们想要创建:


  • 一个“索引”页面,其中列出了按标题排序的所有可用图片,并链接到图片的详情页面。

  • 一个“show/[id]”页面,用于向访客显示请求的图像,并允许访客查看和发布评论。

  • 用于下载已上传图像的 “download/[name]” 操作。


这在这里以示意性表示:

yUML diagram


返回编辑页面并编辑 “default.py” 控制器,将其内容替换为以下内容:

def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)


此操作将返回一个词典。字典中项目的键被解释为传递给与操作关联的视图的变量。在开发时,如果没有视图,则动作由每个web2py应用程序提供的“generic.html”视图呈现。


index 操作从表 image 中选择所有字段 (db.image.ALL),按 db.image.title 排序。select 的结果是包含记录的 Rows 对象。将其分配给操作返回给视图的名为 images 的局部变量。images 是可迭代的,其元素是选定的行。对于每一行,列可以作为字典访问: images[0]['title'] 或等效为 images[0].title


如果您不编写视图,则字典由 “views/generic.html” 呈现,并且对 index 操作的调用将如下所示:

image


您还没有为此操作创建视图,因此 web2py 以普通表格形式呈现记录集。


继续为 index 操作创建视图。返回 admin,编辑 “default/index.html” 并将其内容替换为以下内容:

{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>


首先要注意的是,视图是带有特殊 {{...}} 标签的纯 HTML。嵌入在 {{...}} 中的代码是纯 Python 代码,但需要注意的是:缩进无关紧要。代码块以冒号 (:) 结尾的行开头,以关键字 pass 开头的行结尾。在某些情况下,块的结尾从上下文中是明显的,并且不需要使用 pass


第 5-7 行循环显示图像行,每行图像显示:

LI(A(image.title, _href=URL('show', args=image.id))


这是一个 <li>...</li> 标记包含 <a href=“...”>...</a> 标记,其中包含 image.title。超文本引用(href 属性)的值为:

URL('show', args=image.id)


即,与当前请求相同的应用程序和控制器中的 URL,该请求调用名为 “show” 的函数,将单个参数传递给函数 args=image.idLIA 等是映射到相应 HTML 标签的 web2py 辅助方法。它们的 unnamed 参数被解释为要序列化并插入到标签的 innerHTML 中的对象。以下划线开头的命名参数(例如 _href)被解释为标签属性,但没有下划线。例如,_hrefhref 属性,_classclass 属性,依此类推。


例如,以下语句:

{{=LI(A('something', _href=URL('show', args=123))}}

 呈现为:

<li><a href="/images/default/show/123">something</a></li>


一些帮助程序(INPUT、TEXTAREA、OPTIONSELECT)也支持一些不以下划线开头的特殊命名属性(valuerequires)。它们对于构建自定义表单非常重要,稍后将讨论。


返回编辑页面。它现在指示 “default.py expose index”。通过点击 “index”,您可以访问新创建的页面:

http://127.0.0.1:8000/images/default/index

 如下所示:

image


如果单击映像名称链接,则会将您定向到:

http://127.0.0.1:8000/images/default/show/1


这会导致错误,因为您尚未在控制器 “default.py” 中创建名为 “show” 的操作。


让我们编辑 “default.py” 控制器并将其内容替换为:

def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

def show():
    image = db(db.image.id==request.args(0)).select().first()
    db.comment.image_id.default = image.id
    form = SQLFORM(db.comment)
    if form.process().accepted:
        response.flash = 'your comment is posted'
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

def download():
    return response.download(request, db)


控制器包含两个操作:“show”和“download”。“show” 操作选择具有从请求 args 解析的 id 的图像以及与图像相关的所有注释。然后,“show” 将所有内容传递给视图 “default/show.html”。


映像 ID 引用:

URL('show', args=image.id)


在 “default/index.html” 中,可以通过以下方式访问:

request.args(0)

 从 “show” 操作。


“download” 操作需要 request.args(0) 中的文件名,构建该文件应该在的位置的路径,并将其发送回客户端。如果文件太大,它会流式传输文件而不会产生任何内存开销。


请注意以下语句:


  • 第 7 行仅使用指定的字段为 db.comment 表创建插入表单 SQLFORM。

  • 第 8 行设置引用字段的值,该字段不是输入表单的一部分,因为它不在上面指定的字段列表中。

  • 第 9 行在当前会话中处理提交的表单(提交的表单变量在 request.vars 中)(该会话用于防止重复提交,并强制导航)。如果提交的表单变量经过验证,则新注释将插入到 db.comment 表中;否则,表单将被修改为包含错误消息(例如,如果作者的电子邮件地址无效)。这一切都在第 9 行完成!

  • 只有在将记录插入数据库表后,如果接受表单,则仅执行第 10 行。response.flash 是一个 web2py 变量,显示在视图中,用于通知访客发生了某些事情。

  • 第 11 行选择引用当前图像的所有注释。


“download” 操作已在 Scaffolding 应用程序的 “default.py” 控制器中定义。


“download” 操作不返回字典,因此它不需要视图。不过,“show” 操作应该有一个视图,因此请返回 admin 并创建一个名为 “default/show.html” 的新视图。


编辑此新文件并将其内容替换为以下内容:

{{extend 'layout.html'}}
<h1>Image: {{=image.title}}</h1>
<center>
<img width="200px"
     src="{{=URL('download', args=image.file)}}" />
</center>
{{if len(comments):}}
  <h2>Comments</h2><br /><p>
  {{for comment in comments:}}
    <p>{{=comment.author}} says <i>{{=comment.body}}</i></p>
  {{pass}}</p>
{{else:}}
  <h2>No comments posted yet</h2>
{{pass}}
<h2>Post a comment</h2>
{{=form}}


此视图通过调用 <img ... /> 标签内的 “download” 操作来显示 image.file。如果有注释,它会循环访问它们并显示每个注释。


以下是所有内容对访问者的显示方式。

image


当访客通过此页面提交评论时,该评论将存储在数据库中,并附加到页面底部。


web2py 还提供了一个 CRUD (Create/Read/Update/Delete) API,可以进一步简化表单。要使用 CRUD,必须在某处定义它,例如在文件 “db.py” 中:

from gluon.tools import Crud
crud = Crud(db)


这两条线路已经在脚手架应用中。


crud 对象提供了高级方法,例如:

form = crud.create(table)


,可用于替换编程模式:

form = SQLFORM(table)
if form.process().accepted:
    session.flash = '...'
    redirect('...')


在这里,我们使用 crud 重写了之前的 “show” 操作,并进行了更多改进:

def show():
    image = db.image(request.args(0)) or redirect(URL('index'))
    db.comment.image_id.default = image.id
    form = crud.create(db.comment,
                       message='your comment is posted',
		       next=URL(args=image.id))
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)


首先请注意,我们使用了语法

db.image(request.args(0)) or redirect(...)


以获取所需的记录。由于 'table(id) 如果未找到记录,则返回 None,因此在这种情况下,我们可以在一行中使用 或 redirect(...)。


crud.create下一个参数是接受表单后要重定向到的 URL。message 参数是接受时要显示的参数。您可以在第 7 章中阅读有关 CRUD 的更多信息。


用于基于角色的访问控制的 web2py API 相当复杂,但现在我们将限制对 show 操作的访问仅限于经过身份验证的用户,将更详细的讨论推迟到第 9 章。


要限制对经过身份验证的用户的访问,我们需要完成三个步骤。在模型中,例如 “db.py”,我们需要添加:

from gluon.tools import Auth
auth = Auth(db)
auth.define_tables()


在我们的控制器中,我们需要添加一个动作:

def user():
    return dict(form=auth())


这足以启用 login、register、logout etc. pages。默认布局还将在右上角显示相应页面的选项。

image


我们现在可以装饰我们想要限制的函数,例如:

@auth.requires_login()
def show():
    image = db.image(request.args(0)) or redirect(URL('index'))
    db.comment.image_id.default = image.id
    form = crud.create(db.comment, next=URL(args=image.id),
                     message='your comment is posted')
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

 任何访问

http://127.0.0.1:8000/images/default/show/[image_id]


将需要登录。如果用户未登录,则用户将被重定向到

http://127.0.0.1:8000/images/default/user/login

image


user 函数还公开了以下操作:

http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized


现在,首次使用的用户需要注册才能登录并阅读或发布评论。


auth 对象和 user 函数都已在基架应用程序中定义。auth 对象是高度可定制的,可以通过插件处理电子邮件验证、注册批准、CAPTCHA 和备用登录方法。


我们可以使用 SQLFORM.gridSQLFORM.smartgrid 小工具进一步改进这一点,为我们的应用程序创建一个管理界面:

@auth.requires_membership('manager')
def manage():
    grid = SQLFORM.smartgrid(db.image)
    return dict(grid=grid)


与关联的 “views/default/manage.html”

{{extend 'layout.html'}}
<h2>Management Interface</h2>
{{=grid}}


使用 appadmin 创建一个组 “manager” 并使一些用户成为该组的成员。他们将无法访问

http://127.0.0.1:8000/images/default/manage

 并浏览、搜索:

image


创建、更新和删除图像及其评论:

image


您可以通过编辑 “views/layout.html” 来配置默认布局,但也可以在不编辑 HTML 的情况下配置它。事实上,“static/base.css” 样式表在第 5 章中有很好的文档和描述。您可以更改颜色、列、大小、边框和背景,而无需编辑 HTML。如果要编辑菜单、标题或副标题,可以在任何模型文件中进行编辑。脚手架应用程序在文件 “models/menu.py” 中设置这些参数的默认值:

response.title = request.application
response.subtitle = T('customize me!')
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]

In this section, we build a wiki, from scratch and without using the extended functionality provided by plugin_wiki which is described in chapter 12. The visitor will be able to create pages, search them (by title), and edit them. The visitor will also be able to post comments (exactly as in the previous applications), and also post documents (as attachments to the pages) and link them from the pages. As a convention, we adopt the Markmin syntax for our wiki syntax. We will also implement a search page with Ajax, an RSS feed for the pages, and a handler to search the pages via XML-RPC[xmlrpc] .


下图列出了我们需要实施的操作以及我们打算在它们之间建立的链接。

yUML diagram


首先创建一个新的脚手架应用程序,将其命名为 “mywiki”。


模型必须包含三个表:page、comment 和 document。注释和文档引用页面,因为它们都属于页面。文档包含上传类型的 file 字段,如前面的图像应用程序中所示。


这是完整的模型:

db = DAL('sqlite://storage.sqlite')

from gluon.tools import *
auth = Auth(db)
auth.define_tables()
crud = Crud(db)

db.define_table('page',
    Field('title'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id),
    format='%(title)s')

db.define_table('comment',
    Field('page_id', db.page),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id))

db.define_table('document',
    Field('page_id', db.page),
    Field('name'),
    Field('file', 'upload'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id),
    format='%(name)s')

db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False

db.comment.body.requires = IS_NOT_EMPTY()
db.comment.page_id.readable = db.comment.page_id.writable = False
db.comment.created_by.readable = db.comment.created_by.writable = False
db.comment.created_on.readable = db.comment.created_on.writable = False

db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False


编辑控制器 “default.py” 并创建以下操作:


  • index:列出所有 wiki 页面

  • create:发布另一个 Wiki 页面

  • show:显示 Wiki 页面及其评论,并附加评论

  • 编辑:编辑现有页面

  • 文档:管理附加到页面的文档

  • download:下载文档(如 images 示例中所示)

  • search:显示搜索框,并通过 Ajax 回调返回所有匹配的标题作为访客类型

  • callback:Ajax 回调函数。它返回访客键入时嵌入到搜索页面中的 HTML。


这是 “default.py” 控制器:

def index():
     """ this controller returns a dictionary rendered by the view
         it lists all wiki pages
     >>> index().has_key('pages')
     True
     """
     pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
     return dict(pages=pages)

@auth.requires_login()
def create():
     "creates a new empty wiki page"
     form = crud.create(db.page, next=URL('index'))
     return dict(form=form)

def show():
     "shows a wiki page"
     this_page = db.page(request.args(0)) or redirect(URL('index'))
     db.comment.page_id.default = this_page.id
     form = crud.create(db.comment) if auth.user else None
     pagecomments = db(db.comment.page_id==this_page.id).select()
     return dict(page=this_page, comments=pagecomments, form=form)

@auth.requires_login()
def edit():
     "edit an existing wiki page"
     this_page = db.page(request.args(0)) or redirect(URL('index'))
     form = crud.update(db.page, this_page,
                        next=URL('show',args=request.args))
     return dict(form=form)

@auth.requires_login()
def documents():
     "browser, edit all documents attached to a certain page"
     page = db.page(request.args(0)) or redirect(URL('index'))
     db.document.page_id.default = page.id
     db.document.page_id.writable = False
     grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id])
     return dict(page=page, grid=grid)

def user():
     return dict(form=auth())

def download():
     "allows downloading of documents"
     return response.download(request, db)

def search():
     "an ajax wiki search page"
     return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
              _onkeyup="ajax('callback', ['keyword'], 'target');")),
              target_div=DIV(_id='target'))

def callback():
     "an ajax callback that returns a <ul> of links to wiki pages"
     query = db.page.title.contains(request.vars.keyword)
     pages = db(query).select(orderby=db.page.title)
     links = [A(p.title, _href=URL('show',args=p.id)) for p in pages]
     return UL(*links)


第 2-6 行提供了 index 操作的注释。注释中的第 4-5 行被 python 解释为测试代码 (doctest)。可以通过管理界面运行测试。在这种情况下,测试将验证 index 操作是否运行没有错误。


第 18、27 和 35 行尝试获取 id 在 request.args(0) 中的页面记录。


第 13 行、第 20 行定义和处理为新页面和新注释创建表单。


第 28 行定义并处理 Wiki 页面的更新表单。


第 38 行创建了一个 grid 对象,该对象允许浏览、添加和更新链接到页面的注释。


一些神奇的事情发生在第 51 行。设置了 INPUT 标签 “keyword” 的 onkeyup 属性。每次访客释放密钥时,都会在客户端执行 onkeyup 属性内的 JavaScript 代码。这是 JavaScript 代码:

ajax('callback', ['keyword'], 'target');


ajax 是文件 “web2py.js” 中定义的一个 JavaScript 函数,该文件包含在默认的 “layout.html” 中。它采用三个参数:执行同步回调的操作的 URL、要发送到回调的变量 ID 列表 ([“keyword”]) 以及必须插入响应的 ID (“target”)。


只要你在搜索框中输入内容并释放一个键,客户端就会调用服务器并发送 'keyword' 字段的内容,当服务器响应时,响应将作为 'target' 标签的 innerHTML 嵌入到页面本身中。


'target' 标签是第 52 行中定义的 DIV。也可以在视图中定义它。


以下是视图 “default/create.html” 的代码:

{{extend 'layout.html'}}
<h1>Create new wiki page</h1>
{{=form}}


如果您访问 create 页面,则会看到以下内容:

image


以下是视图 “default/index.html” 的代码:

{{extend 'layout.html'}}
<h1>Available wiki pages</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
     {{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]


它会生成以下页面:

image


以下是视图 “default/show.html” 的代码:

{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Comments</h2>
{{for comment in comments:}}
  <p>{{=db.auth_user[comment.created_by].first_name}} on {{=comment.created_on}}
          says <I>{{=comment.body}}</i></p>
{{pass}}
<h2>Post a comment</h2>
{{=form}}


如果您希望使用 markdown 语法而不是 markmin 语法:

from gluon.contrib.markdown import WIKI


并使用 WIKI 而不是 MARKMIN 帮助程序。或者,您可以选择接受原始 HTML 而不是 markmin 语法。在这种情况下,您将替换:

{{=MARKMIN(page.body)}}

 跟:

{{=XML(page.body)}}


(这样 XML 就不会被转义,就像默认的 web2py 行为一样)。


这可以通过以下方式更好地完成:

{{=XML(page.body, sanitize=True)}}


通过设置 sanitize=True,您可以告诉 web2py 转义不安全的 XML 标签,例如 “


现在,如果您从索引页面单击页面标题,则可以看到您创建的页面:

image


以下是视图 “default/edit.html” 的代码:

{{extend 'layout.html'}}
<h1>Edit wiki page</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
{{=form}}


它会生成一个看起来与 create 页面几乎相同的页面。


以下是视图 “default/documents.html” 的代码:

{{extend 'layout.html'}}
<h1>Documents for page: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Documents</h2>
{{=grid}}


如果您从 “show” 页面单击 documents,您现在可以管理附加到该页面的文档。

image


最后,这是视图 “default/search.html” 的代码:

{{extend 'layout.html'}}
<h1>Search wiki pages</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}


生成以下 Ajax 搜索表单:

image


您也可以尝试通过访问以下 URL 直接调用回调操作:

http://127.0.0.1:8000/mywiki/default/callback?keyword=wiki


如果你查看页面源,你会看到回调返回的 HTML:

<ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul>


使用 web2py 从存储的页面生成 RSS 源很容易,因为 web2py 包含 gluon.contrib.rss2。只需将以下操作附加到默认控制器即可:

def news():
    "generates rss feed form the wiki pages"
    reponse.generic_patterns = ['.rss']
    pages = db().select(db.page.ALL, orderby=db.page.title)
    return dict(
       title = 'mywiki rss feed',
       link = 'http://127.0.0.1:8000/mywiki/default/index',
       description = 'mywiki news',
       created_on = request.now,
       items = [
          dict(title = row.title,
               link = URL('show', args=row.id),
               description = MARKMIN(row.body).xml(),
               created_on = row.created_on
               ) for row in pages])


以及当您访问该页面时

http://127.0.0.1:8000/mywiki/default/news.rss


您会看到 Feed(确切的输出取决于 Feed 阅读器)。请注意,由于 URL 中的 .rss 扩展,dict 会自动转换为 RSS。

image


web2py 还包括 FeedParser 来读取第三方 Feed。


最后,让我们添加一个 XML-RPC 处理程序,允许以编程方式搜索 wiki:

service = Service()

@service.xmlrpc
def find_by(keyword):
     "finds pages that contain keyword for XML-RPC"
     return db(db.page.title.contains(keyword).select().as_list()

def call():
    "exposes all registered services, including XML-RPC"
    return service()


在这里,处理程序操作只是发布 (通过 XML-RPC) 在列表中指定的函数。在本例中,find_byfind_by 不是一个操作(因为它需要一个参数)。它使用 .select() 查询数据库,然后使用 .response 将记录提取为列表并返回列表。


以下是如何从外部 Python 程序访问 XML-RPC 处理程序的示例。

>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
    'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
        print item['created_on'], item['title']


可以从许多理解 XML-RPC 的其它编程语言(包括 C、C++、C# 和 Java)访问该处理程序。


每种字段类型都有三种不同的表示形式:日期日期时间和时间


  • 数据库表示

  • 内部 web2py prepresentation

  • 表单和表格中的字符串表示


数据库表示是一个内部问题,不会影响代码。在内部,在 web2py 级别,它们分别存储为 datetime.datedatetime.datetimedatetime.time 对象,它们可以按以下方式进行操作:

for page in db(db.page).select():
    print page.title, page.day, page.month, page.year


当日期在表单中转换为字符串时,它们将使用 ISO 表示进行转换

%Y-%m-%d %H:%M:%S


然而,这种表示形式在 Internationalized 中,你可以使用 Admin Stranslation 页面将格式更改为另一种格式。例如:

%m/%b/%Y %H:%M:%S


请注意,默认情况下英语不会被翻译,因为 web2py 假设应用程序已经是用英语编写的。如果您希望国际化适用于英语,则需要创建翻译文件(使用 admin),并且需要声明应用程序当前语言不是英语,例如:

T.current_languages = ['null']


管理界面提供了其他功能,我们在此处简要回顾一下。


此页面列出了所有已安装的应用程序。底部有两种形式。


第一个允许通过指定其名称来创建新应用程序。


第二种形式允许从本地文件或远程 URL 上传现有应用程序。上传应用程序时,您需要为其指定名称。这可以是其原始名称,但不需要是。这允许安装同一应用程序的多个副本。例如,您可以尝试从以下位置上传由 Martin Mulone 创建的 Instant Press CMS:

http://code.google.com/p/instant-press/


Web2py 文件是 .w2p 文件的包。这些是 tar gzip 文件。Web2py 使用 .w2p 扩展名而不是 .tgz 扩展名来防止浏览器在下载时解压缩。可以使用 tar zxvf [filename] 手动解压缩它们,尽管这不是必需的。

image


成功上传后,web2py 会显示上传文件的 MD5 校验和。您可以使用它来验证文件在上传过程中是否未损坏。InstantPress 名称将出现在已安装应用程序的列表中。


单击 admin 上的 InstantPress 名称以启动并运行它。

image


您可以在以下 URL 上阅读有关 Instant Press 的更多信息:

http://code.google.com/p/instant-press/


对于每个应用程序,站点页面允许您:


  • 卸载应用程序。

  • 跳转到 About 页面(阅读下文)。

  • 跳转到编辑页面(阅读下文)。

  • 跳转到错误页面(阅读下文)。

  • 清理临时文件(会话、错误和 cache.disk 文件)。

  • 打包所有。这将返回一个 tar 文件,其中包含应用程序的完整副本。我们建议您在打包应用程序之前清理临时文件。

  • 编译应用程序。如果没有错误,此选项将对所有模型、控制器和视图进行字节码编译。因为视图可以扩展和包含树中的其他视图,所以在字节码编译之前,每个控制器的视图树都折叠成一个文件。实际效果是字节码编译的应用程序速度更快,因为运行时不再发生模板解析或字符串替换。

  • Pack 已编译。此选项仅适用于字节码编译的应用程序。它允许打包没有源代码的应用程序,以便作为闭源分发。请注意,Python(与任何其他编程语言一样)在技术上可以反编译;因此,编译不提供对源代码的完全保护。然而,反编译可能很困难,并且可能是非法的。

  • 删除 compiled。它只是从应用程序中删除字节码编译的模型、视图和控制器。如果应用程序与源代码一起打包或在本地编辑,则删除字节码编译的文件并没有什么坏处,应用程序将继续工作。如果应用程序是从打包的编译文件中安装的,那么这并不安全,因为没有源代码可以还原,应用程序将不再工作。


web2py 管理站点页面提供的所有功能也可以通过模块 gluon/admin.py 中定义的 API 以编程方式访问。只需打开 python shell 并导入此模块即可。


的 about 选项卡允许编辑应用程序及其许可证的描述。这些分别写入应用程序文件夹的 ABOUT 和 LICENSE 文件中。

image


您可以对这些文件使用 MARKMINgluon.contrib.markdown.WIKI 语法,如 ref.[markdown2] 中所述。


您已经使用了本章中已有的编辑页面。在这里,我们想指出编辑页面的更多功能。


  • 如果单击任何文件名,则可以看到带有语法高亮显示的文件内容。

  • 如果单击“编辑”,则可以通过 Web 界面编辑文件。

  • 如果您单击删除,您可以(永久)删除该文件。

  • 如果你点击 test,web2py 将运行测试。测试由开发人员使用 Python doctests 编写,每个函数都应该有自己的测试。

  • 您可以通过 Web 界面添加语言文件、扫描应用程序以发现所有字符串以及编辑字符串翻译。

  • 如果静态文件按文件夹和子文件夹进行组织,则可以通过单击文件夹名称来切换文件夹层次结构。


下图显示了欢迎应用程序的测试页面的输出。

image


下图显示了欢迎应用程序的 languages 选项卡。

image


下图显示了如何编辑语言文件,在本例中为欢迎应用程序的“it”(意大利语)语言。

image


如果你在编辑中点击控制器选项卡下的 “shell” 链接,web2py 将打开一个基于 Web 的 Python shell 并执行当前应用程序的模型。这允许您以交互方式与您的应用程序通信。

image


此外,在 edit 的 controllers 选项卡下有一个 “crontab” 链接。通过单击此链接,您将能够编辑 web2py crontab 文件。这遵循与 unix crontab 相同的语法,但不依赖于 unix。事实上,它只需要 web2py,并且可以在 Windows 上运行。它允许您注册需要在计划时间在后台执行的操作。有关此内容的更多信息,请参阅下一章。


在编写 web2py 时,你不可避免地会犯错误并引入 bug。web2py 以两种方式提供帮助:1) 它允许您从编辑页面为每个可以在浏览器中运行的函数创建测试;2) 当出现错误时,会向访客发出一个票证并记录错误。


故意在 images 应用程序中引入错误,如下所示:

def index():
    images = db().select(db.image.ALL,orderby=db.image.title)
    1/0
    return dict(images=images)


当您访问 index 操作时,您会收到以下票证:

image


只有管理员才能访问该工单:

image


该票证显示回溯、导致问题的文件内容,以及系统的完整状态(变量、请求、会话等)如果视图发生错误,web2py 会显示从 HTML 转换为 Python 代码的视图。这允许轻松识别文件的逻辑结构。


默认情况下,票据存储在文件系统上,并按回溯进行分组。管理界面提供聚合视图(回溯类型和出现次数)和详细视图(所有工单都按工单 ID 列出)。管理员可以在两个视图之间切换。


请注意,admin 到处都显示语法高亮的代码(例如,在错误报告中,web2py 关键字显示为橙色)。如果你点击一个 web2py 关键词,你会被重定向到一个关于该关键词的文档页面。


如果你修复了 index 动作中的 divide-by-zero 错误,并在 index 视图中引入了一个 bug:

{{extend 'layout.html'}}

<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>


您将获得以下票证:

image


请注意,web2py 已将视图从 HTML 转换为 Python 文件,票证中描述的错误是指生成的 Python 代码,而不是原始视图文件:

image


乍一看,这可能令人困惑,但实际上它使调试更容易,因为 Python 缩进突出显示了您嵌入在视图中的代码的逻辑结构。


该代码显示在同一页面的底部。


所有票证都列在每个应用程序的错误页面的 admin 下:

image


如果从源代码运行,并且安装了 Mercurial 版本控制库:

easy_install mercurial


然后,管理界面将显示另一个名为 “Mercurial” 的菜单项。它会自动为应用程序创建本地 Mercurial 存储库。按下页面中的“提交”按钮将提交当前应用程序。Mercurial 将创建有关您在代码中所做的更改的信息,并将其存储到 app 子文件夹中的隐藏文件夹 “.hg” 中。每个应用程序都有自己的 “.hg” 文件夹和自己的 “.hgignore” 文件 (告诉 Mercurial 要忽略哪些文件)。


Mercurial Web 界面确实允许您浏览以前的提交和差异文件,但我们建议您直接从 shell 或基于 GUI 的 Mercurial 客户端之一使用 Mercurial,因为它们功能更强大。例如,它们将允许您将应用程序与远程源仓库同步:

images


您可以在此处阅读有关 Mercurial 的更多信息:

http://mercurial.selenic.com/


管理界面包括一个向导,可帮助您创建新应用程序。您可以从 “sites” 页面访问向导,如下图所示。

image


该向导将指导您完成创建新应用程序所涉及的一系列步骤:


  • 为应用程序选择名称

  • 配置应用程序并选择所需的插件

  • 构建所需的模型(它将为每个模型创建 CRUD 页面)

  • 允许您使用 MARKMIN 语法编辑这些页面的视图


下图显示了该过程的第二步。

image


您可以看到一个用于选择布局插件的下拉列表(从 web2py.com/layouts),一个用于检查其他插件的多项选择下拉列表(从 web2py.com/plugins)和一个“登录配置”字段,用于放置 Janrain “domain:key”。


其他步骤几乎是不言自明的。


Wizard 可以很好地完成它的工作,但它被认为是一个实验性功能,原因有两个:


  • 使用向导创建并手动编辑的应用程序以后无法由向导修改。

  • 向导的界面将随着时间的推移而变化,以包括对更多功能和更轻松的可视化开发的支持。


在任何情况下,该向导都是用于快速原型设计的便捷工具,它可用于引导具有备用布局和可选插件的新应用程序。


通常不需要执行 admin 的任何配置,但可以进行一些自定义。登录 admin 后,您可以通过 URL 编辑 admin 配置文件:

http://127.0.0.1:8000/admin/default/edit/admin/models/0.py


请注意,admin 可用于编辑自身。事实上,admin 和其他任何应用程序一样是一个应用程序。


文件 “0.py” 在很大程度上是自记录的,如果您正在打开,您可能已经知道您在寻找什么。无论如何,有一些自定义项比其他自定义项更重要:

GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))


这应该指向 Google App Engine SDK 附带的 “appcfg.py” 文件的位置。如果您有 SDK,则可能需要将此配置参数更改为正确的值。它将允许您从管理界面部署到 GAE。


你也可以在演示模式下设置 web2py admin:

DEMO_MODE = True
FILTER_APPS = ['welcome']


只有 filter apps 中列出的应用程序才能访问,并且它们只能在只读模式下访问。


如果您是一名教师,并且希望向学生公开管理界面,以便学生可以为其项目共享一个管理界面(考虑虚拟实验室),则可以通过设置来实现:

MULTI_USER_MODE = True


这样,学生将需要登录,并且只能通过 admin 访问他们自己的应用程序。作为第一个用户/教师,您将能够访问所有这些 ID。


请注意,此机制仍然假定所有用户都是受信任的。在 admin 下创建的所有应用程序都在同一文件系统上的相同凭证下运行。学生创建的应用程序可以访问其他学生创建的应用程序的数据和来源。


appadmin 不打算向公众公开。它旨在通过提供对数据库的轻松访问来帮助您。它仅包含两个文件:控制器 “appadmin.py” 和视图 “appadmin.html”,控制器中的所有操作都使用这两个文件。


appadmin 控制器相对较小且可读;它提供了一个设计数据库接口的示例。


appadmin 显示哪些数据库可用,以及每个数据库中存在哪些表。您可以插入记录并单独列出每个表的所有记录。appadmin 一次对输出 100 条记录进行分页。


选择一组记录后,页面的标题会发生变化,允许您更新或删除所选记录。


要更新记录,请在 Query string 字段中输入 SQL 分配:

title = 'test'


其中字符串值必须用单引号括起来。多个字段可以用逗号分隔。


要删除记录,请单击相应的复选框以确认您确定。


如果 SQL FILTER 包含涉及两个或多个表的 SQL 条件,appadmin 还可以执行联接。例如,尝试:

db.image.id == db.comment.image_id


web2py 将此传递给 DAL,它明白查询链接了两个表;因此,两个表都使用 INNER JOIN 进行选择。这是输出:

image


如果您单击 id 字段的编号,您将获得具有相应 id 的记录的编辑页面。


如果单击引用字段的编号,则会显示引用记录的编辑页面。


您无法更新或删除联接选择的行,因为它们涉及来自多个表的记录,这将是不明确的。


除了数据库管理功能之外,appadmin 还允许您查看有关应用程序缓存内容的详细信息(位于 /yourapp/appadmin/ccache)以及当前请求响应会话对象的内容(位于 /yourapp/appadmin/state)。


AppAdminresponse.menu 替换为自己的菜单,该菜单提供指向 Admin 中应用程序的编辑页面、DB(数据库管理)页面、状态页面和缓存页面的链接。如果您的应用程序的布局没有使用 response.menu 生成菜单,那么您将看不到 appadmin 菜单。在这种情况下,您可以修改 appadmin.html 文件并添加 {{=MENU(response.menu)}} 来显示菜单。

 共享