# Django

Django后端框架

安装Django

# Django
2005年发布,采用Python语言编写的开源web框架。
早期的时候Django主做新闻和内容管理。
重量级的Python Web框架,Django配备了常用的大部分组件。

# 在线安装
yum install -y python39
pip3 install django==4.0.10
pip3 list | grep -i django

# 离线安装
https://www.djangoproject.com/download/
官网下载离线安装包Django-4.0.10.tar.gz	# 要求python38以上
yum remove -y python3
yum install -y python39
tar xf Django-4.0.10.tar.gz
cd Django-4.0.10
python3 setup.py install			# 需要联网下载依赖
pip3 list | grep -i django

创建项目、启动服务

# Django成功安装后,可以使用django-admin命令创建项目,每个项目相互独立
1. 安装Django
2. `django-admin startproject 项目名`,创建项目目录,内含同名子目录和脚本manage.py
3. `python3 项目名/manage.py runserver [端口]`,前台启动服务
4. 关闭服务的两种方式:
	方式一:在启动服务的终端使用 Ctrl+C 终止服务。
    方式二:在第二终端杀死服务的进程。
    	ss -nutlp | grep 端口 | grep -v grep | awk '{print $1}' | xargs kill -9 

[root@localhost ~]# mkdir projects && cd projects
[root@localhost projects]# django-admin startproject mysite1
[root@localhost projects]# cd mysite1/
[root@localhost mysite1]# ls
	manage.py  mysite1
[root@localhost mysite1]# python3 manage.py				# help说明
    Type 'manage.py help <subcommand>' for help on a specific subcommand.
    Available subcommands:
    [auth]
        changepassword
        createsuperuser
    [contenttypes]
        remove_stale_contenttypes
    [django]
        check
        compilemessages
    ......
[root@localhost mysite1]# python3 manage.py runserver	 # 默认端口8000
    Watching for file changes with StatReloader
    Performing system checks...

    System check identified no issues (0 silenced).

    You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
    Run 'python manage.py migrate' to apply them.
    August 04, 2024 - 14:03:41
    Django version 4.0.10, using settings 'mysite1.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CONTROL-C.			# 服务占用终端,退出使用Ctrl+C
此时,浏览器访问http://127.0.0.1:8000/,可看到django的启动页面
在启动服务的终端可以看见请求日志信息

[root@localhost mysite1]# python3 manage.py runserver 5000
    ……省略一万字
    February 21, 2023 - 15:27:45
    Django version 2.2.12, using settings 'mysite1.settings'
    Starting development server at http://127.0.0.1:5000/ 
    Quit the server with CONTROL-C.
此时,浏览器访问http://127.0.0.1:5000/,可看到django的启动页面
在启动服务的终端可以看见请求日志信息

项目结构解析

# 项目结构
[root@localhost projects]# tree mysite1/
	manage.py
	mysite1
        __init__.py		# Python包的初始化文件
        settings.py		# 项目的配置文件,包含项目启动时需要的配置
        urls.py			# 项目的主路由配置,HTTP请求进入Django时,优先调用该文件
        wsgi.py			# WEB服务网关的配置文件,Django正式启动时,需要用到
        asgi.py

# manage.py的子命令
python3 manage.py runserver [端口]  	   # 启动服务
python3 manage.py startapp    			# 创建应用
python3 manage.py migrate     			# 数据库迁移
setting.py配置文件
DEBUG、ALLOWED_HOSTS
# DEBUG用于配置Django项目的启动模式,两种取值方式:
DEBUG = True		# 表示开发环境中使用,开发调试模式(用于开发测试阶段)
	# 检测代码改动后,立刻启动服务,如果服务出错,会提示报错界面
DEBUG = False		# 表示当前项目运行在生产环境中,正式启动模式/上线模式
  	# 将DEBUG修改为False,然后修改 ALLOWED_HOSTS = ["127.0.0.1"]
  	# DEBUG = False
	# ALLOWED_HOSTS = ["127.0.0.1"]
  	# 启动服务,访问不存在页面地址,不对外暴漏报错具体信息

# ALLOWED_HOSTS设置允许访问到本项目的host头值
ALLOWED_HOSTS = [] 		# 空列表,表示只有请求头中host值为127.0.0.1才能访问本项目(配置名称解析则允许localhost),DEBUG=True时生效
ALLOWED_HOSTS = ["*"] 	# 表示任何请求头的host都能访问到当前项目
ALLOWED_HOSTS = ['192.168.1.3','127.0.0.1'] # 表示只有当前两个host头的值可以访问当前项目,可用于后期配置域名或者公网IP地址,过滤具有攻击性质的IP
# ALLOWDE_HOSTS

]# vim settings.py
    DEBUG = True  			# 正常显示错误信息
    ALLOWED_HOSTS = []  	# 首先不添加内网IP地址
# 启动服务
]# python3 manage.py runserver 0.0.0.0:8000

此时,浏览器访问 http://内网IP:8000/abc.jpg	# ??

# 上线模式
]# vim settings.py
	DEBUG = True 	 			# 正常显示错误信息
	ALLOWED_HOSTS = ["内网IP"]  	# 添加内网IP地址
# 启动服务
]# python3 manage.py runserver 0.0.0.0:8000

此时,浏览器访问 http://内网IP:8000/abc.jpg	# 正常显示错误信息,请求发送成功
其他公有配置
# setting.py配置文件
# 公有配置

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))	# 当前项目的绝对路径
# print(os.path.abspath(__file__))						# /xxx/mysite1/mysite1/settings.py
# print(os.path.dirname(os.path.abspath(__file__)))		# /xxx/mysite1/mysite1
# print(BASE_DIR)  									# /xxx/mysite1,当前项目的绝对路径

