Django之表单


HTML表单是网站交互的经典方式。流程:从html把数据提交到服务端,服务端接收数据后判断数据,然后做出对应的响应。

HTTP请求

  HTTP协议以“请求—回复”的方式工作,客户发送请求时,可以在请求中附加数据,服务器通过解析请求,就可获得客户传来的数据,并根据URL来提供特定的服务。

form表单

  表单在html中由<form>标签实现,一个完整的表单包括4部分:提交地址、请求方式、元素控件、提交按钮。

  • action:提交地址,设置用户提交的数据由哪个url接收和处理。
  • method:请求方式,get或post请求。
  • input:元素控件,输入文本信息。
  • submit:提交按钮,触发提交动作。

1. Get请求

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

Django之表单Django之表单Django之表单

获取提交参数(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),
]

显示效果如下:

Django之表单Django之表单Django之表单

2. POST请求-注册

  注册页面:需输入用户名(必填)、密码(必填)、邮箱(非必填),点击注册按钮。注册成功后,跳转到登录页面(逻辑先不写)。

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_token

  CSRF:Cross Site Request Forgery,跨站点伪造请求。

例:某个恶意网站上有一个指向你的网站的链接,如果某个用户已经登录到你的网站了,则当这个用户点击这个恶意网站上那个链接时,就会向你的网站发来一个请求,你的网站会以为那个请求是用户发来的。

  • django第一次响应来自某个客户端的请求时,会在服务端随机生成一个token,把这个token放在cookie里,然后每次POST请求都会带上这个token,这样就能避免被CSRF攻击。
  • 在返回的HTTP响应的cookie里,django会为你添加一个csrftoken字段,其值为一个自动生成的token。
  • 在所有的POST表单里,必须包含一个csrfmiddlewaretoken字段(只需在模板里添加一个tag,django就会自动帮你生成)。
  • 在处理POST请求之前,django会验证这个请求的cookie里的csrftoken字段的值和提交的表单里的csrfmiddlewaretoken字段的值是否一样,如果一样则表明这是个合法请求,否则这个请求可能来自别人的csrf攻击,返回403 Forbidden。
  • 在所有的ajax POST请求里,添加一个X-CSRFTOKEN header,其值为cookie里的csrftoken的值。

3. POST请求-登录

  注册和登录都是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("用户名或密码错误!")

4. POST请求-修改密码

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})

 5. 视图

  一个视图函数,简称视图。它接受web请求并返回web响应。响应可以是HTML页面、一个404错误页面、重定向页面、XML文档、或一张图片……

无论视图本身包含什么逻辑,都要返回响应。代码写在哪都可以,一般放在views.py文件中。

视图层中有2个重要对象:请求对象(request)与响应对象(HttpResponse)

5.1 请求对象:HttpRequest对象(简称request对象)

  常用的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)
Django之表单

5.2 响应对象:HttpResponse对象

  响应对象主要有3种形式:HttpResponse()、render()、redirect()

HttpResponse():返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含html标签,也可以渲染。

def runoob(request):
    return HttpResponse("<a href='https://www.runoob.com/'>修仙入口</a>")
Django之表单

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的基础上进行了封装:

  • render:底层返回的也是 HttpResponse 对象
  • redirect:底层继承的是 HttpResponse 对象

6. Request对象 详解

  每个视图函数的第一个参数是一个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:

  • filename:上传文件名,用Python字符串表示
  • content-type:上传文件的Content type
  • content:上传文件的原始内容

只有请求方式为POST,且请求页面中<form>有enctype=”multipart/form-data”属性时FILES才有数据,否则,FILES是一个空字典。

META

包含所有可用HTTP头部信息的字典,例如:

  • CONTENT-LENGTH
  • CONTENT -TYPE
  • QUERY-STRING:未解析的原始查询字符串
  • REMOTE_ADDR:客户端IP地址
  • REMOTE _HOST:客户端主机名
  • SERVER_NAME:服务器主机名
  • SERVER_PORT:服务器端口

META中这些头加上前缀HTTP_为Key,冒号后面为Value,例如:

  • HTTP_ACCEPT_ENCODING
  • HTTP _ACCEPT_LANGUAGE
  • HTTP_HOST:客户端发送的HTTP主机头信息
  • HTTP_REFERER:referring页
  • HTTP_USER_AGENT:客户端的user-agent字符串
  • HTTP_X_BENDER:X-Bender头信息
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”

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279805.html

(0)
上一篇 2022年8月11日
下一篇 2022年8月11日

相关推荐

发表回复

登录后才能评论