一、Form标签简介
首先要学习的是表格标签<form>
<form action ="/foo"> <input type = "text" name = "test"> <input type ="submit" value ="ClickOnMe"> </form>
- <form> 表格标签
- <form action ="url"> 表格所要提交的目的(where a form submit to)
- <input type ="text"> input为输入控件,text定义了该控件类型是文本框
- <input type ="submit"> intput为输入控件,submit定义了该空间类型是提交按钮,点击该按钮相当于按回车键
当输入123,然后点击ClickOnMe后,显示如下结果。
掌握了这些基本知识,就能够做一个简单的搜索框,让结果指向百度或者Google
<form action="http://www.baidu.com/s"> <input name="wd"> <input type="submit"> </form>
二、第一个应用程序:Suck World
先下载并安装GoogleAppEngine(自行百度),新建一个新的应用
import webapp2 class MainHandler(webapp2.RequestHandler): def get(self): self.response.write(‘Suck world!‘) app = webapp2.WSGIApplication([ (‘/‘, MainHandler) ], debug=True)
- 当webapp2接受到一个对‘/‘(根路径)的请求时,会实例化MainHandler类,并调用get方法
- 关于请求的信息,包含在self.request中
- 如果遇到异常,debug=ture 会通知打印栈跟踪
接下来对程序进行修改,让它和知识点一最后给出的例子一样
import webapp2 # new string object from form=""" <form action="http://www.baidu.com/s"> <input name="wd"> <input type="submit"> </form> """ #print form class MainHandler(webapp2.RequestHandler): def get(self): #slef.resopne.headers[‘Content-Type‘] = ‘text/plain‘ self.response.write(form) app = webapp2.WSGIApplication([ (‘/‘, MainHandler) ], debug=True)
GoogleApp自动将打印的信息当成html处理,如果你想打印纯文本,需要多添加一条:slef.resopne.headers[‘Content-Type‘] = ‘text/plain‘
接下来,我们让form的信息提交到自己创建的路径中,而不是提交到百度中
import webapp2 # new string object from form=""" <form action="/testform"> <input name="wd"> <input type="submit"> </form> """ #print form class MainHandler(webapp2.RequestHandler): def get(self): #slef.resopne.headers[‘Content-Type‘] = "" self.response.write(form) class TestHandler(webapp2.RequestHandler): def get(self): wd = self.request.get("wd") self.response.write(wd) app = webapp2.WSGIApplication([ (‘/‘, MainHandler), (‘/testform‘,TestHandler) ], debug=True)
- form的信息提交给了“/formtest",由于app只识别‘/‘,所以新建了一个(‘/testform‘,TestHandler)
- TestHandler类中包含了以下信息:新建一个wd对象,用于储存请求信息中wd变量的信息,然后做出回应,打印wd变量
也可以让浏览器打印所有请求信息
#!/usr/bin/env python # # Copyright 2007 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import webapp2 # new string object from form=""" <form action="/testform"> <input name="wd"> <input type="submit"> </form> """ #print form class MainHandler(webapp2.RequestHandler): def get(self): #self.response.headers[‘Content-Type‘] = ‘text/plain‘ self.response.write(form) class TestHandler(webapp2.RequestHandler): def get(self): #wd = self.request.get("wd") #self.response.write(wd) self.response.headers[‘Content-Type‘] = ‘text/plain‘ self.response.write(self.request) app = webapp2.WSGIApplication([ (‘/‘, MainHandler), (‘/testform‘,TestHandler) ], debug=True)
接下来介绍form标签的post属性,也就是post方法
POST:向指定的资源提交要被处理的数据
# new string object from form=""" <form method="post" action="/testform"> <input name="wd"> <input type="submit"> </form> """ #print form class MainHandler(webapp2.RequestHandler): def get(self): #self.response.headers[‘Content-Type‘] = ‘text/plain‘ self.response.write(form) class TestHandler(webapp2.RequestHandler): def post(self): wd = self.request.get("wd") self.response.write(wd) #self.response.headers[‘Content-Type‘] = ‘text/plain‘ #self.response.write(self.request) app = webapp2.WSGIApplication([ (‘/‘, MainHandler), (‘/testform‘,TestHandler) ], debug=True)
注意代码的两处改动,一个是在<form>标签中声明 method = post,也就是说,如果不声明,那么默认为get方法。另一处改动是TestHandler类的get方法要改名为post方法
Get vs Post
- get方法常用于获取某个文件;post方法用于更新数据
- get方法的参数保存在url中;post方法的参数保存在主体中
- get方法的参数有长度限制;post方法的参数没有长度限制
- get方法支持缓存,不必每次都请求;post方法不支持缓存
- get方法无法改变服务端;post方法可以改变服务端
三、认识更多的Form属性
password属性:能让输入的文本以星号显示,但是结果能会在url中显示
<form> <input type="password" name="wd"> <input type="submit"> </form>
checkbox属性:多选按钮,如果点击按钮,参数会以name=on的形式显示在浏览器中,否则则不会显示参数
<form> <input type="checkbox" name="wd"> <input type="checkbox" name="r"> <input type="checkbox" name="s"> <br> <input type="submit"> </form>
radio属性:单选按钮控件,只需要为不同按钮赋予相同名字即可
<form> <input type="radio" name="wd"> <input type="radio" name="wd"> <input type="ra" name="wd"> <br> <input type="submit"> </form>
但是,单单使用radio属性的话,不管点击哪一个,结果的url中都不会有提示点击哪一个的信息,只会显示wd=on。这时候,就需要借助value属性
<form> <input type="radio" name="wd" value="one"> <input type="radio" name="wd" value="two"> <input type="radio" name="wd" value="three"> <br> <input type="submit"> </form>
选择第三个按钮,最终会在url上显示相关信息
label标签: input 元素定义标注(标记)
为上述按钮添加文本标注
<form> <label> One <input type="radio" name="wd" value="one"> </label> <label> Two <input type="radio" name="wd" value="two"> </label> <label> Three <input type="radio" name="wd" value="three"> </label> <br> <input type="submit"> </form>
selcet:创建单选或者多选元素
<form> <select name="q"> <option>one</option> <option>two</option> <option>three</option> </select> <br> <input type="submit"> </form>
url的信息会以q=one(two或者three)的形式来显示
如果要改变url显示的信息,可以借助value
<form> <select name="q"> <option value="1">one</option> <option value="2">two</option> <option value="3">three</option> </select> <br> <input type="submit"> </form>
结果会以q=1(2或者3)的形式显示在url中
四、验证
验证服务端接受的内容是否是什么所期待的(what we receive is what we expect)
新建一个应用,让用户输入年/月/日
form=""" <form method="post"> what is your birthday? <br> <label> Month <input type="text" name="month"> </label> <label> Day <input type="text" name="day"> </label> <label> Year <input type="text" name="year"> </label> <br> <input type="submit"> </form> """ class MainHandler(webapp2.RequestHandler): def get(self): self.response.write(form) def post(self): self.response.write("Thanks!") app = webapp2.WSGIApplication([ (‘/‘, MainHandler) ], debug=True)
运行效果如下:
我们知道,年月日的输入是有限定的,可以采取两种方式对用户输入的数字进行限定:
- 用下拉列表的方式呈现所有合法的选项,供用户选择
- 对用户输入的信息进行验证
先编写对月份的验证
# judge the input of month is what we expect #新建一个字典,来保存12个月的信息 months = [‘janurary‘, ‘February‘, ‘March‘, ‘April‘, ‘May‘, ‘june‘, ‘july‘, ‘August‘, ‘September‘, ‘October‘, ‘November‘, ‘December‘ ] def valid_month(month): if month: #将用户输入的月份的首字母大写化 cap_month = month.capitalize() #遍历字典,看首字母大写化之后的月份是否在里面 if cap_month in months: return cap_month else: print("not legal") return None
可以稍微修改,只校验前三个字母即可
# judge the input of month is what we expect months = [‘janurary‘, ‘February‘, ‘March‘, ‘April‘, ‘May‘, ‘june‘, ‘july‘, ‘August‘, ‘September‘, ‘October‘, ‘November‘, ‘December‘ ] month_abbvs = dict((m[:3].lower(), m) for m in months) def valid_month(month): if month: short_month = month[:3].lower() return month_abbvs.get(short_month)
对日期的验证较为简单,现在只是简单的验证,没有具体到某个月最大天数
def valid_day(day): if day and day.isdigit(): day = int(day) if day > 0 and day <= 31: return day
对年份的验证和日期验证类似
def valid_year(year): if year and year.isdigit(): year1 = int(year) if year > 1900 and year <2020: return year
接下来就可以将其整合到应用中了,先了解下整个验证的过程
- =》用户获取表格信息(get form)
- 《= 服务器返回表格页面(form date)
- =》用户提交表格信息(post date)
- 服务端对其进行验证:验证成功,返回感谢页面;验证失败,返回表格页面
也就是说,我们的验证机制应该包括三个方面的内容:
- 验证用户的输入是否合法
- 如果不合法的话,再次返回表格输入页面
- 提示错误信息
import webapp2 def valid_day(day): if day and day.isdigit(): day = int(day) if day > 0 and day <= 31: return day def valid_year(year): if year and year.isdigit(): year = int(year) if year > 1900 and year <2020: return year # judge the input of month is what we expect months = [‘janurary‘, ‘February‘, ‘March‘, ‘April‘, ‘May‘, ‘june‘, ‘july‘, ‘August‘, ‘September‘, ‘October‘, ‘November‘, ‘December‘ ] month_abbvs = dict((m[:3].lower(), m) for m in months) def valid_month(month): if month: short_month = month[:3].lower() return month_abbvs.get(short_month) form=""" <form method="post"> what is your birthday? <br> <label> Month <input type="text" name="month"> </label> <label> Day <input type="text" name="day"> </label> <label> Year <input type="text" name="year"> </label> <br> <input type="submit"> </form> """ class MainHandler(webapp2.RequestHandler): def get(self): self.response.write(form) def post(self): user_month = valid_month(self.request.get(‘month‘)) user_day = valid_day(self.request.get(‘day‘)) user_year = valid_year(self.request.get(‘year‘)) # not x means if x is any kind of zero or empty container, or False... if not (user_month and user_day and user_year): self.response.write(form) else: self.response.write("Thanks! That‘s a totally valid date !") app = webapp2.WSGIApplication([ (‘/‘, MainHandler) ], debug=True)
程序不过是按照刚才的分析来做,在post方法下添加了对用户数据输入的验证,然后判断是否合法,并根据不同判断返回不同信息。不过该程序还差一样提示错误信息。
先补充一个字符串替代的知识,共有三种方式可以在字符串中嵌入变量:
t1 = "I think %s is perfectly normal things to do in public" def sub1(s): return t1 %s t2 = "I think %s and %s are perfectly normal things to do in public" def sub2(s1,s2): return t2 %(s1,s2) t3 = "I‘m %(nickname)s. My real name is %(name)s, but my friend all call me %(nickname)s" def sub_3(name, nickname): return t3 %{‘name‘:name, ‘nickname‘:nickname}
除了提示错误信息之外,还增加了一个功能:当用户输入错误信息的时候,错误信息仍然显示在文本框里面,以避免用户需要全部重新输入一遍
import webapp2 def valid_day(day): if day and day.isdigit(): day = int(day) if day > 0 and day <= 31: return day def valid_year(year): if year and year.isdigit(): year = int(year) if year > 1900 and year <2020: return year # judge the input of month is what we expect months = [‘janurary‘, ‘February‘, ‘March‘, ‘April‘, ‘May‘, ‘june‘, ‘july‘, ‘August‘, ‘September‘, ‘October‘, ‘November‘, ‘December‘ ] month_abbvs = dict((m[:3].lower(), m) for m in months) def valid_month(month): if month: short_month = month[:3].lower() return month_abbvs.get(short_month) #嵌入了四个变量 month,day,year,error form=""" <form method="post"> what is your birthday? <br> <label> Month <input type="text" name="month" value="%(month)s"> </label> <label> Day <input type="text" name="day" value="%(day)s"> </label> <label> Year <input type="text" name="year" value="%(year)s"> </label> <div style="color:red">%(error)s</div> <br> <input type="submit"> </form> """ class MainHandler(webapp2.RequestHandler): #因为根据数据合法性所返回的结果是不同的,所以统一用一个函数编写 def write_form(self,error="",month="",day="",year=""): self.response.write(form %{"error": error, "month": month, "day": day, "year": year}) #step1:获取表格信息,四个变量不需要任何参数 def get(self): self.write_form() #setp2:用户提交表格信息 def post(self): #获取用户输入信息 user_month = self.request.get(‘month‘) user_day = self.request.get(‘day‘) user_year = self.request.get(‘year‘) #判断输入信息合法与否 month = valid_month(user_month) day = valid_day(user_day) year = valid_year(user_year) # not x means if x is any kind of zero or empty container, or False... if not (month and day and year): #如果数据输入没有通过验证,除了返回错误信息外,还返回原来用户的输入信息 self.write_form("That donesn‘t look valid to me ,friend.", user_month, user_day, user_year) else: self.response.write("Thanks! That‘s a totally valid date !") app = webapp2.WSGIApplication([ (‘/‘, MainHandler) ], debug=True)
上述的程序仍然有bug,就是文本框无法对输入字符进行处理,例如,在月份那栏输入foo">drep!,结果显示如下
因为用户输入的">被当做特殊字符处理,所以才导致出错。我们需要用到转移字符。例如,要在浏览器打印<html>应该如何打印?正确方式是用 < 与 > 的转义字符来替代
What if i want to talk about <html> in html? What if i want to talk about <html>in html?
&:&;
<: <
>: >
" : "
那么,如何解决上述问题呢?
编写函数,用于将用户在文本框输入的内容转化成转义字符,python本身也有内置模块(cgi)直接支持
#import cgi def escape_html(s): #return cgi.escape(s,quote = True) for(i,o) in (("&","&"), (">",">"), ("<","<"), (‘"‘,""")): s = s.replace(i,o) return s
然后重写write_form函数即可
def write_form(self,error="",month="",day="",year=""): self.response.write(form %{"error": error, "month": escape_html(month), "day": escape_html(day), "year": escape_html(year)})
五、重定向
当我们输入正确的日期时,会返回一个感谢界面
如果解决上述的两个问题?答案就是给对返回的页面进行重定向,包括以下三个步骤:
- 做一个“thank"的hander
- 添加 /thank的url
- 进行定向
# not x means if x is any kind of zero or empty container, or False... if not (month and day and year): self.write_form("That donesn‘t look valid to me ,friend.", user_month, user_day, user_year) else: #进行定向 self.redirect("/thanks") #添加Thank的Handler class ThanksHandler(webapp2.RequestHandler): def get(self): self.response.write("Thanks! That‘s a totally valid date !") #添加thank的url app = webapp2.WSGIApplication([ (‘/‘, MainHandler),(‘/thanks‘,ThanksHandler) ], debug=True)