LANGUAGE_CODE = 'zh-Hans'		# 用于指定语言配置,英文:en_us,中文:zh-Hans
TIME_ZONE = 'Asia/Shanghai'		# 用于指定当前服务器端时区,默认是世界标准时区UTC,改为中国时区Asia/Shanghai

ROOT_URLCONF = 'mysite1.urls'	# 路由配置文件,用于配置主url

INSTALLED_APPS:指定当前项目中安装的应用列表
MIDDLEWARE:用于注册中间件
TEMPLATES:用于指定模板的配置信息
DATABASES:用于指定数据库的配置信息


#  自定义配置
例如:ALPAY_KEY = 'xxxx'

# settings.py中所有配置项,可以按需在代码中引入
引入方式:from django.conf import settings

Django处理URL请求的过程

URL结构

# URL(Uniform Resource Locator,统一资源定位符)
作用:用来表示互联网上某个资源的地址
一般语法格式:
	protocol://host[:port]/path[?query][#fragment]
	例如:https://cn.bing.com/dict/search?q=endpoint&FORM=BDVSP6&cc=cn#subject
	
# protocol(协议)
常用协议:
- https 通过安全的HTTPS访问该资源。格式 https://
- http 通过 HTTP 访问该资源。格式 http://
- file 资源是本地计算机上的文件。格式 file://

# host(主机)
- 是指存放资源的服务器的域名系统(DNS)主机名、域名或者IP地址

# port(端口)
- 各种传输协议都有默认的端口号,省略时使用方案的默认端口。如https的默认端口是443,http的默认端口是80

# path(资源路径)
例如:https://cn.bing.com/dict/search?q=endpoint&FORM=BDVSP6&cc=cn#subject 中的`dict/search`
- 由零或多个 / 符号隔开的字符串,一般用来表示主机上的一个目录或者文件地址
- 资源路径决定了服务器端如何处理这个请求

# query(查询参数)
例如:https://cn.bing.com/dict/search?q=endpoint&FORM=BDVSP6&cc=cn#subject 中的`q=endpoint&FORM=BDVSP6&cc=cn`
- 可选,键值对,用于给动态网页传递参数,如果有多个参数,用&符号隔开,每个参数的名和值用 = 符号隔开

# fragment(锚点)
例如:https://cn.bing.com/dict/search?q=endpoint&FORM=BDVSP6&cc=cn#subject 中的`subject`
- 可选,字符串,用于指定网络资源的片段,类似于书签
- 用于页面内部跳转

主路由文件、视图文件

# 主路由文件
1. 主路由文件由settings.py配置文件中的配置项ROOT_CONF决定,默认是项目同名目录下的urls.py
2. 主路由文件通过定义urlpatterns变量来配置路由,该变量是一个定义了多个path()函数的数组,用于匹配客户端访问的URL中的path
	import django.urls import path
	urlpatterns = [ 
		path("path01", 视图文件.视图函数01),
		path("path02", 视图文件.视图函数02),
	]
	
# 视图文件
1. 视图文件是自定义的,一般为views.py,与主路由文件同目录。
2. 视图文件中可以定义多个视图函数,被主路由文件引用。
3. 视图函数用于接收一个浏览器请求(HttpRequest对象)并通过HttpResponse对象返回响应
	from django.http import HttpResponse
	def 视图函数01(request[, 其它参数...]):		# 这里的request是一个HttpRequest对象
		return HttpResponse对象
	def 视图函数02(request[, 其它参数...]):
		return HttpResponse对象

Django处理URL请求的过程

浏览器地址栏 http://127.0.0.1:8000/page/1234
Django处理过程:
1. Django根据settings.py配置文件中的ROOT_CONF找到主路由文件
2. Django加载主路由文件中的urlpatterns变量
3. 解析客户端访问的URL中的path,依次去匹配urlpatterns变量中的path,匹配到第一个合适的path则停止(匹配即停止)
4. 如果匹配成功:调用视图文件中对应的视图函数处理请求,返回响应
5. 如果全部匹配失败:返回404响应

# 示例
]# cat 项目名/项目名/urls.py 			# 主路由文件
from django.contrib import admin
from django.urls import path
from . import views					# 相对路径导入视图文件 `项目名/项目名/views.py`
urlpatterns = [						# 匹配即停止
    path("admin/", admin.site.url),
    path("page/2023/", views.page_2023_view),
    path("page/2024/", views.page_2024_view),
]

]# cat 项目名/项目名/views.py			# 视图文件
from django.http import HttpResponse  	# 导入Django框架中响应类HttpResponse
def page_2023_view(request):
    html = "<h1>这是第一个页面</h1>"
    return HttpResponse(html)  			# 返回响应对象
def page_2024_view(request):
    html = "<h1>这是第二个页面</h1>"
    return HttpResponse(html)  			# 返回响应对象 

# 客户端
浏览器访问 http://127.0.0.1:8000/page/2023/
	匹配path page/2023/,由views.py中的page_2023_view()处理,返回"这是第一个页面"
浏览器访问 http://127.0.0.1:8000/page/2024/
	匹配path page/2024/,由views.py中的page_2024_view()处理,返回"这是第二个页面"
浏览器访问 http://127.0.0.1:8000/page/2025/
	没有匹配的path,返回404

