《笨方法学Python》习题51

习题 51: 从浏览器中获取输入

表单的工作原理

首先写一个可以接受表单数据的程序出来,先将你的bin/app.py修改成如下样子:

import web

urls = (
    '/hello','Index'
)
app = web.application(urls, globals())
render = web.template.render('templates/')
class Index(object):
    def GET(self):
        #使用了web.input从浏览器获取数据,
        #这个函数会将一组 key=value 的表述作为默认参数,
        #解析你提供的URL中的?name=Frank部分,然后返回一个对象,你可以通过这个对象方便地访问到表 单的值。
        form = web.input(name = "Nobody")
        #通过form对象的form.name属性为greeting赋值
        greeting = "Hello, %s" % form.name
        return render.index(greeting = greeting)
if __name__ == "__main__":
    app.run()

重启你的web程序(按 CTRL-C 后重新运行),确认它有运行起来,然后使 用浏览器访问 http://localhost:8080/hello,这时浏览器应该会显示“I just wanted to say Hello, Nobody.”,接下来,将浏览器的地址改成http://localhost:8080/hello?name=Frank,然后你可以看到页面显示为“I just wanted to say Hello, Frank. ”,最后将 nam= Frank修改为你自己的名字,你就可以看到它对你说“Hello”了。
URL 中该还可以包含多个参数。将本例的 URL 改成这样子:http://localhost:8080/hello?name=Frank&greet=Hola 。然后修改代码,让它去获取 form.name和form.greet ,如下所示:

greeting = "%s, %s" % (form.greet,form.name)

在web.input中为greet设一个默认值,另外你还可以设 greet=None,这样你可以通过程序检查 的 值是否存在,然后提供一个比较好的错误信息出来,例如:

form = web.input(name = "Nobody", greet = None)
        if form.greet:
            #通过form对象的form.name属性为greeting赋值
            greeting = "%s, %s" % (form.greet,form.name)
            return render.index(greeting = greeting)
        else:
            return "ERROR: greet is required."

修改完毕后,试着访问新的URL。


image.png

创建HTML表单

你可以通过 URL 参数实现表单提交,不过这样看上去有些丑陋,而且不方便一 般人使用,你真正需要的是一个“POST 表单”,这是一种包含了<form>标签的 特殊 HTML 文件。这种表单收集用户输入并将其传递给你的 web 程序,这和 你上面实现的目的基本是一样的。
让我们来快速创建一个,从中你可以看出它的工作原理。你需要创建一个新的 HTML 文件, 叫做 :
templates/hello_form.html:

<html>
<head>
    <title>Sample Web Form #25</title>
</head>
<body>
    <h1>Fill out this Form</h1>
    <form action="/hello" method="POST">
        A Greeting: <input type="text" name="greet">
        <br/>
        Your Name: <input type="text" name="name">
        <br/>
        <input type="submit">
    </form>
</body>
</html>>

然后将bin/app.py改成这样:

##-- coding: utf-8 --
import web

urls = (
    '/hello','Index'
)
app = web.application(urls, globals())
render = web.template.render('templates/')
class Index(object):
    def GET(self):
        return render.hello_form()
    def POST(self):
        form = web.input(name = "Nobody", greet = "Hello")
        greeting = "%s, %s" % (form.greet,form.name)
        return render.index(greeting = greeting)
        
if __name__ == "__main__":
    app.run()

都写好以后,重启 web 程序,然后通过你的浏览器访问它。
这回你会看到一个表单,它要求你输入“一个问候语句(A Greeting)”和“你的名字 (Your Name)”,等你输入完后点击“提交(Submit)”按钮,它就会输出一个正常的 问候页面,不过这一次你的 URL 还是http://localhost:8080/hello并没有添加参数进去。

image.png

image.png

在hello_form.html里面最关键的一行是,<form action="/hello" method="POST">,它告诉你的浏览器以下内容:

  1. 从表单中的各个栏位手机用户输入的数据。
  2. 让浏览器使用一种POST类型的请求,将这些数据发送给服务器。这是另外一种浏览器请求,它会将表单栏位“隐藏”起来。
  3. 将这个请求发送到/hello URL,这是由action="/hello"告诉浏览器的。
    你可以看到两段 标签的名字属性(name)和代码中的变量是对应的,另 外我们在 中使用的不再只是 GET 方法,而是另一个 POST 方法。
这个新程序的工作原理如下:
  1. 浏览器访问到web应用程序的/hello目录,它发送了一个GET请求,于是我们的index.GET函数就运行并返回了hello_form。
  2. 浏览器对hello_form进行渲染,你填好了浏览器的表单,然后浏览器依照<form>中的要求,将数据通过POST请求的方式发给web程序。
  3. web程序运行了index.POST方法来处理这个请求。
  4. 这个index.POST方法完成它的正常的功能,将hello页面返回,这里并没有新的东西,只是一个新函数名称而已。

创建布局模板(layout template)

