1. 前言
一个教练下面有一群学员,教练想把学员的成绩放到web平台上,分享给所有人查看。
2. 设计
该web应用可能需要3个页面:一个“欢迎”页面,一个“选择选手”页面,以及一个“显示时间”页面
3. 开发
好的web应用应当遵循MVC模式:
1)模型
建立AthleteList类,将文本数据转换为AthleteList对象实例。新建athletelist.py:
# coding=utf-8
class AthleteList(list):
def __init__(self, a_name, a_dob=None, a_times=[]):
list.__init__([])
self.name = a_name
self.dob = a_dob
self.extend(a_times)
@staticmethod
def sanitize(the_time):
if '-' in the_time:
splitter = '-'
elif ':' in the_time:
splitter = ':'
else:
return the_time
(mins, secs) = the_time.split(splitter)
return mins + '.' + secs
# @property
def top3(self):
return sorted(set([self.sanitize(t) for t in self]))[0:3]
新建模块athletemodel.py:
#coding=utf-8
import cPickle as p
from athletelist import AthleteList
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline().strip().split(',')
return AthleteList(data.pop(0), data.pop(0), data)
except IOError as err:
print 'File error: ' + str(err)
return None
def put_to_store(the_files):
athletes = {}
for f in the_files:
ath = get_coach_data(f)
athletes[ath.name] = ath
try:
with open('athletes.pickle' , 'w') as athp:
p.dump(athletes, athp)
except IOError as err:
print 'File error(put_to_store): ' + str(err)
return athletes
def get_from_store():
athletes = {}
try:
with open('athletes.pickle') as athp:
athletes = p.load(athp)
except IOError as err:
print 'File erro(get_from_store): ' + str(err)
return athletes
2)视图
模板引擎yate.py
#coding=utf-8
"""从标准库的'string'模块导入'Template'类,它支持简单的字符串替换模板"""
from string import Template
# 该函数需要一个(可选)字符串作为参数,用来创建一个CGI行,默认值是:"text/html"
def start_response(resp="text/html"):
return('Content-type: ' + resp + '\n\n')
def include_header(the_title):
# 打开模板文件,读入文件内容,换入所提供的标题
with open('templates/header.html') as headf:
head_text = headf.read()
header = Template(head_text)
#subsitute()方法用来替换字符串中指定内容
return(header.substitute(title=the_title))
def include_footer(the_links):
with open('templates/footer.html') as footf:
foot_text = footf.read()
# 将链接字典转换为一个字符串,再换入模板
link_string = ''
for key in the_links:
link_string += '<a href="' + the_links[key] + '">' + key + '</a> '
footer = Template(foot_text)
return(footer.substitute(links=link_string))
#返回表单最前面的html,允许指定url和type
def start_form(the_url, form_type="POST"):
return('<form action="' + the_url + '" method="' + form_type + '">')
# 返回表单末尾的html,允许定制submit的文本内容
def end_form(submit_msg="Submit"):
return('<p></p><input type=submit value="' + submit_msg + '"></form>')
# 给定一个单选按钮名和值,创建一个html单选组
def radio_button(rb_name, rb_value):
return('<input type="radio" name="' + rb_name +
'" value="' + rb_value + '"> ' + rb_value + '<br />')
# 给定一个项列表,函数将该列表转换为一个html无序列表。每次迭代增加一个li元素
def u_list(items):
u_string = '<ul>'
for item in items:
u_string += '<li>' + item + '</li>'
u_string += '</ul>'
return(u_string)
# 创建并返回一个html标题标记,默认为2级标题
def header(header_text, header_level=2):
return('<h' + str(header_level) + '>' + header_text +
'</h' + str(header_level) + '>')
# 用html段落标记创建一个文本段
def para(para_text):
return('<p>' + para_text + '</p>')
3)用python构建一个web服务器
新建模块simple_httpd.py
#!/usr/local/bin/python
#coding=utf-8
# 导入http服务器和CGI模块
from BaseHTTPServer import HTTPServer
from CGIHTTPServer import CGIHTTPRequestHandler
#指定一个端口
port = 8080
#创建一个http服务器
httpd = HTTPServer(('', port), CGIHTTPRequestHandler)
#显示一个友好消息,并启动服务器
print("Starting simple_httpd on port: " + str(httpd.server_port))
httpd.serve_forever()
在终端执行该模块:
打开浏览器,在地址栏输入 http://localhost:8080,可直接进入webapp下的index.html页面。
同时,web服务器记录了web请求的运行日志:
4) 控制器
推荐web应用目录结构
webapp/ 项目顶层文件夹,除了包含子文件夹,还包含web应用的index.html、favicon.ico、样式表以及不放在其他子文件夹的所有内容
cgi-bin/ 为web应用写的所有代码需要放在一个名为cgin-bin的文件夹中
data/ 存放数据的文件夹
images/ 存放web应用的图像文件
templates/ 存放web应用的模板文件
在cgi-bin目录中,新建选择选手页面generate_list.py:
#!/usr/local/bin/python
#coding=utf-8
import athletemodel
import yate
import glob
data_files = glob.glob('data/*.txt')
athletes = athletemodel.put_to_store(data_files)
# genereate form
print yate.start_response()
print yate.include_header("Coach Kelly's list of Athletes")
print yate.start_form("generate_timing_data.py")
print yate.para("Select an athlete from the list to work with:")
for item in athletes:
print yate.radio_button('athlete', athletes[item].name)
print yate.end_form('Selete')
print yate.include_footer({"Home":"/index.html"})
再建立显示时间页面generate_timing_data.py
#!/usr/local/bin/python
#coding=utf-8
import cgi
import athletemodel
import yate
import cgitb
cgitb.enable()
# get all form data, and store a dictionary
form_data = cgi.FieldStorage()
# access a specify data from form data
athlete_name = form_data['athlete'].value
athletes = athletemodel.get_from_store()
print yate.start_response()
print yate.include_header("Coach Kelly's Timing Data")
print yate.header("Athlete: " + athlete_name + ", DOB: "+ athletes[athlete_name].dob + ".")
print yate.para("The top times for this athlete are: ")
print yate.u_list(athletes[athlete_name].top3()) #如果top3()方法被指定为@property,访问时直接使用.top3
print yate.include_footer({"Home":"/index.html",
"Select another athlete": "generate_list.py"})
4. 调试
python标准库提供了一个CGI跟踪模块cgitb,弃用这个模块时,会在web浏览器上显示详细的错误消息。
在CGI脚本前面增加下面两行代码,启用python的CGI跟踪技术:
import cgitb
cgitb.enable()
小结:
1)标准库string模块包括一个名为Template的类,它支持简单的字符串替换
2)标准库BaseHTTPServer、CGIHTTPServer(3.x版本合并为http.server)模块可以在python中建立一个简单的web服务器
3)标准库glob模块适合处理文件名列表
4)启用标准库cgitb时,允许在浏览器中查看CGI编码错误
5)使用cgi.FieldStorage()访问作为web请求发送给web服务器的数据,数据将作为一个python字典
6)@property修饰符,可以使类的方法表现的像是一个属性