path()函数、path转换器、re_path()函数

path()函数
# path()函数
import django.urls import path
path(route, views, name=None)
    # route:字符串类型,匹配的请求路径
    # views:指定路径所对应的视图函数的名称,函数名后不加括号
    # name:为地址起别名,在模板中地址反向解析时使用
path()函数示例
# path()函数示例
]# cat mysite1/mysite1/urls.py			# 添加路由
from django.urls import path
from . import views
urlpatterns = [
    path("", views.index_view),        	# 匹配http://127.0.0.1:8000
    path("page/1", views.page1_view),  	# 匹配http://127.0.0.1:8000/page/1
    path("page/2", views.page2_view),  	# 匹配http://127.0.0.1:8000/page/2
]

]# cat mysite1/mysite1/views.py			# 添加视图函数
from django.http import HttpResponse
def index_view(request):
    html = "<h1>这是我的首页</h1>"
    return HttpResponse(html)
def page1_view(request):
    html = "<h1>这是编号为1的网站</h1>"
    return HttpResponse(html)
def page2_view(request):
    html = "<h1>这是编号为2的网站</h1>"
    return HttpResponse(html)

浏览器访问http://127.0.0.1:8000/page/1,返回 这是编号为1的网站
浏览器访问http://127.0.0.1:8000/page/2,返回 这是编号为2的网站
path转换器 关键字传参
# path转换器
语法:<转换器类型:自定义名>
作用:若转换器类型匹配到对应类型的数据,则将数据按照"关键字传参"的方式传递给视图函数
示例:`path('page/<int:abc>', views.xxx)`,当匹配到page/正整数,则将`abc=正整数`传递给视图函数views.xxx
# 转换器类型:
    str		# 匹配除了'/'之外的非空字符串
    		# `v1/users/<str:username>` 可以匹配 v1/users/nfx 中 username=nfx
    int		# 匹配0和任何正整数??
    		# `page/<int:page>` 可以匹配 /page/100 中 page=100
    path	# 匹配非空字段,包括路径分割符'/'
			# `v1/users/<path:ph>` 可以匹配 /v1/users/a/b/c 中 ph=a/b/c
path转换器示例
# path转换器示例			
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("page/<int:pg>", views.pagen_view),	# 传递一个int类型的关键字参数pg给函数views.pagen_view
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def pagen_view(request, pg):	# pg:该参数为path()函数传来的int类型的关键字参数,参数名不能变,变了的话程序报错
    html = f"<h1>这是编号为{pg}的网站</h1>"
    return HttpResponse(html)

浏览器访问http://127.0.0.1:8000/page/3,返回 这是编号为3的网站
浏览器访问http://127.0.0.1:8000/page/4,返回 这是编号为4的网站
path转换器示例:两个数的加减乘
# 多参数示例:两个数的加减乘
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("<int:n>/<str:op>/<int:m>", views.cal_view),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def cal_view(request, n, op, m):
    if op == "add":
        result = n + m
    elif op == "sub":
        result = n - m
    elif op == "mul":
        result = n * m
    else:
        return HttpResponse("Your op is wrong!")
    return HttpResponse(f"<h1>计算结果为: {result}</h1>")

浏览器访问http://127.0.0.1:8000/3/abc/4,返回 Your op is wrong!
浏览器访问http://127.0.0.1:8000/3/add/4,返回 计算结果为: 7
浏览器访问http://127.0.0.1:8000/3/sub/4,返回 计算结果为: -1
浏览器访问http://127.0.0.1:8000/3/mul/4,返回 计算结果为: 12
path转换器示例:校验用户密码
# 多参数示例:校验用户密码
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns [
	path('login/<str:user>/<str:pass>', views.login_view)
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def login_view(request, user, pass):
	if (user == 'admin') and (pass == '123abc'):
    	result = '<h1 style="color:green">登陆成功</h1>'
    else:
        result = '<h1 style="color:red">登陆失败</h1>'
    return HttpResponse(result)
    
浏览器访问http://127.0.0.1:8000/admin/123abc,返回 登陆成功
浏览器访问http://127.0.0.1:8000/admin/123,返回 登陆失败
re_path()函数
# re_path函数
from django.urls import re_path
re_path(r"正则表达式", views, name=None)
    # r"正则表达式":正则表达式为命名分组模式`(?P<name>pattern)`,匹配参数后用关键字传参方式传递给视图函数
    # views:指定路径所对应的视图函数的名称,函数名后不加括号
    # name:为地址起别名,在模板中地址反向解析时使用
    # 注意:re_path()传递的参数都是字符串类型,进行算数运算前需要先类型转换
re_path()函数示例:100以内的正整数加减乘
# 示例:100以内的正整数加减乘
]# cat mysite1/mysite1/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
    re_path(r"^(?P<n>\d{1,2})/(?P<op>\w+)/(?P<m>\d{1,2})$", views.cal_view),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def cal_view(request, n, op, m):
    n, m = int(n), int(m)  				# 字符串类型,进行算数运算前需要先类型转换
    if op == "add":
        result = n + m
    elif op == "sub":
        result = n - m
    elif op == "mul":
        result = n * m
    else:
        return HttpResponse("Your op is wrong!")
    return HttpResponse(f"<h1>计算结果为: {result}</h1>")

