HTML表单是网站交互的经典方式。流程:从html把数据提交到服务端,服务端接收数据后判断数据,然后做出对应的响应。
HTTP请求
HTTP协议以“请求—回复”的方式工作,客户发送请求时,可以在请求中附加数据,服务器通过解析请求,就可获得客户传来的数据,并根据URL来提供特定的服务。
form表单
表单在html中由<form>标签实现,一个完整的表单包括4部分:提交地址、请求方式、元素控件、提交按钮。
HelloWorld/helloApp/templates/demo.html <!DOCTYPE html> <html lang="en"><head> <meta charset="UTF-8"><title>测试你的QQ号</title> </head> <body> <p>请输入你的QQ号</p> <form action="/result" method="get"> qq:<input type="text" name="q"><br><br> <input type="submit" value="提交"> </form> </body></html> |
HelloWorld/helloApp/views.py from django.shortcuts import render from django.http import HttpResponse def demo_qq(request): return render(request,'demo.html') def result_qq(request): return HttpResponse('提交成功!') 也可写在任意文件,urls.py引入就行 |
HelloWorld/HelloWorld/urls.py
from django.conf.urls import url from helloApp import views as view urlpatterns = [ url(r'^demo$',view.demo_qq), url(r'^result/',view.result_qq) ] |
浏览器打开http://127.0.0.1:8000/demo
获取提交参数(request.GET):
html上的数据提交后,希望获取提交的数据,再对数据做处理,不同数据返回不同结果。
HelloWorld/helloApp/views.py
from django.shortcuts import render from django.http import HttpResponse def demo_qq(request): return render(request,'demo.html') def result_qq(request): if request.method == 'GET': r = request.GET['q'] # key就是输入框里的name属性对应值name='q' res = '' try: if int(r) %2: # %操作符返回除法的余数,0为false,1为true res = '大吉大利' else: res = '恭喜发财' except: res = '请输入正确的QQ号' return HttpResponse("测试结果:%s" %res) else: # render(request,'demo.html') return HttpResponse('请求方式不匹配!')
request.GET可以看成一个字典,用GET方法传递的值都会保存到其中,可以用request.GET[‘key_name’]来取值,但当key值不存在时会报错”MultiValueDictKeyError”。
为了规避key值不存在报错问题,可用另一种写法:request.GET.get(‘key_name’,None)
r = request.GET.get('q',None)
查询数据库返回:
通常查询涉及到查询数据库,与数据库会有数据交互,接着前面的Person库,通过输入用户名,查询对应的age:
HelloWorld/helloApp/templates/demo.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>查询</title> </head> <body> <form action="/result" method="get"> 输入用户名:<input type="text" name="name"><br><br> <input type="submit" value="提交"> </form> <p>查询结果:<br>{{result}}</p> </body> </html> |
HelloWorld/helloApp/views.py 也可写在任意文件,urls.py引入 from django.shortcuts import render from django.http import HttpResponse from helloApp.models import Person def demo(request): return render(request,'demo.html') def result(request): if request.method == "GET": if 'name' in request.GET and request.GET['name']: r = request.GET.get('name',None) res = Person.objects.filter(name="%s" %r) try: res = res[0].age except: res = "未查询到数据" else: res = "输入内容不能为空!" return render(request,'demo.html',{'result':res}) else: return render(request,'demo.html') |
HelloWorld/HelloWorld/urls.py from django.conf.urls import url from helloApp import views as view urlpatterns = [ url(r'^demo$',view.demo), url(r'^result/',view.result), ] |
显示效果如下:
注册页面:需输入用户名(必填)、密码(必填)、邮箱(非必填),点击注册按钮。注册成功后,跳转到登录页面(逻辑先不写)。
HelloWorld/helloApp/templates/register.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> </head> <body> <h1>新用户注册:</h1> <form action="" method="post"> {% csrf_token%} <p>用户名:<input type="text" id="uid" name="username" required="required">*{{rename}}</p> <p>密码:<input type="text" id="pwd" name="password" required="required">*</p> <p>注册邮箱:<input type="text" id="mail" name="email"></p> <p><input type="submit" value="注册"></p> </form> </body> </html> |
HelloWorld/helloApp/templates/register.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <h1>欢迎来到登录页面!</h1> <form action="/" method="post"> {% csrf_token %} <p>用户名:<input type="text" id="uid" name="username" required="required">*</p> <p>密码:<input type="text" id="pwd" name="password" required="required">*</p> <p><input type="submit" value="登录"></p> </form> </body> </html> |
POST请求form下面加个{% csrf_token %}
防止跨站点伪造请求。
视图与urls:
HelloWorld/helloApp/views.py 也可写在任意文件,urls.py引入就行
from django.shortcuts import render from helloApp.models import User def register(request): res = "" if request.method == "POST": inName = request.POST.get('username') inPwd = request.POST.get('password') inMail = request.POST.get('email') user_list = User.objects.filter(name=inName) if user_list: res = "%s用户已被注册!" % inName return render(request,'register.html',{'rename':res}) else: # 插入数据第一种写法 --推荐 user = User() user.name = inName user.pwd = make_password(inPwd) # 加密存储 user.mail = inMail user.save() # 插入数据第二种写法 user = User(name=inName,pwd=inPwd,mail=inMail) user.save() return render(request,'login.html',{'rename':res}) else: return render(request,'register.html') def login(request): return render(request,'login.html')
HelloWorld/HelloWorld/urls.py
from django.conf.urls import url from helloApp import views as view #引入文件名同名,可重命名 urlpatterns = [ url(r'^register/',view.register), url(r'^login/',view.login), ]
CSRF:Cross Site Request Forgery,跨站点伪造请求。
例:某个恶意网站上有一个指向你的网站的链接,如果某个用户已经登录到你的网站了,则当这个用户点击这个恶意网站上那个链接时,就会向你的网站发来一个请求,你的网站会以为那个请求是用户发来的。
注册和登录都是post请求接口,注册是插入数据,登录是查询数据。
HelloWorld\helloApp\templates\login.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <h1>欢迎来到登录页面!</h1> <form action="/login" method="post"> {% csrf_token %} <p>用户名:<input type="text" id="uid" name="username" required="required">*</p> <p>密码:<input type="text" id="pwd" name="password" required="required">*</p> <p><input type="submit" value="登录"></p> </form> </body> </html> |
HelloWorld\helloApp\views.py from django.shortcuts import render from django.http import HttpResponse from helloApp.models import User def login(request): if request.method == "GET": return render(request,'login.html') if request.method == "POST": inUsername = request.POST.get('username') inPassword = request.POST.get('password') user_obj = User.objects.filter(name=inUsername,pwd=inPassword).first() if user_obj: return HttpResponse('登录成功!') else: return HttpResponse("用户名或密码错误!") |
E:\PyCharm\HelloWorld\HelloWorld\urls.py
from django.conf.urls import url from helloApp import views as view #引入文件名同名,可重命名 urlpatterns = [ url(r'^register$',view.register), url(r'^login$',view.login), ]
密码加密和解密:
密码明文存储到数据库不安全,一般会加密,django提供一套加密和解密的方法。需先导入。加密用 make_password()
解密用 check_password()
from django.contrib.auth.hashers import make_password,check_password
注册时,views.py里写入数据库时,加个make_password()方法转下就行
user = User() user.name = inName user.pwd = make_password(inPwd) # 加密存储 user.mail = inMail user.save()
注意:密码加密的话,数据库里密码字段max_length就不能太小了,否则报错。
登录时,校验密码,先获取输入密码,再读取数据库用户对应密码,再通过check_password()函数校验密码,密码一致返回True,密码不一致返回False
def login(request): if request.method == "GET": return render(request,'login.html') if request.method == "POST": inUsername = request.POST.get('username') inPassword = request.POST.get('password') user_obj = User.objects.filter(name=inUsername).first() if user_obj: is_pwd_true = check_password(inPassword,user_obj.pwd) if is_pwd_true: return HttpResponse('登录成功!') else: return HttpResponse("用户名或密码错误!") else: return HttpResponse("用户名或密码错误!")
E:\PyCharm\HelloWorld\helloApp\templates\reset_pwd.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>密码修改</title> </head> <body> <h1>密码修改:</h1> <form action="" method="post"> {% csrf_token %} <p>用户名:<input type="text" id="id_username" name="username" required="required">*</p> <p>原密码:<input type="text" id="id_pwd" name="password" required="required">*</p> <p>新密码:<input type="text" id="id_newpwd" name="newPassword" required="required">*</p> <p>{{msg}}</p> <p><input type="submit" value="提交"></p> </form> </body> </html>
E:\PyCharm\HelloWorld\HelloWorld\urls.py from django.conf.urls import url from helloApp import views as view #引入文件名同名,可重命名 urlpatterns = [ url(r'^register$',view.register), url(r'^login$',view.login), url(r'^reset_pwd$',view.reset_pwd), ] |
E:\PyCharm\HelloWorld\helloApp\views.py
from django.shortcuts import render from helloApp.models import User from django.contrib.auth.hashers import make_password,check_password def reset_pwd(request): res = "" if request.method == 'GET': return render(request,'reset_pwd.html',{'msg':res}) if request.method == "POST": inName = request.POST.get('username') inPwd = request.POST.get('password') inNewPwd = request.POST.get('newPassword') if inPwd == inNewPwd: res = "新密码和旧密码不能重复!" return render(request,'reset_pwd.html',{'msg':res}) else: user_list = User.objects.filter(name=inName) if not user_list: res = '用户未注册:%s' % inName return render(request,'reset_pwd.html',{'msg':res}) else: user_obj = User.objects.filter(name=inName).first() is_pwd_true = check_password(inPwd,user_obj.pwd) if is_pwd_true: user = User() user.pwd = make_password(inNewPwd) user.save() res = "密码修改成功!" else: res = "原密码错误!" return render(request,'reset_pwd.html',{'msg':res}) |
一个视图函数,简称视图。它接受web请求并返回web响应。响应可以是HTML页面、一个404错误页面、重定向页面、XML文档、或一张图片……
无论视图本身包含什么逻辑,都要返回响应。代码写在哪都可以,一般放在views.py文件中。
视图层中有2个重要对象:请求对象(request)与响应对象(HttpResponse)
常用的request属性:GET、POST、body、path、method。
GET
数据类型是QueryDict,一个类似于字典的对象,包含HTTP GET的所有参数。有相同的键,就把所有的值放到对应的列表里。
取值格式:对象.方法 如:request.GET.get()
get():返回字符串,如果该键对应有多个值,取出该键的最后一个值。
def runoob(request): name = request.GET.get("name") return HttpResponse('姓名:{}'.format(name))
POST
数据类型是QueryDict,一个类似于字典的对象,包含HTTP POST是多有参数。常用于form表单,form表单里的标签name属性对应参数的键,value属性对应参数的值。
取值格式:对象.方法 如:request.POST.get()
method
获取当前请求的方式,数据类型是字符串,且结果为大写。request.method
def login(request): if request.method == "GET": return render(request,'login.html') if request.method == "POST": inUsername = request.POST.get('username') inPassword = request.POST.get('password') user_obj = User.objects.filter(name=inUsername).first() if user_obj: is_pwd_true = check_password(inPassword,user_obj.pwd) if is_pwd_true: return HttpResponse('登录成功!') else: return HttpResponse("用户名或密码错误!") else: return HttpResponse("用户名或密码错误!")
body
数据类型是二进制字节流,是原生请求体里的参数内容,在HTTP中用于POST,因为GET没有请求体。如登录页面改成这样打印body内容。request.body
def login(request): body = request.body return HttpResponse(body)
登录后页面展示为:csrfmiddlewaretoken=WWPyT5oWUTVDUUMyuxTuJVK7kXTwWxPJZcT0K2nXqN3vZDcEsR2AVOL0GRdgDtVM&username=aaa&password=aaa
path
获取URL中的路径部分,数据类型是字符串。request.path
def login(request): path = request.path return HttpResponse(path) |
响应对象主要有3种形式:HttpResponse()、render()、redirect()
HttpResponse():返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含html标签,也可以渲染。
def runoob(request): return HttpResponse("<a href='https://www.runoob.com/'>修仙入口</a>") |
render():返回文本,第1个参数是request,第2个参数是字符串(页面名称),第3个参数是字典(可选参数,向页面传递的参数,键为页面参数名,值为views参数名)
def runoob(request): name ="无敌大宝剑" # 可是字符串、字典、数组等 return render(request,"runoob.html",{"name":name})
redirect():重定向,跳转新页面。参数为字符串,字符串中填写页面页面路径。一般用于form表单提交后跳转至新页面。
def runoob(request): return redirect("/index/")
render和redirect在HttpResponse的基础上进行了封装:
每个视图函数的第一个参数是一个HttpRequest对象,
from django.http import HttpResponse def runoob(request): return HttpResponse("Hello world")
HttpRequest对象包含当前请求URL的一些信息:
属性 | 描述:request.属性 |
path | 请求页面的全路径,不包括域名。如”/hello/” |
method |
请求中使用HTTP方法的字符串表示,全大写表示。如: If request.method == ‘GET’: do_something() elif request.method == ‘POST’: do_something_else() |
GET | 包含所有HTTP GET参数的类字典对象 |
POST |
包含所有HTTP POST参数的类字典对象。服务器收到空的POST请求的情况也有可能,即表单form通过HTTP POST方法提交请求,但表单中可以没有数据。 因此,不能用if request.POST来判断是否使用HTTP POST方法,应使用if request.method == ‘POST’ 注:POST不包括file-upload信息。 |
REQUEST(建议不用) |
建议用GET和POST。为了方便,该属性是POST和GET属性的集合体,但有特殊性,先查找POST属性,再查找GET属性。例如:如果GET={“name”:”john”}和POST={”age”:34},则REQUEST[“name”]的值是”john”,REQUEST[“age”]的值是34。 |
COOKIES | 包含cookies的标准Python字典对象。Keys和values都是字符串。 |
FILES |
包含所有上传文件的类字典对象。 FILES中每个Key都是<input type=”file” name=”” /> 标签中name属性的值, FILES中每个value同时也是一个标准的Python字典对象,包含下面3个Keys:
只有请求方式为POST,且请求页面中<form>有enctype=”multipart/form-data”属性时FILES才有数据,否则,FILES是一个空字典。 |
META |
包含所有可用HTTP头部信息的字典,例如:
META中这些头加上前缀HTTP_为Key,冒号后面为Value,例如:
|
user | 是一个django.contrib.auth.modules.User对象,代表当前登录的用户。
若访问用户未登录,user将被初始化为django.contrib.auth.modules.AnonymousUser的实例。可通过user的is_authenticated()方法辨别用户是否登录: if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users. 只有激活Django中的AuthenticationMiddleware时属性才可用。 |
session | 唯一可读写的属性,代表当前会话的字典对象,只有激活Django中的session支持时该属性才可用。 |
raw_post_data | 原始HTTP POST数据,未解析过。高级处理时会有用处 |
Request对象也有一些有用的方法:
方法 | 描述 |
__getitem__(key) |
返回GET/POST的键值,先取POST后取GET。如果键值不存在抛出KeyError。这时我们可以使用字典语法访问HttpRequest对象。 如:request[“foo”]等同于先request.POST[“foo”]然后request.GET[“foo”] |
has_key() | 检查request.GET or request.POST中是否包含参数指定的Key |
get_full_path() | 返回包含查询字符串的请求路径。”/music/bands/the_beatles/?print=true” |
is_secure() | 如果请求是安全的,返回True。就是说发出的是https请求。 |
QueryDict对象
在HttpRequest对象中,GET和POST属性是django.http.QueryDict类的实例。
QueryDict类似字典的自定义类,用来处理单键对应多值的情况。QueryDict实现所有标准的字典方法,还包括一些特有的方法:
方法 | 描述 |
__getitem__ | 和标准字典不同的是:如果Key有多个Value,__getitem__()返回最后一个 |
__setitem__ | 设置参数指定key和value列表(一个Python list)。它只能在一个mutable QueryDict对象上被调用(就是通过copy()产生一个QueryDict对象的拷贝)。 |
get() | 如果key对应多个value,get()返回最后一个value。 |
update() |
参数可以是QueryDict,也可以是标准字典。和标准字典的update()方法不同,该方法添加字典items,而不是替换它们: >>> q = QueryDict('a=1') >>> q = q.copy() # to make it mutable >>> q.update({'a': '2'}) >>> q.getlist('a') ['1', '2'] >>> q['a'] # returns the last ['2'] |
items() |
和标准字典的items()方法有点不同,该方法使用单值逻辑的__getitem__() >>> q = QueryDict('a=1&a=2&a=3') >>> q.items() [('a', '3')] |
values() | 和标准字典的values()方法有点不同,该方法使用单值逻辑的__getitem__() |
此外,QueryDict也有一些方法,如下:
方法 | 描述 |
copy() | 返回对象的拷贝,内部实现是python标准库的copy.deepcopy()。该拷贝是mutable(可更改的)--即是说,可以更改该拷贝的值。 |
getlist(key) | 返回和参数key对应的所有值,作为一个python list返回。如果key不存在,则返回空list。 It's guaranteed to return a list of some sort.. |
setlist(key,list) | 设置key的值为list_(unlike __setitem__()) |
appendlist(key,item) | 添加item到和key关联的内部list |
setlistdefault(key,list) | 和setdefault有一点不同,它接受list而不是单个value作为参数。 |
lists() |
和items()有一点不同,它会返回key的所有值,作为一个list。如: >>> q = QueryDict('a=1&a=2&a=3') >>> q.lists() [('a', ['1', '2', '3'])] |
urlencode() | 返回一个以查询字符串格式进行格式化后的字符串。如:”a=2&b=3&b=5” |