Python - Django Web开发(2)

表单

HTTP 请求

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

GET 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
## search.py
from django.http import HttpResponse
from django.shortcuts import render
# 表单
def search_form(request):
return render(request, 'search_form.html')

# 接收请求数据
def search(request):
request.encoding='utf-8'
if 'query' in request.GET and request.GET['query']:
message = '你搜索的内容为: ' + request.GET['query']
else:
message = '你提交了空表单'
return HttpResponse(message)


## urls.py
from django.conf.urls import url
from . import search

urlpatterns = [
url(r'^search-form/$', search.search_form),
url(r'^search/$', search.search),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--templates/search_form.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Demo-Search</title>
</head>
<body>
<form action="/search/" method="get">
<input type="text" name="query" />
<input type="submit" value="搜索" />
</form>
</body>
</html>

POST 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## search_post.py
from django.views.decorators import csrf
from django.shortcuts import render

# 接收POST请求数据
def search_post(request):
ctx = {}
if request.POST:
ctx['rlt'] = request.POST['post_text']
return render(request, "post_result.html", ctx)


## urls.py
from django.conf.urls import url
from . import views,testdb,search,search2

urlpatterns = [
url(r'^search-post/$', search_post.search_post),
]

Request 对象

每个视图函数的第一个参数是一个 HttpRequest 对象

1
2
3
4
from django.http import HttpResponse

def demo(request):
return HttpResponse("Hello world")

属性

  • path: 请求页面的全路径,不包括域名,例如:”/index/“
  • method: 请求使用的 HTTP 方法,常用值包括:GET, POST
  • encoding: 表单数据使用的编码,只有当请求的内容类型是application/x-www-form-urlencoded时才使用
  • GET: 包含所有 HTTP GET 参数的类字典对象
  • POST: 包含所有 HTTP POST 参数的类字典对象
  • REQUEST: 是 GET 和 POST 的合体,但是有特殊性,先查找 POST 属性,然后再查找 GET 属性
  • FILES: 包含所有上传文件的类字典对象(注:只有在请求方法是 POST,并且请求页面中
    有 enctype=”multipart/form-data”属性时 FILES 才拥有数据。否则,FILES 是一个空字典。),FILES 中的每个 Key 都是标签中 name 属性的值. FILES 中的每个 value 同时也是一个标准 Python 字典对象,包含下面三个 Keys:
    • filename: 上传文件名,含路径
    • content-type: 上传文件内容类型
    • content: 上传文件的原始内容
  • COOKIES: 包含所有 cookie 的标准 Python 字典对象,Keys 和 Values 都是字符串
  • META: 包含所有 HTTP 头部信息的类字典对象
    • CONTENT_LENGTH
    • CONTENT_TYPE
    • QUERY_STRING: 未解析的原始查询字符串
    • REMOTE_ADDR: 客户端 IP 地址
    • REMOTE_HOST: 客户端主机名
    • SERVER_NAME: 服务器主机名
    • SERVER_PORT: 服务器端口
    • HTTP_USER_AGENT: 客户端代理信息
    • HTTP_REFERER: Referring URL
    • HTTP_ACCEPT_ENCODING
    • HTTP_ACCEPT_LANGUAGE
    • HTTP_HOST: 客户发送的 HTTP 主机头信息
    • HTTP_X_BENDER: X-Bender 头信息
  • user: 是一个 django.contrib.auth.models.User 对象,代表当前登录的用户,如果当前访问的用户没有登录,则 user 将被初始化为 django.contrib.auth.models.AnonymousUser 的实例,只有激活 Django 的 AuthenticationMiddleware 时才会生效。
    • user.is_authenticated(): 如果是已认证的用户(即用户已登录),返回 True;否则返回 False
    • user.is_staff(): 如果是超级用户,返回 True;否则返回 False
    • user.is_active(): 如果是已激活的用户,返回 True;否则返回 False
    • user.is_anonymous(): 如果是匿名用户,返回 True;否则返回 False
    • user.get_username(): 返回用户名
    • user_id: 当前登录用户的主键,int 类型
  • session: 当前会话的 SessionStore 对象,唯一可读写的属性,只有激活 Django 中的 session 支持时才可用。
  • raw_post_data: 原始 HTTP POST 数据,为解析过,是一个字符串。

方法

  • getitem(key): 返回 GET 或 POST 的键值,先取 POST 后取 GET,如果没有则抛出 KeyError 异常
  • has_key(): 检查 request.GET or request.POST 中是否包含参数指定的 Key
  • get_full_path(): 返回包含查询字符串的完整路径
  • is_secure(): 检查请求是否使用 HTTPS 协议,如果安全则返回 True
  • is_ajax(): 判断请求是否是通过 XMLHttpRequest 发起的
  • get_host(): 返回请求的主机部分
  • get_port(): 返回请求使用的端口
  • get_scheme(): 返回请求使用的协议,例如 http, https
  • build_absolute_uri(location): 返回 location 参数在当前请求下完整的绝对路径

QueryDict 对象

在 HttpRequest 对象中,GET 和 POST 属性是 django.http.QueryDict 类型的实例。
其类似字典的自定义类,用来处理单键对应多值的情况,实现所有的标准字典方法,还有一些特有方法。

方法

  • getitem: 和标准字典的不完全相同,如果 Key 对应多个 Value,则其返回最后一个 Value
  • setitem: 设置 Key 和 Value,如果 Key 已经存在,则对应的 Value 会被替换,只能在一个 mutable QueryDict 对象上被调用,就是通过 copy()产生的一个 QUeryDict 对象的拷贝
  • get(): 如果 Key 对应多个 Value,则返回最后一个
  • update(): 参数可以是 QueryDict,也可以是标准字典,和标准字典的 update 方法不同,其会保留 Key 对应的所有 Value,即添加 items 而不是替换。
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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(): 该方法使用单值逻辑的getitem方法,返回一个二元元组列表,每个二元元组包含(key, value)
1
2
3
4
5
>>> q = QueryDict('a=1&a=2&a=3')

>>> q.items()

[('a', '3')]
  • values():
  • copy(): 返回对象的拷贝,底层实现使用的标准库的 copy.deepcopy()方法,是 mutable,可以更改该拷贝的值
  • getlist(key): 返回和参数 Key 对应的所有值,作为一个 Python List 返回,如果 Key 不存在,则返回空列表
  • setlist(key, list**): 设置 Key 的值为 list**
  • setlistdefault(key, list): 和 setdefautl 不同,其接收 list 而不是单个 value 作为参数
  • appendlist(key, value): 添加 item 和 key 关联到内部的 list
  • lists(): 和 items()类似,但会返回 key 的所有值,作为一个 list
1
2
3
4
5
>>> q = QueryDict('a=1&a=2&a=3')

>>> q.lists()

[('a', ['1', '2', '3'])]
  • urlencode(): 返回一个字符串,表示对 QueryDict 中所有 Key 和 Value 的 URL 编码

视图

视图函数

视图函数是一个简单的 Python 函数,它接受 Web 请求并返回 Web 响应。

视图函数接收一个 HttpRequest 对象作为第一个参数,HttpRequest 对象包含了请求的元数据,比如请求方法、请求路径、请求头等。

视图函数返回一个 HttpResponse 对象,HttpResponse 对象可以包含以下内容:

  • 响应头
  • 响应体
  • 响应状态码

一般放在项目的 views.py 文件中,类似于 MVC 中的控制器。

请求对象:HttpRequest

  1. GET
    • 数据类型是 QueryDict
    • 包含 HTTP GET 请求的所有参数
    • 有相同的键,就将所有的值放到对应的列表里
    • 取值格式: 对象.方法
    • get(): 返回字符串,如果该键对应有多个值,取出该键的最后一个值
      1
      2
      3
      def demo_get(request):
      name = request.GET.get("name")
      return HttpResponse('姓名:{}'.format(name))
  2. POST
    • 数据类型是 QueryDict
    • 包含 HTTP POST 请求的所有参数
    • 常用于 form 表单,form 表单里的标签 name 属性对应参数的键,value 属性对应参数里的值
    • 取值格式: 对象.方法
    • get(): 返回字符串,如果该键对应有多个值,取出该键的最后一个值
      1
      2
      3
      def demo_post(request):
      name = request.POST.get("name")
      return HttpResponse('姓名:{}'.format(name))
  3. body
    • 数据类型是二进制字节流,是原生请求体里的参数内容
    • 在 HTTP 中用于 POST,因为 GET 没有请求体
    • 在 HTTP 中不常用,而在处理非 HTTP 形式的报文时非常有用,如:二进制图片、XML、JSON 等
      1
      2
      3
      4
      def demo_body(request):
      name = request.body
      print(name)
      return HttpResponse("测试:body")
  4. path
    • 获取 URL 中的路径部分,数据类型是字符串
      1
      2
      3
      4
      def demo_path(request):
      name = request.path
      print(name)
      return HttpResponse("测试:path")
  5. method
    • 获取当前请求的方式,数据类型是字符串,且结果为大写
      1
      2
      3
      4
      def demo_method(request):
      name = request.method
      print(name)
      return HttpResponse("测试:method")

响应对象:HttpResponse

  1. HttpResponse()
    • 返回文本,参数为字符串,若含有 html 标签,也可以渲染
      1
      2
      3
      def demo_res(request):
      # return HttpResponse("测试res")
      return HttpResponse("<a href='https://www.baidu.com/'>百度一下</a>")
  2. render()
    • 返回文本,第一个参数 request,第二个参数为字符串(页面名称),第三个参数为字典(可选参数,向页面传递的参数:键为页面的参数名,值为 views 的参数名)
      1
      2
      3
      def demo_render(request):
      name ="测试: render"
      return render(request,"index.html",{"name":name})
  3. redirect()
    • 重定向,跳转新页面,参数为字符串,字符串填写页面路径
    • 一般用于 form 表单提交后,跳转到新页面
      1
      2
      def demo_redirect(request):
      return redirect("/index/")
  • render 和 redirect 均是在 HttpResponse 的基础上封装的,底层返回的也是 HttpResponse 的对象

路由

  • 根据用户的请求链接来判断对应的处理函数,并返回处理结果,即将 URL 和视图建立映射关系
  • 在 Django 中,URL 的配置文件是 urls.py,每一条配置对应相应的处理方法
1
2
3
4
5
6
7
8
9
10
from django.urls import re_path
# 用re_path 需要引入

urlpatterns = [
# 普通路径
path('admin/', admin.site.urls),
path('index/', views.index),
# 正则路径
re_path(r'^articles/([0-9]{4})/$', views.articles),
]

正则路径的分组

无名分组

  • 按位置传参,一一对应
  • views 中除了 request,其它形参的数量要与 urls 中的分组数量一致
1
2
3
4
5
6
7
8
9
10
11
12
13
# urls.py
urlpatterns = [
path('admin/', admin.site.urls),
re_path("^index/([0-9]{4})/$", views.index),
]

#views.py
from django.shortcuts import HttpResponse

def index(request, year):
# 一个形参代表路径中一个分组的内容,按顺序匹配
print(year)
return HttpResponse('测试')

有名分组

  • 语法:(?P<组名>正则表达式)
  • 按关键字传参,与位置顺序无关
  • views 中除了 request,其它形参的数量要与 urls 中的分组数量一致,并且 views 中的形参名称要与 urls 中的组名一致
1
2
3
4
5
6
7
8
9
10
11
12
# urls.py
urlpatterns = [
path('admin/', admin.site.urls),
re_path("^index/(?P<year>[0-9]{4})/$", views.index),
]

# views.py
from django.shortcuts import HttpResponse
def index(request, year, month):
# 一个形参代表路径中一个分组的内容,按关键字对应匹配
print(year,month)
return HttpResponse('测试')

路由分发(include)

Django 项目里多个 app 共用一个urls.py容易造成混乱,也不方便维护。
可以将每个 app 的 urls.py 拆分出来,在主 urls.py 中使用 include 进行导入

  1. 在每个 app 里都创建一个 urls.py
  2. 在项目名称目录下的 urls.py 文件里,统一将路径分发给各个 app 目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 项目urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
path('admin/', admin.site.urls),
path("app01/", include("app01.urls")),
path("app02/", include("app02.urls")),
]