浏览器访问http://127.0.0.1:8000/13/add/4,返回 计算结果为: 17
re_path()函数示例:获取生日
# 示例:获取生日并显示为 xxxx年xx月xx日
]# cat mysite1/mysite1/urls.py
from django.urls import re_path
from . import views
urlpatterns [
    re_path(r"^birthday/(?P<yead>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})$", views.birthday),
    re_path(r"^birthday/(?P<day>\d{1,2})/(?P<month>\d{1,2})/(?P<yead>\d{4})$", views.birthday),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def birthday(request, yead, month, day):
    return HttpResponse(f"<h1>生日为{year}{month}{day}日</h1>")

浏览器访问http://127.0.0.1:8000/birthday/2023/11/23,返回 生日为 20231123日
浏览器访问http://127.0.0.1:8000/birthday/11/23/2023,返回 生日为 20231123

HTTP协议的请求和响应

请求是指浏览器端通过HTTP协议发送给服务器端的数据。
响应是指服务器端接收到请求后做相应的处理后再回复给浏览器端的数据。
	服务器可以利用Django框架,处理接收的请求并恢复给浏览器端。

请求

# 请求样例
POST /post HTTP/1.1					# 起始行
HOST: httpbin.org					# 请求头
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Content-Type: application/json
Content-Length: 62
Connection: keep-alive

{"name": "zhangsan", "hobby": "lisi"}	# 请求体

# F12,'网络','消息头'中有起始行和请求头,'请求'中是请求体的内容
请求中的方法
根据HTTP标准,HTTP请求可以使用多种请求方法

HTTP1.0定义了三种请求方法:GET/POST/HEAD方法(最常用)
    GET		# 请求指定的页面信息,并返回实体主体。	缺省请求方法
    HEAD	# 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
    POST	# 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

HTTP1.1新增了五种请求方法:OPTIONS/PUT/DELETE/TRACE/CONNECT方法
    PUT		# 从客户端向服务器传送的数据取代指定的文档的内容。
    DELETE	# 请求服务器删除指定的页面。
    CONNECT	# HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    OPTIONS	# 允许客户端查看服务器的性能。
    TRACE	# 回显服务器收到的请求,主要用于测试或诊断。
Django中的请求
请求在Django中实则就是视图函数的第一个参数,即HttpRequest对象
Django接收到http协议的请求后,会根据请求数据报文创建HttpRequest对象
HttpRequest对象通过属性描述了请求的所有相关信息
HttpRequest对象属性
视图函数用于接收一个浏览器请求(HttpRequest对象)并通过HttpResponse对象返回响应
	from django.http import HttpResponse
	def 视图函数01(request[, 其它参数...]):		# 这里的request是一个HttpRequest对象
		return HttpResponse对象
	def 视图函数02(request[, 其它参数...]):
		return HttpResponse对象
		
request.path_info			# 返回URL字符串
request.get_full_path()		# 返回请求的完整路径
request.method				# 返回字符串,表示HTTP请求方法,常用值:GET/POST
request.GET					# 返回QueryDict查询字典的对象,包含GET请求方式的所有查询参数
request.GET['键']			# 返回对应的值(获取单个数据)。注意,键必须存在否则报错
request.GET.getlist['键']	# 返回对应的值的列表(获取一组数据)。注意,键不存在时返回空列表
request.POST				# 返回QueryDict查询字典的对象,包含POST请求方式的所有数据
request.request.FILES		# 返回类似于字典的对象,包含所有的上传文件信息
request.COOKIES				# 返回Python字典,包含所有的cookie,键和值都是字符串
request.session				# 返回类似于字典的对象,表示当前的会话
request.body				# 返回字符串,请求体的内容(POST/PUT)
request.scheme				# 返回请求协议(http/https)
request.META				# 返回请求中的元数据(消息头)
request.META['REMOTE_ADDR']	  # 返回客户端IP地址
get请求
]# cat mysite1/mysite1/urls.py 
from django.urls import path
from . import views
urlpatterns = [
    path("test_request", views.test_request),
]

]# cat mysite1/mysite1/views.py
def test_request(request):
    print("path info is:", request.path_info)		# /test_request
    print("method is:", request.method)				# GET
    print("full path:", request.get_full_path())	# /test_request?a=1&b=2&a=8
    print("查询参数:", request.GET)					# <QueryDict: {'a': ['1', '8'], 'b': ['2']}>
    print("只能获取一个:", request.GET['a'])			# 8
    print("获取一组:", request.GET.getlist['a'])	# ['1', '8']
    # 注意:request.GET['a']的参数a必须存在,否则报错。request.GET.getlist['a']的参数a不存在时,返回空列表
    # 往GET中添加参数(用处不大)
    print(request.GET.get('c', 'hello'))		# hello
    
    return HttpResponse("test request ok~")
    
浏览器访问:http://127.0.0.1:8000/test_request?a=1&b=2&a=8
F12,'网络',查看'消息头'
post请求
]# cat mysite1/mysite1/urls.py 
from django.urls import path
from . import views
urlpatterns = [
    path("test_get_post", views.test_get_post)
]

]# cat mysite1/mysite1/views.py
# 准备一个表单
form_html = '''
	<form method='post' action='/test_get_post'>
		用户:<input type='text' name='username'><br>
		密码:<input type='text' name='password'><br>
		<input type='submit' value='登陆'>
	</form>
'''
# 注意1:表单要成功提交,需要注释掉setting.py中关于csrf中间件的内容
# 注意2:表单提交后,可以在F12'网络','消息头'中看见请求方法是post,'请求'中看见请求体的内容
def test_get_post(request):
	# 判断用户发送的时GET还是POST
	# 如果是GET,响应一组html
	# 如果是POST,表示发送数据过来,需要获取
	if request.method == 'GET':
		return HttpResponse(form_html)
	elif request.method == 'POST':
		# 获取post方法的内容
		print(request.POST)		# <QueryDict: {'username': ['zhangsan'], 'password': ['123456']}>
		print('用户名:', request.POST('username'))			# zhangsan
		print('用户名:', request.POST.getlist('username'))	# ['zhangsan']
		print('用户名:', request.POST.get('username'))		# zhangsan
		return HttpResponse('登陆成功')
		pass
	else:
		pass
		return HttpResponse('test_get_post ok')
		