“布局模板”,也就是一种提供了通用的头文件和脚注的外壳模板,你可以用它将 你所有的其他网页包裹起来。
将templates/index.html修改成这样:

$def with (greeting)

$if greeting:
    I just wanted to say <em style="color: green; font-size: 2em;">$greeting</em>.
$else:
    <em>Hello</em>, world!

然后把templates/hello_form.html修改成这样:

<h1>Fill out this Form</h1>
<form action="/hello" method="POST">
    A Greeting: <input type="text" name="greet">
    <br/>
    Your Name: <input type="text" name="name">
    <br/>
    <input type="submit">
</form>

上面这些修改的目的,是将每一个页面顶部和底部的反复用到的“boilerplate”代 码剥掉。这些被剥掉的代码会被放到一个单独的templates/layout.html文件 中,从此以后,这些反复用到的代码就由 layout.html 来提供了。
上面的都改好以后,创建一个templates/layout.html文件,内容如下:

$def with (content)
<!DOCTYPE html>
<html>
<head>
    <title>Gothons From Planet Percal #25</title>
</head>
<body>
$:content
</body>
</html>

这个文件和普通的模板文件类似,不过其它的模板的内容将被传递给它,然后它 会将其它 模板的内容“包裹”起来。
最后一步,就是将render对象改成这样:

render = web.template.render('templates/', base="layout")

这会告诉lpthw.web让它去使用templates/layout.html作为其它模板的基础模板。

为表单撰写自动测试代码

为了让 Python 加载 并进行测试,你需要先做一点准备工作。首先创建一个bin/__init__空文件,这样 Python 就会将bin/当作一个目录了。
我还为lpthw.web创建了一个简单的小函数,让你判断(assert)web 程序的响 应,这个函数有一个很合适的名字,就叫assert_resonpse。创建一个 tests/tools.py文件,内容如下:

from nose.tools import *
import re
def assert_response(resp, contains=None, matches=None, headers=None, status="200"):
    assert status in resp.status, "Expected response %r not in %r" % (status, resp.status)
    if status == "200":
        assert resp.data, "Response data is empty."

    if contains:
        assert contains in resp.data, "Response does not contain  %r" % contains

    if matches:
        reg = re.compile(matches)
        assert reg.matches(resp.data), "Response does not match %r" % matches

    if headers:
        assert_equal(resp.headers, headers)

准备好这个文件以后,你就可以为你的 bin/app.py写自动测试代码了。创建一 个新文件,叫做tests/app_test.py,内容如下

from nose.tools import * 
from bin.app import app
from tests.tools import assert_response

def test_index():
    #check that we get a 404 on the /url
    resp = app.request("/")
    assert_response(resp, status="404")

    # test our first GET request to /hello
    resp = app.request("/hello") assert_response(resp)

    # make sure default values work for the form
    resp = app.request("/hello", method="POST") assert_response(resp, contains="Nobody")

    # test that we get expected values
    data = {'name': 'Zed', 'greet': 'Hola'}
    resp = app.request("/hello", method="POST", data=data) 
    assert_response(resp, contains="Zed")

最后,使用 nosetests 运行测试脚本,然后测试你的 web 程序。
第一次执行自动化测试总是报以下错误,提示没有bin.app模块。

$ nosetests
E
======================================================================
ERROR: Failure: ImportError (No module named bin.app)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/Cellar/numpy/1.16.2/libexec/nose/lib/python2.7/site-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/local/Cellar/numpy/1.16.2/libexec/nose/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/local/Cellar/numpy/1.16.2/libexec/nose/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/Users/douyueyue/study/python_practise/projects/gothonweb/tests/app_tests.py", line 2, in <module>
    from bin.app import app
ImportError: No module named bin.app

----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (errors=1)

原因是:未按照书中的步骤在bin目录下创建一个bin/__init__空文件。修改后测试通过。

/gothonweb$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.115s

OK

加分习题

  1. 阅读和 HTML 相关的更多资料,然后为你的表单设计一个更好的输出格式。你可 以先在纸上设计出来,然后用 HTML 去实现它。
  2. 这是一道难题,试着研究一下如何进行文件上传,通过网页上传一张图像,然后将 其保存到磁盘中。
  3. 更难的难题,找到 HTTP RFC 文件(讲述 HTTP 工作原理的技术文件),然后努 力阅读一下。这是一篇很无趣的文档,不过偶尔你会用到里边的一些知识。
  4. 又是一道难题,找人帮你设置一个 web 服务器,例如 Apache、Nginx、或者 thttpd。 试着让服务器伺服一下你创建的 .html 和 .css 文件。如果失败了也没关系,web 服务器本来就都有点挫。
  5. 完成上面的任务后休息一下,然后试着多创建一些 web 程序出来。你应该仔细阅 读web.py(它和 lpthw.web 是同一个程序)中关于会话(session)的内容,这样你可 以 明白如何保持用户的状态信息。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容