# app01/urls.py
from django.urls import path,re_path
from app01 import views # 从自己的 app 目录引入 views
urlpatterns = [
re_path(r'^login/(?P<m>[0-9]{2})/$', views.index, ),
]

# app02/urls.py
from django.urls import path,re_path
from app02 import views # 从自己的 app 目录引入views
urlpatterns = [
re_path("^xxx/(?P[0-9]{4})/$", views.xxx),
]

反向解析

  • 当路由层 url 发生改变,在视图层和模板层动态反向解析出更改后的 url,免去修改的操作
  • 一般用在模板中的超链接及视图中的重定向

普通路径

  • 在 urls.py 中给路由起别名,name=”路由别名”
  • 在 views.py 中,利用 reverse(“路由别名”)反向解析
  • 在模板中的 html 文件内,利用{% url "路由别名" %}反向解析
1
2
3
4
5
6
7
8
# urls.py
path("login1/", views.login, name="login")

# views.py
return redirect(reverse("login"))

# template.html
<form action="{% url 'login' %}" method="post">

正则路径(无名分组)

  • 在 urls.py 中给路由起别名,name=”路由别名”
  • 在 views.py 中,利用 reverse(“路由别名”, args=(1, 符合正则匹配的参数))反向解析
  • 在模板中的 html 文件内,利用{% url "路由别名" 符合正则匹配的参数 %}反向解析