浏览器访问:http://127.0.0.1:8000/test_request
用户输入zhangsan,密码输入123456
F12,'网络',查看'消息头''请求'

响应

状态码
常见的HTTP状态码(Http Status Code)
    200:请求成功
    301:永久重定向,资源/网页等被永久转移到其他URL地址
    302:临时重定向
    404:请求的资源/网页等不存在
    500:服务器内部错误

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为 5种类型:
    1** 	# 信息,服务器收到请求,需要请求者继续执行操作
    2** 	# 成功,操作被成功接收并处理         
    3** 	# 重定向,需要进一步的操作以完成请求
    4** 	# 客户端错误,请求包含语法错误或无法完成请求
    5** 	# 服务器错误,服务器在处理请求的过程中发生了错误
HttpResponse子类
构造函数格式:`HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)`
作用:向客户端浏览器返回响应,同时携带响应体内容
参数:
	content:表示返回的内容
	content_type:指定返回数据的MIME类型(缺省text/html)
	status_code:返回的HTTP响应状态码(缺省200)

# 常见的Content_Type如下
text/html			# 默认的,html文件
text/plain			# 纯文本
text/css			# css文件
text/javascript		# javascript文件
application/json	# json数据传输
multipart/form-data	# 文件提交

# HttpResponse子类及其状态码
HttpResponseRedirect()		# 重定向 302
HttpResponseNotModified()	# 未修改 304
HttpResponseBadRequest()	# 错误请求 400
HttpResponseForbidden()		# 请求被禁止 403
HttpResponseNotFound()		# 没有对应的资源 404
HttpResponseServerError()	# 服务器错误 500
# 测试HttpResponse子类
]# cat mysite1/mysite1/urls.py 
from django.urls import path
from . import views
urlpatterns = [
	path('page/1/', views.page_1_view),
    path("test_request", views.test_request),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse, HttpResponseRedirect  # 导入重定向
def page_1_view(request):
    return HttpResponse('this is page_1_view')
def test_request(request):
    print("path info is:", request.path_info)
    print("method is:", request.method)
    print("query string:", request.GET)
    print("full path:", request.get_full_path())
    # return HttpResponse("test request ok~")  # 注释掉普通响应
    return HttpResponseRedirect("/page/1")     # 改为重定向,会自动跳转到/page/1。注意page前面的/

浏览器访问http://127.0.0.1:8000/test_request,跳转到http://127.0.0.1:8000/page/1,返回this is page_1_view

Django处理请求 request.method

# 无论是GET还是POST请求,统一都由视图函数接收请求,通过判断request.method属性来区分具体的请求动作

# 视图函数中的判断示例
if request.method == "GET":
    处理GET请求时的业务逻辑
elif request.method == "POST":
    处理POST请求时的业务逻辑
else:
    其他请求业务逻辑
处理GET请求
GET请求动作,一般用于向服务器获取数据,能够产生GET请求的场景:
    1. 浏览器地址栏中输入URL,回车后
    2. html跳转链接`<a href="地址?参数=值&参数=值"/>`
    3. html表单form中的method为 get

    
GET请求方式中,如果有数据需要传递给服务器,通常会用查询字符串(QueryString)传递
	格式:`URL地址?参数1=1&参数2=2`,如:http://127.0.0.1:8000/page1?a=1&b=2


服务器端使用GET来获取客户端GET请求的数据,常用方法:
	request.GET					# QueryDict
	request.GET["参数名"]  			# 单个值
	request.GET.getlist("参数名")		# 一组值
	request.GET.get("参数名", "参数值")	# 给get()提供参数

# 示例
page?a=100&b=200&c=300&b=400
request.GET					# QueryDict({"a": ["100"], "b": ["200", "400"], "c": ["300"],})
request.GET["a"]			# 100
request.GET.getlist("a")	# ["100"]
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("test_get_post", views.test_get_post),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
def test_get_post(request):
    if request.method == "GET":  	# 处理GET请求
        print("1", request.GET) 
        print("2", request.GET["a"])
        print("3", request.GET.getlist('a'))
        print("4", request.GET.get("c", "no c"))
    elif request.method == "POST":	# 处理POST请求
        pass
    else:
        pass
    return HttpResponse("--test get post is ok --")


浏览器访问http://127.0.0.1:8000/test_get_post?a=400
浏览器访问http://127.0.0.1:8000/test_get_post
浏览器访问http://127.0.0.1:8000/test_get_post?a=100&a=200&a=300
处理POST请求
POST请求动作,一般用于向服务器提交大量/隐私数据,客户端通过表单等POST请求将数据传递给服务器端,如:
    <form method='post' action='/test_get_post'>
        用户:<input type='text' name='username'><br>
        密码:<input type='text' name='password'><br>
        <input type='submit' value='登陆'>
    </form>

服务器端使用POST来接收客户端POST请求的数据,常用方法
    request.POST["参数名"]
    request.POST.getlist("参数名")
    request.POST.get("参数名", "参数值")

注意:Django配置中取消csrf验证,否则Django将会拒绝客户端发来的POST请求,报403响应
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("test_get_post", views.test_get_post),
]

]# cat mysite1/mysite1/views.py
from django.http import HttpResponse
POST_FORM = """
<form method="post" action="/test_get_post">
    用户名: <input type="text" name="uname">
    <input type="submit" value="提交">
</form>
"""
def test_get_post(request):
    if request.method == "GET":
        print(request.GET)
        print(request.GET.getlist("a"))
        print(request.GET.get("c", "no c"))
        return HttpResponse(POST_FORM)
    elif request.method == "POST":
        print("uname is:", request.POST.get("uname"))
        return HttpResponse("post is ok~")
    else:
        pass
    return HttpResponse("--test get post is ok --")

浏览器访问http://127.0.0.1:8000/test_get_post,返回表单数据
填写表单数据,点击提交,发送POST请求,查看浏览器显示内容,POST请求失败,403响应
取消csrf验证:注释掉 settings.py 中的 MIDDLEWARE 中的 CsrfViewsMiddleWare 的中间件,重新提交表单数据,数据提交成功
]# vim mysite1/mysite1/settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django模板层

设计模式 MVC和MTV

# 传统的MVC
MVC设计模式把Web框架分为三个基础部分:
    - M 模型层(Model):主要用于对数据库层的封装
    - V 视图层(View):用于向用户展示结果(what+how)
    - C 控制层(Controller):用于处理请求获取数据,返回结果
作用:降低模块间的耦合度(解耦)

# MTV模式
MTV和MVC本质上是一样的,MTV代表 Model-Template-View (模型-模板-视图)模式
    - M 模型层(Model):负责与数据库交互
    - T 模板层(Template):负责呈现内容到浏览器(how)
    - V 视图层(View):是核心,负责接收请求,获取数据,返回结果(what)

# 浏览器发送请求,服务端响应的过程
浏览器发送请求给服务端
# MVC
服务端中的控制器C收到请求,根据请求去模型层M获取数据,模型层M去数据库获取数据并封装后返回给控制器C,控制器C去视图层V获取模板,将从模型层M获取的数据填充到从视图层V获取的模板中,最后将结果响应返回给浏览器
# MTV
服务端中的视图层V收到请求,从模板层T获取模板,根据请求去模型层M获取数据,模型层M去数据库获取数据并封装后返回给视图层V,视图层V将从模型层M获取的数据填充到从模板层T获取的模板中,最后将结果响应返回给浏览器

在这里插入图片描述

模板层

# 定义
模板是可以根据字典数据"动态变化的HTML网页",模板可以根据视图中"传递的字典数据动态生成相应的HTML页面"

# 配置
修改 settings.py 中 TEMPLATES 配置项
	BACKEND		# 指定模板的引擎        
	DIRS		# 模板的搜索目录
	APP_DIRS	# 是否要在DIRS目录中搜索模板文件
	OPTIONS		# 有关模板的选项

]# mkdir mysite1/templates
]# vim mysite1/mysite1/settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))	# 当前项目的绝对路径
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")],  		# 修改DIRS配置
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

模板渲染方式

# 方案1:通过 loader 获取模板,再在视图函数中通过 HttpResponse类 进行响应
]# cat views.py
from django.http import HttpResponse
from django.template import loader
def test_html(request):
    # 指定渲染的模板test_html.html,会自动到settings.py的Templates中的DIRS中去找
    t = loader.get_template("test_html.html")
    html = t.render()
    return HttpResponse(html)


# 方案2:在视图函数中直接使用 render类 加载模板并响应
]# cat views.py
from django.shortcuts import render
def test_html(request):
    # 指定渲染的模板test_html.html,会自动到settings.py的Templates中的DIRS中去找
    return render(request, "test_html.html")
示例:模板渲染
]# mkdir mysite1/templates
]# vim mysite1/mysite1/settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))	# 当前项目的绝对路径
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")],  		# 修改DIRS配置
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

]# cat mysite1/templates/test_html.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>我是模板层!!!</h3>
</body>
</html>

]# cat mysite1/mysite1/urls.py 
from django.urls import path
from . import views
urlpatterns = [
    path("test_html", views.test_html),
]

]# cat mysite1/mysite1/views.py			# 渲染模板方式一
from django.http import HttpResponse
def test_html(request):
    from django.template import loader
    t = loader.get_template("test_html.html")
    html = t.render()
    return HttpResponse(html)

]# cat mysite1/mysite1/views.py			# 渲染模板方式二
def test_html(request):
    from django.shortcuts import render
    return render(request, "test_html.html")

浏览器访问http://127.0.0.1:8000/test_html

模板层—变量 render(request, "模板", 字典类型数据)

视图函数中可以将Python变量封装到"字典"中传递到模板,样例:
写法一:直接创建字典
	def xxx_view(request):
		dic = {
			"变量1": "值1",
			"变量2": "值2",
		}
		return render(request, 'xxx.html', dic)
写法二:使用locals()传递同名键值的字典{'变量1': 变量1, '变量2': 变量2}
	def xxx_view(request):
		变量1 = 值1
		变量2 = 值2
		return render(request, 'xxx.html', locals())
	示例看 模板层-标签,if标签,示例二;算数运算