1
2
3
4
5
6
7
8
# urls.py
re_path(r"^login/([0-9]{2})/$", views.login, name="login")

# views.py
return redirect(reverse("login",args=(10,)))

# template.html
<form action="{% url 'login' 10 %}" method="post">

正则路径(有名分组)

  • 在 urls.py 中给路由起别名,name=”路由别名”
  • 在 views.py 中,利用 reverse(“路由别名”, kwargs={“分组名”:符合正则匹配的参数})反向解析
  • 在模板中的 html 文件内,利用{% url "路由别名" 分组名=符合正则匹配的参数 %}反向解析
1
2
3
4
5
6
7
8
# urls.py
re_path(r"^login/(?P<year>[0-9]{4})/$", views.login, name="login")

# views.py
return redirect(reverse("login",kwargs={"year":2333}))

# template.html
<form action="{% url 'login' year=2333 %}" method="post">

命名空间

  • 是用于表示标识符的可见范围
  • 一个标识符可以在多个命名空间中定义,它在不同命名空间中的含义是互不相干的
  • 一个新的命名空间中可以定义任何标识符,不会与任何重复的标识符发生冲突,因为重复的定义都处理其它命名空间中
  • 路由别名 name 没有作用域,反向解析时会在项目全局顺序搜索,当搜索到第一条匹配时立即返回,如果不同 app 目录下存在相同的路由别名时,会导致 URL 反向解析错误。

普通路径

  • 定义命名空间,include 里面是一个元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include(("app名称:urls""app名称"))

# e.g:
# 项目urls.py
path("app01/", include(("app01.urls","app01")))
path("app02/", include(("app02.urls","app02")))

# app01/urls.py
path("login/", views.login, name="login")

# app01/views.py
#reverse("app名称:路由别名")
return redirect(reverse("app01:login")

# app01/template.html
# {% url "app名称:路由别名" %}
<form action="{% url 'app01:login' %}" method="post">