模板中可以使用 {{ 变量名 }} 的语法调用视图函数传进来的变量
当views.py封装到"字典"中的变量的值的"类型"不同时,模板html中调用变量时的写法不同:
	数值或字符串类型:`{{ 变量名 }}`
	列表类型:`{{ 变量名.索引 }}` 
	字典类型:`{{ 变量名.key名 }}`
	方法类型:`{{ 变量名 }}`
	类类型:`{{ 变量名.方法名 }}`
示例一:模板调用视图函数的变量
]# cat mysite1/mysite1/urls.py 
from django.urls import path
from . import views
urlpatterns = [
    path("test_html", views.test_html),
]

]# cat mysite1/mysite1/views.py
def test_html(request):
    from django.shortcuts import render
    dic = {"username": "nfx", "age": 18}			# 渲染的数据使用字典格式
    return render(request, "test_html.html", dic)	# 将数据使用字典格式传入模板中

]# cat mysite1/templates/test_html.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	<h3> 我是模板层!!!</h3>
    <!-- {"username": "nfx", "age": 18} ,使用{{变量名}}来调用视图传进来的变量 -->
    <p> {{ username }} </p>
    <p> {{ age }} </p>
</body>
</html>

浏览器访问http://127.0.0.1:8000/test_html
示例二:不同变量类型的调用写法
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns [
    path('test_html_param', views.test_html_param)
]

]# cat mysite1/mysite1/views.py
from django.shortcuts import render
def test_html_param(request):
    dict = {}
    ditc['name'] = 'Tom'				# 值为字符串类型
    dict['age'] = 20					# 值为数值类型
    dict['lis'] = ['a', 'b']			# 值为列表类型
    dict['dic'] = { 'zhangsan': 10, 'lisi': 15 }	# 值为字典类型
    dict['func'] = say					# 值为方法类型
    dict['obj'] = Dog()					# 值为类类型
	return render(request, 'test_html_param.html', dict)
def say():
    return 'hello'
class Dog:
    def wang():
        return 'wangwang'

]# cat mysite1/templates/test_html_param.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	<h1> this is test_html_param </h1>
    <p> {{ name }} </p>				# 值为字符串类型 {{变量名}}
    <p> {{ age }} </p>				# 值为数值类型 {{变量名}}
    <p> {{ lis.0 }} </p>			# 值为列表类型 {{变量名.索引}}
    <p> {{ dic.zhangsan }} </p>		# 值为字典类型 {{变量名.key名}}
    <p> {{ func }} </p>				# 值为方法类型 {{变量名}}
    <p> {{ obj.wang }} </p>			# 值为类类型 {{变量名.方法名}}
</body>
</html>

浏览器访问http://127.0.0.1:8000/test_html_param
this is test_html_param
Tom
20
a
10
hello
wangwang

模板层—标签

标签作用:将一些服务器端的功能嵌入到模板层,例如流程控制等
标签语法:
    {% 标签 %}
    ......
    {% 结束标签 %}
条件 if 标签
{% if 条件表达式1 %}
...
{% elif 条件表达式2 %}
...
{% elif 条件表达式3 %}
...
{% else %}
...
{% endif %}

注意:
1. if 条件表达式里可以用的运算符:
	==!=<><=>=
    innot in
    isis not
    notandor
   注意:运算符两侧要有空格
2. if 条件表达式中,使用字典的键时,直接使用`键名`,而不是`{{ 键名 }}`
2.if 标记中使用实际括号是无效的语法,如果需要指示优先级,则应使用嵌套的 if 标记
示例一:比大小
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("test_if", views.test_if),
]

]# cat mysite1/mysite1/views.py
from django.shortcuts import render
def test_if(request):
    dic = {}
    dic["x"] = request.GET["tem"]		# 将get请求中变量tem的值赋予给字典的键x
    return render(request, "test_if_for.html", dic)

]# cat mysite1/templates/test_if_for.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% if x > 20 %}			<!-- 注意:if条件表达式中,使用字典的键时,直接使用`键名` -->
今天天气非常好
{% elif x <= 20 and x > 10 %}
今天天气很好
{% else %}
今天天气不好
{% endif %}
</body>
</html>

浏览器访问http://127.0.0.1:8000/test_if?tem=25
浏览器访问http://127.0.0.1:8000/test_if?tem=15
浏览器访问http://127.0.0.1:8000/test_if?tem=10
示例二:算数运算
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("mycal", views.test_mycal),
]

]# cat mysite1/mysite1/views.py
from django.shortcuts import render
def test_mycal(request):
    if request.method == "GET":
        return render(request, "mycal.html")
    elif request.method == "POST":
        x = int(request.POST["xx"])	# 将post请求中变量xx的值赋予给字典的键x,注意输入框提交的类型是字符串
        y = int(request.POST["yy"])
        op = request.POST["op"]
        result = 0
        if op == "add":
            result = x + y
        elif op == "sub":
            result = x - y
        elif op == "mul":
            result = x * y
        elif op == "div":
            result = x / y
        # locals()相当于:dic = {"x": x, "y": y, "op": op, "result": result}
        return render(request, "mycal.html", locals())

]# cat mysite1/templates/mycal.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/mycal" method="post">
								<-- 实际action为"http://ip:端口/mycal" -->
								<!-- 注意method="post",request.method为"POST" -->
    <input type="text" name="xx" value="{{ x }}">
    		<!--这里的value,用于处理post请求提供新页面时,保持输入框的显示内容为输入内容-->
    							<!-- value显示的值,数值类型的变量x,使用值{{ x }} -->
    <select name="op">
        <option value="add" {% if op == "add" %}selected{% endif %}></option>
        						<!-- 注意:if条件表达式中,使用字典的键时,直接使用`键名` -->
        <option value="sub" {% if op == "sub" %}selected{% endif %}></option>
        <option value="mul" {% if op == "mul" %}selected{% endif %}></option>
        <option value="div" {% if op == "div" %}selected{% endif %}></option>
        	<!--这里的if判断和selected,用于处理post请求提供新页面时,保持下拉选框op的选择-->
    </select>
    <input type="text" name="yy" value="{{ y }}"> 等于 <span>{{ result }}</span>
    <div><input type="submit" value="开始计算"></div>
    							<!-- 提交按钮将值赋予给xx、op、yy -->
</form>
</body>
</html>

浏览器访问http://127.0.0.1:8000/mycal
示例二的思路
1. 通过request.method来判断是get请求还是post请求,然后返回不同render对象
要保持页面一致,因此不同render对象使用同一模板
2. get请求:访问URL
处理get请求:提供对应的模板页面
3. post请求:提供表单,通过输入框、下拉选框等来输入值,提交键将值赋予变量
处理post请求,获取变量的值赋予给字典的键,将计算结果赋予给字典的一个键,在模板中使用字典的键值
循环 for 标签和forloop变量
{% for 变量 in 可迭代对象 %}
...循环语句
{% empty %}
...可迭代对象无数据时填充的语句
{% endfor %}

# 内置变量 forloop
forloop.counter			# 当前迭代次数(从1起计)
forloop.counter()		# 当前迭代次数(从0起计)
forloop.revcounter		# counter值的倒序
forloop.revcounter()	# revcounter值的倒序
forloop.first			# 如果这是第一次通过循环,则为真
forloop.last			# 如果这是最后一次循环,则为真
forloop.parentloop		# 当嵌套循环,parentloop表示外层循环
示例:for循环和forloop的使用
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("test_for", views.test_for),
]

]# cat mysite1/mysite1/views.py
from django.shortcuts import render
def test_for(request):
    dic = {}
    dic["lis"] = ["zhangsan", "lisi", "wangwu"]
    return render(request, "test_if_for.html", dic)

]# cat mysite1/templates/test_if_for.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for name in lis %}
    {% if forloop.first %} ------- {% endif %}
    <p>{{ forloop.counter }}  {{ name }}</p>
    {% if forloop.last %} ======= {% endif %}
{% empty %}
    当前没数据
{% endfor %}
</body>
</html>

浏览器访问http://127.0.0.1:8000/test_for
-------
1 zhangsan
2 lisi
3 wangwu
=======
模板继承 extends标签和block标签
通过模板继承,子模板可以直接继承父模板的全部内容然后复用,也可以重写父模板中的块(允许被重写的部分)
注意:模板继承时,"服务器端的动态内容无法继承"

# 语法—父模板:
    允许被重写的内容:使用block标签标识可以被重写的内容
        {% block 块名 %}
        ...可以被重写的内容
        {% endblock %}

# 语法—子模板:
	继承模板:使用extends标签(写在模板文件的第一行)继承父模板全部内容
    	{% extends "父模板" %}
		例如:{% extends "base.html" %}
	重写父模板中的块:使用block标签指定重写的内容
    	{% block 父模板的块名 %}
        ...新内容
        {% endblock %}
示例:主页跳转子页面
]# cat mysite1/mysite1/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path("base_index", views.base_view),	# 主页
    path("music_index", views.music_view),	# 音乐板块
    path("sport_index", views.sport_view),	# 体育板块
]

]# cat mysite1/mysite1/views.py
from django.shortcuts import render
def base_view(request):
    return render(request, "base.html")
def sport_view(request):
    return render(request, "sport.html")
def music_view(request):
    return render(request, "music.html")

]# cat mysite1/templates/base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% block mytitle %}			<!--block可以被重写的内容,块名mytitle-->
    <title>主页</title>
    {% endblock %}
</head>
<body>
	<a href="/base_index">首页</a>
            	<!-- 这里的href实际是"http://ip:端口/base_index"-->
    <a href="/music_index">音乐频道</a>
            	<!-- 这里的href实际是"http://ip:端口/music_index"-->
    <a href="/sport_index">体育频道</a>
            	<!-- 这里的href实际是"http://ip:端口/sport_index"-->
    <br>
    {% block info %}			<!--block可以被重写的内容,块名info-->
        这是主页
    {% endblock %}
    <br>
    <h3>这是底部</h3>
</body>
</html>

]# cat mysite1/templates/sport.html
{% extends "base.html" %}		<!--继承父模板base.html的全部内容-->
{% block mytitle %}				<!--重写块mytitle-->
<title>体育频道</title>
{% endblock %}
{% block info %}				<!--重写块info-->
  欢迎来到体育频道
{% endblock %}

]# cat mysite1/templates/music.html
{% extends "base.html" %}		<!--继承父模板base.html的全部内容-->
{% block mytitle %}				<!--重写块mytitle-->
<title>音乐频道</title>
{% endblock %}
{% block info %}				<!--重写块info-->
  欢迎来到音乐频道
{% endblock %}

浏览器访问http://127.0.0.1:8000/base_index,点击体育频道,跳转http://127.0.0.1:8000/sport_index
浏览器访问http://127.0.0.1:8000/base_index,点击音乐频道,跳转http://127.0.0.1:8000/music_index
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值