一. Django基础介绍
1.1 Django是一个WEB应用框架
1.2 WEB应用程序
什么是WEB应用? socket套接字?
客户端(浏览器)<----->服务端
发送请求<----->响应请求
WEB应用就是服务器上的程序,它接受客户端(浏览器)的请求,并响应请求发送字符串给客户端.
二. WEB应用程序1
2.1 socket套节字 网络数据传输必须要用到socket,(其实本地电脑的应用程序和操作系统之间也需要socket)
2.2 实例: 一个简单的WEB应用程序
# 一个简单的WEB应用程序 import socket sock = socket.socket() sock.bind(("127.0.0.1", 8800)) sock.listen(5) while 1: print(\'server waiting......\') conn, addr = sock.accept() data = conn.recv(1024) print(\'data\', data) # conn.send(b"hello luffycity") # 用浏览器访问时,显示该网页无法正常运作127.0.0.1 发送的响应无效。ERR_INVALID_HTTP_RESPONSE.因为浏览器无法识别发过来的数据. conn.send(b"HTTP/1.1 200 OK\r\n\r\nhello luffycity") # 规范的WEB请求格式,浏览器的页面可以正常显示了. conn.close()
三. WEB应用程序2
3.1 发送字符串,让浏览器渲染出带html标签<h1>的字符串
3.2 WEB应用程序读取html文件,并发送到客户端,客户端浏览器进行渲染
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>page</title> </head> <body> <h1>hello dashijie</h1> <img src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3318256381,3210603049&fm=26&gp=0.jpg"> </body> </html>
# -*- coding:utf-8 -*- import socket sock = socket.socket() sock.bind(("127.0.0.1", 8899)) sock.listen(5) while 1: conn, addr = sock.accept() data = conn.recv(1024) print("data", data) with open(\'index.html\', \'r\') as f: data2 = f.read() conn.send(("HTTP/1.1 200 OK\r\n\r\n %s" % data2).encode(\'utf8\')) conn.close()
四. HTTP请求协议1
浏览器向服务器发送的请求,本质是一堆字符串.
4.1 包含以下三个部分:
(每一个小块数据用\r\n区隔开)
a. 请求首行 包含: 请求方式(get或post) 请求路径(url) 协议(HTTP/1.1)
b. 请求头(若干个键值对) 常包含:Host Connection content-Type等
c. 请求体 连续两个\r\n后的就是请求体 只有post请求才有请求体,get请求没有请求体
4..2 请求数据实例
GET / HTTP/1.1\r\nHost: 127.0.0.1:8899\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: none\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\nSec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n\'
4.3 get与post的使用区别
get一般应用于查询,post应用于更新数据库数据
只有POST请求才有请求体
五. HTTP请求协议2
post请求实例,是一个典型的用户登录框
服务器的接收到的请求(末尾黄色字是请求体,即浏览器用户输入的用户名和密码):
POST / HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nContent-Length: 31\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nOrigin: http://127.0.0.1:8800\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\nSec-Fetch-Dest: document\r\nReferer: http://127.0.0.1:8800/\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\nusername=lldbyt&password=123456\'
一个典型的服务器端post请求源代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> </head> <body> <form action="http://127.0.0.1:8800/" method="post"> <!--这里提交后地址没变,action可以省略--> 用户名:<input type="text" name="username"> <!--name就是POST请求体键值对的KEY--> 密码:<input type="password" name="password"> <!--服务器接收到的请求体nusername=lldbyt&password=123456--> <input type="submit"> </form> </body> </html>
import socket sock = socket.socket() sock.bind(("127.0.0.1", 8800)) sock.listen(5) while 1: print(\'server waiting......\') conn, addr = sock.accept() data = conn.recv(1024) print(\'data\', data) with open(\'login.html\', \'r\', encoding=\'utf-8\') as f: # 注意编码 data_login = f.read() conn.send(("HTTP/1.1 200 OK\r\n\r\n%s" % data_login).encode(\'utf-8\')) conn.close()
六. HTTP之响应协议
6.1 请求方式: get与post请求的区别
- get提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的请求体中.
- get提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
- get与post请求在服务端获取请求数据方式不同.
6.2 响应格式(浏览器接收到服务器发来的信息)
6.3 自定义响应头(黄色字符部分)
conn.send(b"HTTP/1.1 200 OK\r\nContent-Type:text\r\n\r\n%s" % data)
6.4 响应状态码
状态码如200 OK,以3位数字和原因 成。数字中的 一位指定了响应 别,后两位无分 。响应 别有以5种。
重定向的解释: 浏览器向服务器发送请求,服务器接收到后再发送响应信息给浏览器,该响应信息告诉浏览器需要(浏览器再发一次请求)转到另外一个资源.
七. wsgiref模块1
- wsgi是接口协议
7.1 wsgiref模块功能
- 按照HTTP请求协议格式进行解包(解析数据)
- 按照HTTP响应协议封装数据
- wgiref模块封装了socket
- 让WEB应用开发者专注于WEB业务开发
八. wsgiref模块2
- wsgiref做的简单的,根据用户不的输入,实现不同网页的效果
# wsgiref做的WEB应用程序 from wsgiref.simple_server import make_server def application(environ, start_response): # 按照http协议解析数据:environ字典格式 # 按照http协议组装数据:start_response # print(environ) # print(type(environ)) # 获取当前请求路径 path = environ.get("PATH_INFO") print(path) # 根据用户不同的输入,实现不同的网页,动态网站效果. if path == \'/login\': # 登录页面 with open(\'login.html\', \'r\', encoding=\'utf-8\') as f: data = f.read() elif path == \'/index\': # 主页面 with open(\'index.html\', \'r\', encoding=\'utf-8\') as f: data = f.read() # 响应体 start_response(\'200 OK\', [("Content-Type", "text/html")]) return [data.encode("utf-8")] # 返回值一定要加[],这是规定 \'\'\' # 下面两句构成了完整的最简单的响应体 start_response(\'200 OK\', [("Content-Type", "text/html")]) # 响应首行, 响应头 return [b\'<h1>Hello, WEB!</h1>\'] # 响应主体 \'\'\' # make_server封装了socket, application是回调函数 httped = make_server("", 8090, application) # 省略host地址表示127.0.0.1 # 等待用户连接,相当于之前的: conn, addr = sock.accept() httped.serve_forever() # 一旦请求进来后,就调用上面的application函数
九. diy一个web框架
- 路由的解释
9.1 浏览器默认会发送图标请求
- 此响应时,浏览器只接收图片,如果发送字符串,则字符串不会显示.
from wsgiref.simple_server import make_server def application(environ, start_response): start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')]) print(\'PATH\', environ.get(\'PATH_INFO\')) path = environ.get("PATH_INFO") if path == "/favicon.ico": # return [b"hello favicon.ico"] # 此字符串响应不会在浏览器上显示,图标请求只接收图片请求. with open(\'baidu.ico\', \'rb\') as f: # 读取图标文件 data = f.read() return [data] # 返回图标文件 return [b"<h1>Hello</h1>"] httped = make_server(\'\', 8899, application) httped.serve_forever()
十. diy一个web框架
10.1 用户输入不同的路径,实现访问不同的页面,不用很多的if..else,用路由分发的方法
- 其实也是解了耦合
from wsgiref.simple_server import make_server
# 定义每个视图函数
def login(environ):
with open(\'login.html\', \'rb\') as f:
data = f.read()
return data
def index(environ):
with open(\'index.html\', \'rb\') as f:
data = f.read()
return data
def fav(environ):
with open(\'favicon.ico\', \'rb\') as f:
data = f.read()
return data
def application(environ, start_response):
start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')])
print(\'PATH\', environ.get(\'PATH_INFO\'))
path = environ.get("PATH_INFO")
# 路由
url_patterns = [
(\'/login\', login),
(\'/index\', index),
(\'/favicon.ico\', fav)
]
func = None
for item in url_patterns:
if path == item[0]:
func = item[1]
break
if func:
return [func(environ)] # 返回值中包含environ的原因是视图中一般都会用到请求的数据
else:
return [b\'404!\']
httped = make_server(\'\', 8899, application)
httped.serve_forever()
十一. diy一个web框架2
11.1 软件开发规范,再解耦合,修改diy的web框架
from wsgiref.simple_server import make_server def application(environ, start_response): start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')]) print(\'PATH\', environ.get(\'PATH_INFO\')) path = environ.get("PATH_INFO") from urls import url_patterns func = None for item in url_patterns: if path == item[0]: func = item[1] break if func: return [func(environ)] # 返回值中包含environ的原因是视图中一般都会用到请求的数据 else: return [b\'404!\'] httped = make_server(\'\', 8899, application) httped.serve_forever()
from views import * # 路由 url_patterns = [ (\'/login\', login), # 视图函数 (\'/index\', index), (\'/favicon.ico\', fav) ]
# 定义每个访问页面视图函数 def login(environ): with open(\'login.html\', \'rb\') as f: data = f.read() return data def index(environ): with open(\'index.html\', \'rb\') as f: data = f.read() return data def fav(environ): with open(\'favicon.ico\', \'rb\') as f: data = f.read() return data
十二. 框架的简单使用
增添功能,只需要修改路由文件,视图文件,模板等就可以了.
十三. 扩展框架关于数据库的操作
如果没有数据库,就没有什么意义.
十四. Django简介
大而全,组建特别多
14.1 mvc和mtv模型(模型的主要目的是解耦合)
十五. Django的下载与基本命令
15.1 cmd纯命令行, 安装Django模块命令
pip install Django #注意: Django首字母大写
如果提示pip错误,则先python.exe -m pip install --upgrade pip,注意该命令前面不能带路径
15.2 cmd纯命令行, 在当前文件夹下创建一个mysite项目
django-admin.py startproject mysite # 注意首字母d是小写
项目的文件夹树, manage.py为启动文件,相当于之前的main.py
manage.py ----Django项目里面的工具,通过它可以调用django shell和数据库
settings.py ----包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量
urls.py ----负责把URL模式映射到应用程序
15.3 cmd纯命令行, 在mysite目录下创建应用blog(注意:mysite是项目文件夹,blog是应用程序文件夹, 一个项目中可以包含多个应用程序)
首先cd mysite文件夹,进入sysite项目根目录
然后python manage.py startapp blog创建blog应用程序
下图为创建blog应用后的文件夹
下图为blog应用程序的文件结构
15.4 cmd命令下, 启动Django项目
python manage.py runserver 8000
下图为命令输入后显示的效果:
浏览器访问http://127.0.0.1:8000后的效果
十六. Django简单示例
16.1 一个显示当前时间的小程序
16.1.1 创建一个Django项目
16.1.2 生成的一个fisrt_pro项目,新建一个app01程序的目录
16.1.3 几个文件的代码
from django.contrib import admin from django.urls import path from app1 import views urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), # path帮助调用自定义的views.timer函数 ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>当前时间:{{ date }}</h2> {#上面的两个大括号包着变量date这个是规定的格式#} </body> </html>
16.2 Django服务(请求)执行过程(MPV过程)
---->1. urls.py(由它决定用户发送的路径交给哪个视图函数处理) ---->2. views.py(主要逻辑处理) ---->3. models.py(涉及到数据库的从这里取) ---->4. timer.html(最终的返回页面的模板) ---->
十七. 静态文件配置1
17.1 什么叫静态文件?
项目中的css, js都应当处理为静态文件. 项目中的任何文件都需要Django去找, 最终都是由客户端的浏览器去执行的,引入jquery-3.1.1.js时就需要注意文件路径问题(不能当成客户端的本地文件去执行), js文件需要写成服务器的地址.
17.2 如何配置静态文件?
在项目的根目录下创建static文件夹,所有静态文件都放入其中.此时通过客户端浏览器无法直接访问该文件,因为(Django采用虚拟文件夹的方式访问)客户端的所有访问路径都经过urls.py路由处理,而urls.py中没有添加关于js的路由. 在settings.py中配置一下就可以通过别名访问了.配置方法在下面.
相关文件在项目中的位置:
处理settings.py文件
STATIC_URL = \'/static/\' # 路径别名(虚拟目录),客户端浏览器输入地址(路径)可直接访问了 STATICFILES_DIRS = [ os.path.join(BASE_DIR, \'statics\'), # 实际路径, 这里是方便为了区分才使用statics这个名字, 正式使用时,实际目录和别名都用static ]
模板文件引入jquery-3.5.1.js,文字点击变为红色效果timer.html
<body> <h2>当前时间:{{ date }}</h2> <script src="/static/jquery-3.5.1.js"></script> # 这里使用statics目录的别名static, statics是实际目录, static是虚拟目录(别名) # 正式使用时,实际目录和别名都用static # 下面的效果是,点击变为红色 <script type="text/javascript"> $(\'h2\').click(function () { $(this).css(\'color\', \'red\'); }) </script> </body>
十八. 静态文件配置2
18.1 html文件中引入css和js
css文件和js文件也是静态文件,需要放到项目中的static文件夹下,针对不同的应用可以在static文件夹下再建立应用的子文件夹,比如app01,将app01应用程序的css和js文件放到app01文件夹下.这里需要注意的是,在html中引入css\js文件时需要使用static静态目录.
18.2 实例(显示当前时间戳),原文字颜色为红色,鼠标点击文字变为蓝色,采用css和jquery结合的方法
文件目录如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link type="text/css" rel="stylesheet" href="/static/app/timer.css"> <script src="/static/jquery-3.5.1.js"></script> </head> <body> <h2>当前时间是:{{ ctime }}</h2> <script rel="script" src="/static/app/timer.js"></script> </body> </html>
h2{ color: red; }
$(\'h2\').click(function () { $(this).css(\'color\', \'blue\'); })
from app import views urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), ]
STATIC_URL = \'/static/\' STATICFILES_DIRS = [ os.path.join(BASE_DIR, \'static\') ]
from django.shortcuts import render # Create your views here. def timer(request): import time ctime = time.time() return render(request, \'timer.html\', {\'ctime\': ctime})
十九. 路由控制之简单配置
19.1 re_path模块, Django1版本的路由
路由配置: 路径-------->视图函数
采用正则表达式re匹配路径,(路径中加括号的分组)
from django.shortcuts import render, HttpResponse # HttpResponse # Create your views here. def timer(request): import time ctime = time.time() return render(request, \'timer.html\', {\'ctime\': ctime}) def special_case_2003(request): return HttpResponse("special_case_2003") def year_archive(request, year): return HttpResponse(year) def month_archive(request, year, month): return HttpResponse(year+"-"+month) def article_detail(request, year, month, date): return HttpResponse(year+"-"+month+"-"+date)
19.2 注意:
- 若要从URL中获取一值,只需要在它周围放置一对圆括号.
- 不需要添加一个前导的反斜杠,应为每个URL都有.例如,应该是^articles而不是^/articles.
- 每个正则表达式前面的"r"是可选的,但建议加上.它告诉python这个字符串是"原始的"----字符串中任何字符都不应该转义.
二十. 路由控制之有名分组
20.1 为路径中的分组起名字,关键字参数传参
# re_path(r"^articles/([0-9]{4})/([0-9]{2})/$", views.month_archive), # month_archive(request, yaer, month) # 路径中包含年月,re分三个组 re_path(r"^articles/(?P<y>[0-9]{4})/(?P<m>[0-9]{2})/$", views.month_archive), # 分组前加?P<y>相当于定义了y=[0-9]{4},及定义了关键字参数,并向函数传递 # month_archive(request, y=2009, m=12)
def month_archive(request, m, y): # 关键字参数,颠倒位置不影响传值,名称要与url中的分组名称一致 return HttpResponse(y+"-"+m) def article_detail(request, year, month, date): return HttpResponse(year+"-"+month+"-"+date)
二十一. 路由控制之分发
21.1 就是将全局的urls.py文件进行细分, 每个app建立一个自己的urls.py文件,也就是再解耦.
全局urls.py和一个app应用程序的urls.py,文件结构
21.2 两个URLS.py代码
浏览器访问地址: http://127.0.0.1:8000/app/articles/2009/02/
全局urls.py中的关键代码
re_path(\'app/\', include("app.urls")), # 分发到一个app中
from django.contrib import admin from django.urls import path, re_path, include # include from app import views, urls # app的urls urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), re_path(\'app/\', include("app.urls")), # 分发到一个app中 # path(\'app/\', include(urls)) # 这样写也可以 ]
from django.contrib import admin from django.urls import path, re_path # re_path from app import views urlpatterns = [ # ^表示以指定字符串开头 $表示以指定字符串结尾 re_path(r"^articles/2003/$", views.special_case_2003), # special_case_2003(request) re_path(r"^articles/([0-9]{4})/$", views.year_archive), # year_archive(request, 1999) 这里会多传一个参数,及有两个参数.只要是匹配分组的(re正则表达式中一个小括号,就是一个分组),一个分组项目就有一个参数. # re_path(r"^articles/([0-9]{4})/([0-9]{2})/$", views.month_archive), # month_archive(request, yaer, month) # 路径中包含年月,re分三个组 re_path(r"^articles/(?P<y>[0-9]{4})/(?P<m>[0-9]{2})/$", views.month_archive), # 分组前加?P<y>相当于定义了y=[0-9]{4},及定义了关键字参数,并向函数传递 # month_archive(request, y=2009, m=12) re_path(r"^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$", views.article_detail), # article_detail(request, year, month, date) ]
21.3 让浏览器访问的地址中不需要输入APP
re_path(r\'^\', include("app.urls")),
二十二. 路由控制之登录验证实例
22.1 访问login路径及提交验证用户名密码是否正确,及有两次请求同一路径.
第一次是在地址栏访问网址:http://127.0.0.1:8000/login/, 这是get请求
第二次是在login页面点击提交按钮, 这是POST请求
from django.contrib import admin from django.urls import path, re_path, include from app import views, urls urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), re_path(\'login/\', views.login), # 登录 ]
注意Views.py文件中两次请求的逻辑设置
from django.shortcuts import render, HttpResponse # HttpResponse def login(request): print(request.GET) print(request.POST) if request.method == \'GET\': #第一次get请求访问/login/路径 return render(request, \'login.html\') else: #第二次post请求提交用户名密码 print(request.GET) print(request.POST) user = request.POST.get(\'user\') pwd = request.POST.get(\'pwd\') if user == \'杨阳\' and pwd == \'123\': return HttpResponse(\'登录成功!\') else: return HttpResponse("用户名密码错误!") \'\'\' 请求结果 print(request.GET) print(request.POST) 输出样式如下: <QueryDict: {}> <QueryDict: {\'user\': [\'杨阳\'], \'pwd\': [\'123\']}> 使用字典的get方法可获取字典的值. user = request.POST.get(\'user\') \'\'\'
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> </head> <body> <form action="http://127.0.0.1:8000/login/" method="post"> 用户名:<input type="text" name="user"> 密码:<input type="password" name="pwd"> <input type="submit"> </form> </body> </html>
post提交时出现错误提示,需要把settings.py文件中的一句代码删除,或注释掉.
# \'django.middleware.csrf.CsrfViewMiddleware\',
二十三. 路由控制之反向解析1
23.1 用别名指代URL. 在模板中进行反向解析
urls.py中,对于路径采用别名.应用场景:当产品经理要求更改访问路径的时候,只需要改urls.py中的路径名就可以了,其它文件无需改动.
使用urls.py中使用别名,增加参数name=\'别名\',注意在模板文件中把action中的路径改为规定的格式{% url "别名" %}. views.py中的函数名称与别名要保持一致.
from django.contrib import admin
from django.urls import path, re_path, include # include
from app import views, urls # app的urls
urlpatterns = [
path(\'admin/\', admin.site.urls),
path(\'timer/\', views.timer),
re_path(\'login.html\', views.login, name=\'login\'), # 别名为login,地址栏访问时的路径写login.html
]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> </head> <body> <form action="{% url "login" %}" method="post"> 用户名:<input type="text" name="user"> 密码:<input type="password" name="pwd"> <input type="submit"> </form> </body> </html>
二十四. 路由控制之反向解析2
24.1 在视图函数中进行反向解析
可以在后台获取到用户访问的完整路径
需要在视图函数中导入反向解析函数reverse, 需要注意的是,如果urls.py路径中包含正则表达式,则需要在视图函数中增加args参数
from django.contrib import admin
from django.urls import path, re_path # re_path
from app import views
urlpatterns = [
re_path(r"^articles/2003/$", views.special_case_2003, name="s_c_2003"), # name就是路径的别名
re_path(r"^articles/([0-9]{4})/$", views.year_archive, name="year_archive"), # 此处包含正则表达式, 视图函数中如果要获取这个路径,需要增加args参数
]
from django.shortcuts import render, HttpResponse # HttpResponse from django.urls import reverse # reverse反向解析函数 def timer(request): url = reverse(\'s_c_2003\') # 可以在任何函数中获取的需要的路径 print(url) import time ctime = time.time() return render(request, \'timer.html\', {\'ctime\': ctime}) def special_case_2003(request): url = reverse(\'s_c_2003\') # 括号中的参数填写urls.py中的路径别名 print(url) return HttpResponse("special_case_2003") def year_archive(request, year): url = reverse(\'year_archive\', args=("2005",)) # 此处包含正则表达式的匹配问题, 需要增加一个args参数,注意书写规则, 2005是四位数与匹配的路径位数相等[0-9]{4} print(url) return HttpResponse(year)
后台输出如下:
/app/articles/2005/
二十五. 路由控制之名称空间1
25.1 名称空间 (namespace),也叫命名空间,是表示标示符的可见范围.
由于name没有作用域,Django在反解析URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回,我们在开发项目时,会经常使用name属性反解除URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入命名空间.
25.2 实例
25.2.1 使用pycharm的命令行窗口创建新应用app02
25.2.2 创建新应用后的目录如下:
25.2.3 为全局urls.py添加app02的路由分发
from django.contrib import admin from django.urls import path, re_path, include from app import views, urls urlpatterns = [ re_path(r\'^app/\', include("app.urls")), # 分发到app中 re_path(r\'^app02/\', include("app02.urls")), # 分发到app02中 ]
25.2.4 为app02增加index视图
from django.shortcuts import render, HttpResponse # Create your views here. def index(request): return HttpResponse("index2")
25.2.5 为app02的urls.py增加index的路由解析
from django.contrib import admin from django.urls import path, re_path # re_path from app02 import views urlpatterns = [ re_path("index/", views.index) ]
25.2.6 访问http://127.0.0.1:8000/app02/index/得到了想要的结果,app02应用建立成功了.
25.2.7 为app应用添加index视图和路由,以及访问的效果.
def index(request): return HttpResponse(\'index01\')
from django.contrib import admin from django.urls import path, re_path # re_path from app import views urlpatterns = [ re_path(\'index/\', views.index) ]
二十六. 路由控制之名称空间2
25.2.8 分别为app和app01下的两个index加别名
应用程序中的urls.py文件中路由解析
re_path(\'index/\', views.index, name=\'index\')
现在就有两个index,名字就重复了.
25.2.9 两个index,反向解析,就出问题了.
先分别为两个app的views.py添加名称index的反向解析 reverse(\'index\')
分别访问两个index,反向解析路径,得到的路径是相同的,就出问题了.(覆盖了)
from django.shortcuts import render, HttpResponse # Create your views here. from django.urls import reverse def index(request): return HttpResponse(reverse(\'index\')) # reverse(\'index\')反向解析出名称为"index"的实际路径
25.2.10 下面解决名称相同,的反向解析相同的问题
25.2.11 在全局urls.py中给include()路由分发添加名称空间namespace
urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), re_path(\'login.html\', views.login, name=\'login\'), re_path(r\'^app/\', include(("app.urls", "app"))), # 名称空间namespace="app" 注意这里增加了一个元组 re_path(r\'^app02/\', include(("app02.urls", "app02"))), # 名称空间namespace="app02" ]
25.2.12 再给两个app的views.py文件中的反向解析添加上namespace的名称
def index(request): return HttpResponse(reverse(\'app:index\')) # 为反向解析添加了namespace,注意有个冒号
def index(request): return HttpResponse(reverse(\'app02:index\')) # 注意书写的格式
25.2.14 再访问路径,问题解决了.反向解析得到了正确的结果
二十七. url控制之path方法
27.1 django2.0的path. re_path是django1.0版本的语法
re_path传的值是字符串, 如果要使用数字,还需要进行转换. path方法就是解决这类问题而生的.
参考:https://www.cnblogs.com/yuanchenqi/articles/8931472.html
urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), path(\'login/\', views.login), path("articles/<int:year>", views.path_year), # path_year(request, 2001) ]
def path_year(request, year): print(year) print(type(year)) return HttpResponse("path_year...")
27.2 path的5个转化器
str,匹配除了路径分隔符/之外的非空字符串,这是默认的形式
int,匹配正整数,包含0
slug,匹配字母,数字以及横杆,下划线组成的字符串
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00
path,匹配任何非空字符串, 包含了路径分隔符
二十八. path的自定义转换器
28.1 对于一些复杂或者复用的需要,可以定义自己的转化器.转化器是一个类或接口,它的要求有三点:
- regex类属性,字符串类型
- to_python(self, value)方法, value是由类属性regex所匹配到的字符串,返回具体的python变量值,以供Django传递到对应的视图函数中
- to_url(self, value)方法,和to_python方法相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用.
28.2 自定义path转化器应用实例.(使用register_converter
将其注册到URL配置中)
# app中新建文件urlconvert.pyz,自定义匹配规则 class MonConvert: regex = "[0-9]{2}" def to_python(self, value): return int(value) def to_url(self, value): return "%04d" % value
# 视图函数 def path_month(request, month): print(month) print(type(month)) return HttpResponse("path_month...")
from django.contrib import admin from django.urls import path, register_converter # register_converter注册自定义转化器 from app1.urlconvert import MonConvert register_converter(MonConvert, "mm") # 注册并自定义为名称为mm from app1 import views urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'timer/\', views.timer), path(\'login/\', views.login), # path("articles/<int:year>", views.path_year), # path_year(request, 2001) path(\'articles/<mm:month>\', views.path_month), ]
二十九. url控制总结
29.1 简单配置
29.2 有名分组 按关键字传参
29.3 分发 include
29.4 反向解析 利用别名
29.5 名称空间
29.6 path
三十. 视图层之请求对象
参考:https://www.cnblogs.com/yuanchenqi/articles/8876856.html
30.1 在视图函数中获取用户的请求数据
先关闭settings.py中的一项设置项目才可正常运行
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'index/\', views.index), ]
from django.shortcuts import render, HttpResponse # Create your views here. def index(request): print(request.GET) print(request.POST) print(request.method) return render(request, "index.html") # return HttpResponse(\'ok\')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> </head> <body> <form action="" method="post"> 姓名:<input type="text" name="name"> 年龄:<input type="text" name="age"> <input type="submit"> </form> </body> </html>
后台获取到了用户的输入,表单post请求:
获取到的get请求数据,是个字典格式.
获取值:
request.GET.get("name") request.POST.get("name")
30.2 urls.py中匹配根目录,及访问地址是http://127.0.0.1:8000/
re_path("^$", views.index) 或者 path(r"", views.index),
30.3 方法request.get_full_path(),获取完成的路径可保护get数据
下面来看看两个区别:
print(request.path) #只能获取路径
print(request.get_full_path()) #路径+get请求的数据
后台显示结果:
三十一. 视图响应对象
31.1 最终都是返回HttpResponse(), render()也会转化为HttpResponse()
31.1.1 HttpResponse("<h1>ok</h1>")
31.1.2 render(request, "index.html", {"变量名": 变量值}) # 把变量传递给模板,注意书写字典格式
模板文件index.html中使用{{ 变量名 }} # 模板语法, 注意书写格式,双大括号
三十二. 模板语法之变量
参考:https://www.cnblogs.com/yuanchenqi/articles/8876892.html
32.1 字典,列表,对象,字符串, 等更丰富的数据类型怎么嵌入到模板中
模板语法: 渲染变量用{{}} 用句点符深度查询, 过滤器
渲染标签用{%%}
使用句点符达到的效果就是从字典,列表等数据中再获取指定位置的值
def index(request): """ 模板语法: 渲染变量用{{}} 1. 深度查询 用句点符 2. 过滤器 渲染标签用:{%%} """ # 各种类型的数据 name = "yuan" i = 10 l = [111, 222, 333] info = {\'name\': "yuan", "age": 22} b = True class Person: def __init__(self, name, age): self.name = name self.age = age alex = Person(\'alex\', 35) egon = Person(\'egon\', 33) person_list = [alex, egon] # return render(request, "index.html", {"name": name}) return render(request, "index.html", locals()) # locals()传递所有的局部变量
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> </head> <body> <p> {{ name }}</p> <p> {{ i }}</p> <p> {{ info }}</p> <p> {{ l }}</p> <p> {{ alex }}</p> <p> {{ person_list }}</p> <p> {{ b }}</p> <hr> <p>深度查询,用句点符,获取具体值</p> <p>{{ info.name }}</p> <p>{{ l.2 }}</p> <p>{{ alex.age }}</p> <p> {{ person_list.1.name }}</p> </body> </html>
三十三. 过滤器
参考:https://www.cnblogs.com/yuanchenqi/articles/8876892.html的第二部分过滤器
把视图中的变量按照某种形式在模板中渲染出来
33.1 语法:
{{ obj|filter_name:param }}
33.2 日期过滤器date
视图文件代码:
import datetime
date = datetime.datetime.now()
模板文件代码:
<p>{{ date }}</p> 显示效果:Jan. 17, 2021, 1:06 p.m. (显示的不友好)
<p>{{ date|date:\'Y-m-d\' }}</p> 显示效果:2021-01-17 这里date|date,两个date后面一个date是过滤器名称
下图为显示效果对比:
33.3 default过滤器,默认在数据为空的情况下的显示内容
视图文件代码: name_list = [] 列表值为空
模板文件代码: <p>{{ name_list }}</P> 显示效果: []
<p>{{ name_list|default:"姓名数据为空"}} </p> 显示效果:姓名数据为空
33.4 length过滤器,返回值的长度
{{ value|length }} 如果value是[\'a\', \'b\', \'c\'],那么输出是3
33.5 filesizeformat,将值格式化为一个"人类可读"文件尺寸.主要用于显示文件的大小
{{ value|filesizeformat }} 如果value是123456789,输出将会是117.7MB
33.6 slice过滤器,将字符串切片
{{ value|slice:"2:-1" }} 如果value="hello word",将输出llo wor
33.7 truncatechars,截断过滤器,如果字符串字符多余指定的字符数量,那么会被截断.截断的字符串将以可翻译的省略号序列"..."结尾
{{ value|truncatechars:9 }} 参数9,就是要显示(截断)的字符数包含...一共是9个字符,(注意一个字母就是一个字符)
{{ hello_str|truncatewords:1 }} truncatewords参数3,表示显示的单词数量,不包含...(注意hello表示一个单词)
下图为显示效果:
33.8 safe过滤器,强调不转义,用在变量是html标签时
{{ value }} 如果value = \'<a href="#">click</a>\' 在浏览器中当成是字符串显示 <a href="#">click</a>
{{ value|safe }} 如果value = \'<a href="#">click</a>\' 则可在浏览器中当成是html标签渲染出<a>标签的效果
下图为显示效果对比:
33.9 add加法过滤器
{{ l.0|add:100}} 如果l.0 = 100,那么输出200
33.10 upper转大写过滤器
{{ name|upper }} 如果name = "hao",那么输出HAO
import datetime date = datetime.datetime.now() name_lsit = [] hello_str = \'hello word\' link_click = "<a href=\'#\'>click</a>"
<p>过滤器</p> <p>{{ date }}</p> <p>{{ date|date:\'Y-m-d\' }}</p> <p>{{ name_list|default:\'姓名数据为空\' }}</p> <p>{{ hello_str|slice:"2:-1" }}</p> <p>{{ hello_str|truncatechars:3 }}</p> <p>{{ hello_str|truncatewords:1 }}</p> <p>{{ link_click }}</p> <p>{{ link_click|safe }}</p>
三十四. 模板语法之标签
34.1 标签看起来像这样的:{% tag %}.标签比变量更加复杂:一些在输出中创建文本,一些通过循环或逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模板中.一些标签需要开始和结束标签(例如{% tag %}...标签内容...{% endtag %})
34.2 最少需要掌握for标签和if标签
34.3 for循环标签
遍历每一个元素:
{% for person in person_list %} <p>{{ person.name }}</p> {% endfor %}
可以利用{% for obj in list reversed %}反向完成循环。
遍历一个字典:
{% for key,val in dic.items %} <p>{{ key }}:{{ val }}</p> {% endfor %}
如果{% for key in dic %}则只遍历key
注:循环序号可以通过{{forloop}}显示
forloop.counter The current iteration of the loop (1-indexed)从1开始,显示元素的下标 forloop.counter0 The current iteration of the loop (0-indexed)从0开始 forloop.revcounter The number of iterations from the end of the loop (1-indexed) forloop.revcounter0 The number of iterations from the end of the loop (0-indexed) forloop.first True if this is the first time through the loop forloop.last True if this is the last time through the loop
34.4 for...empty数据为空时的处置.为空的时候不会渲染.当使用for...empty时就可以渲染指定的内容.
for 标签带有一个可选的{% empty %} 从句,以便在给出的组是空的或者没有被找到时,可以有所操作。
{% for person in person_list %} <p>{{ person.name }}</p> {% empty %} <p>sorry,no person here</p> {% endfor %}
34.5 if标签
{% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if num > 100 or num < 0 %} <p>无效</p> {% elif num > 80 and num < 100 %} <p>优秀</p> {% else %} <p>凑活吧</p> {% endif %}
模拟网站用户登录状态视图文件views.py
name = None
# name = "chendong"
<h4>模板文件.当用户登录时显示问候和注销,当用户没有登录显示注册和登录</h4> {% if name %} <p> <a href="">hi {{ name }}</a> <a href="">注销</a> </p> {% else %} <p> <a href="">注册</a> <a href="">登录</a> </p> {% endif %}
34.6 with标签,把长名字缩减成短名字
{% with person_list.1.name as n %}
{{ n }} 使用n就相当于用person_list.1.name,当深度查询非常长的时候用
{% endwith%}
使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的
例如:
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
34.7 csrf_token这个标签用于跨站请求伪造保护
此功能与全局settings.py中的\'django.middleware.csrf.CsrfViewMiddleware\'有关系
在表单的post提交中会出现下面的提示Forbidden (403):
如果在模板中添加csrf_token就不会出现Forbidden (403)错误了.
<form action="" method="post"> {% csrf_token %} <input type="text"> <input type="submit"> </form>
查看网页源代码,多出了一个input标签,多出了的input就是{% csrf_token %}的功劳.有了它就可以正确提交post请求.(识别是不是第二次访问,拦截判断用)
三十五. 模板语法之自定义标签与过滤器
34.1 自定义标签与过滤器,流程固定,背下来
1、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
2、在app中创建templatetags模块(模块名只能是templatetags)
3、创建任意 .py 文件,如:my_tags.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from django import template
from django.utils.safestring import mark_safe
register = template.Library() #register的名字是固定的,不可改变
@register . filter
def filter_multi(v1,v2): # 自定义乘法过滤器
return v1 * v2
<br> @register .simple_tag
def simple_tag_multi(v1,v2): # 自定义乘法标签
return v1 * v2
<br> @register .simple_tag
def my_input( id ,arg):
result = "<input type=\'text\' id=\'%s\' class=\'%s\' />" % ( id ,arg,)
return mark_safe(result)
|
4、在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py
1
|
{ % load my_tags % }
|
5、使用simple_tag和filter(如何调用)
1
2
3
4
5
6
7
8
9
10
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .html
{ % load xxx % }
# num=12 {{ num|filter_multi: 2 }} #24
{{ num|filter_multi: "[22,333,4444]" }}
{ % simple_tag_multi 2 5 % } 参数不限,但不能放在 if for 语句中
{ % simple_tag_multi num 5 % }
|
注意:filter可以用在if等语句后,simple_tag不可以
1
2
3
|
{ % if num|filter_multi: 30 > 100 % }
{{ num|filter_multi: 30 }}
{ % endif % }
|
34.2 实例自定义乘法过滤器
34.2.1 在settings.py中配置app
34.2.2 在app中创建templatetags模块(模块名只能是templatetags)
34.2.3 创建my_tag_filter .py 文件,目录如下:
from django import template register = template.Library() @register.filter def multi_filter(x,y): # 自定义乘法过滤器 return x*y
34.2.4 在使用自定义multi_filter的html文件中导入之前创建的my_tag_filter .py
{% load my_tag_filter %}
34.2.5 使用自定义multi_filter(如何调用)
<p>{{ i|multi_filter:5 }}</p>
34.3 自定义标签相关代码
@register.simple_tag def multi_tag(x, y, z): # 自定义乘法标签 return x*y*z
<h4>自定义标签调用</h4> <p>{% multi_tag i 5 2 %}</p>
三十六. 模板语法之继承
36.1 {% include \'advertise.html\' %}在html模板文件中嵌入其它html模板文件
两个父子模板文件在目录中的位置
在index.html文件中的左侧嵌入advertise.html
advertise.html文件全部代码
最终效果,左侧三个面板为 {% include \'advertise.html\' %}嵌入的内容:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style type="text/css"> * { margin: 0; padding: 0; } .header { width: 100%; height: 50px; background-color: #369; } </style> </head> <body> <div class="header"></div> <div class="container"> <div class="row"> <div class="col-md-3"> {% include \'advertise.html\' %} </div> <div class="col-md-9"> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> <h1>首页</h1> </div> </div> </div>
<div class="action"> <div class="panel panel-danger"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-warning"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-success"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div>
36.2 继承.{% extends \'base.html\' %}
建立一个新页面order.html该页面想和前面的index.html拥有相同的头部和左侧面板,这就用到了继承.
新建base.html基础模板页面,作为父页面,复制index.html中的内容到该业中,该页面中,包含css,头部和左侧面板,内容部分被删除,代替以block盒子{% block con%}在这个中间加入该页面独特内容{% endblock %},代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style type="text/css"> * { margin: 0; padding: 0; } .header { width: 100%; height: 50px; background-color: #369; } </style> </head> <body> <div class="header"></div> <div class="container"> <div class="row"> <div class="col-md-3"> {% include \'advertise.html\' %} </div> <div class="col-md-9"> {% block con %} {# 此处为可扩展的盒子, 等待继承者在自己的页面中加入自己独特的内容#} {% endblock %} </div> </div> </div> </body> </html>
清空index.html页面,在页面中输入{% extends \'base.html\' %},意思是index.html页面继承了base.html.页面中block盒子{% block con%}此处添加index.html页面独特的内容{% endblock%}.
{#继承自base.html,包含头部,左侧等内容#} {% extends \'base.html\' %} {% block con %} {# index.html页面独特的内容#} <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> {% endblock %}
新建订单页面orders.html,继承base.html页面,
{% extends \'base.html\' %} {% block con %} <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> {% endblock %}
可以在base.html页面中留多个盒子,甚至<title></title>标签都可以设计为一个盒子.下面的代码是增加了一个控制标题的盒子{% block title %}.模板页面中可以添加多个盒子,以解决代码复用的问题.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> {% block title %} {#这里是标题的盒子,每个页面就可以有自己的标题#} {% endblock %} <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style type="text/css"> * { margin: 0; padding: 0; } .header { width: 100%; height: 50px; background-color: #369; } </style> </head> <body> <div class="header"></div> <div class="container"> <div class="row"> <div class="col-md-3"> {% include \'advertise.html\' %} </div> <div class="col-md-9"> {% block con %} {# 此处为可扩展的盒子, 等待继承者在自己的页面中加入自己独特的内容#} {% endblock %} </div> </div> </div> </body> </html>
{#继承自base.html,包含头部,左侧等内容#} {% extends \'base.html\' %} {#标题盒子#} {% block title %} <title>网站首页</title> {% endblock %} {% block con %} {# index.html页面独特的内容#} <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> {% endblock %}
{% extends \'base.html\' %} {% block title %} <title>订单页面</title> {% endblock %} {% block con %} <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> <h1>订单页面</h1> {% endblock %}
36.3 如何显示父模板的盒子{% block %}中的内容(默认子模板会覆盖父模板盒子总的内容),在子模板中的盒子内加入{{ block.super }}
<div class="col-md-9"> {% block con %} {# 此处为可扩展的盒子, 等待继承者在自己的页面中加入自己独特的内容#} <h1>这是base.html中盒子里显示的内容</h1> {% endblock %} </div>
{% block con %} {# index.html页面独特的内容#} {{ block.super }} <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> <h1>首页内容区域</h1> {% endblock %}
36.4 注意事项
-
如果你在模版中使用
{% extends %}
标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。 -
在base模版中设置越多的
{% block %}
标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。 -
如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个
{% block %}
中。 -
If you need to get the content of the block from the parent template, the
{{ block.super }}
variable will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. Data inserted using{{ block.super }}
will not be automatically escaped (see the next section), since it was already escaped, if necessary, in the parent template. -
为了更好的可读性,你也可以给你的
{% endblock %}
标签一个 名字 。例如:123{
%
block content
%
}
...
{
%
endblock content
%
}
在大型模版中,这个方法帮你清楚的看到哪一个
{% block %}
标签被关闭了。 - 不能在一个模版中定义多个相同名字的
block
标签。
三十七. ORM概念简介
参考:https://www.cnblogs.com/yuanchenqi/articles/8933283.html
37.1 Django的几大部分
url控制器,视图部分,模板部分,模型层
37.2 模型层用于和数据库打交道.ORM就是用来控制数据库的.
ORM直接把python中的类转换为(翻译)mysql语句,以后我们直接编写Python类就可以控制数据库了.
注意ORM只能对表做操作,不能对库做操作.
#sql中的表 #创建表: CREATE TABLE employee( id INT PRIMARY KEY auto_increment , name VARCHAR (20), gender BIT default 1, birthday DATA , department VARCHAR (20), salary DECIMAL (8,2) unsigned, ); #sql中的表纪录 #添加一条表纪录: INSERT employee (name,gender,birthday,salary,department) VALUES ("alex",1,"1985-12-12",8000,"保洁部"); #查询一条表纪录: SELECT * FROM employee WHERE age=24; #更新一条表纪录: UPDATE employee SET birthday="1989-10-24" WHERE id=1; #删除一条表纪录: DELETE FROM employee WHERE name="alex" #python的类 class Employee(models.Model): id=models.AutoField(primary_key=True) name=models.CharField(max_length=32) gender=models.BooleanField() birthday=models.DateField() department=models.CharField(max_length=32) salary=models.DecimalField(max_digits=8,decimal_places=2) #python的类对象 #添加一条表纪录: emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部") emp.save() #查询一条表纪录: Employee.objects.filter(age=24) #更新一条表纪录: Employee.objects.filter(id=1).update(birthday="1989-10-24") #删除一条表纪录: Employee.objects.filter(name="alex").delete()
三十八. 单表操作之生成表模型
参考:https://www.cnblogs.com/yuanchenqi/articles/8933283.html
38.1 使用pycharm创建app01和orm数据库的Book表
38.1.1 使用pycharm新建orm项目app01应用程序,使用cmd命令行创建orm数据库
如果下载Django速度很慢,参考:https://www.cnblogs.com/lldbyt/p/14286952.html
cmd下连接mysql,进入mysql命令提示符,输入创建orm库的命令,完成数据库创建.
create database orm;
38.1.2 在orm文件夹下的settings.py中修改DATABASES数据库设置如下:
DATABASES = { \'default\': { \'ENGINE\': \'django.db.backends.mysql\', \'NAME\': \'orm\', # 要连接的数据库, 连接前需要创建好 \'USER\': "root", # 连接数据库的用户名 "PASSWORD": "", # 连接数据库的密码 "HOST": "127.0.0.1", # 连接主机, 默认本机 "PORT": 3306 # 端口, 默认3306 } }
38.1.3 在orm文件夹下的__init__.py中添加pymysql模块的相关代码
import pymysql pymysql.version_info = (1, 4, 13, "final", 0) # 防止Django版本引起错误 pymysql.install_as_MySQLdb()
38.1.4 在app01应用程序目录下的models.py文件中建立表的结构
class Book(models.Model): id = models.AutoField(primary_key=True) # 自增, 主键 title = models.CharField(max_length=32) # 字符串,最大32 pub_date = models.DateField() # 日期 price = models.DecimalField(max_digits=8, decimal_places=2) # 123456.78 整数部分6位,小数部分2位,共计8位 publish = models.CharField(max_length=32)
38.1.5 在pycharm的Terminal命令提示窗口使用下面的两条命令,系统会依照models.py文件中建立表的结构创建出表,及其字段结构.
python manage.py makemigrations #依照models.py中的类创建出表, 创建迁移表
python manage.py migrate # 迁移
38.1.6 创建出的表,查看效果如下:
38.1.7 如果想在orm转换过程中把对应的sql语句输出在后台,需要在settings中进行如下配置:
LOGGING = { \'version\': 1, \'disable_existing_loggers\': False, \'handlers\': { \'console\':{ \'level\':\'DEBUG\', \'class\':\'logging.StreamHandler\', }, }, \'loggers\': { \'django.db.backends\': { \'handlers\': [\'console\'], \'propagate\': True, \'level\':\'DEBUG\', }, } }
三十九. 单表操作之添加记录
39.1 ORM主要用于增删改查数据记录
39.2 添加一条表记录的方法,相关代码写在视图层文件中.
两种方法,一种是实例化类,一种的类的.objects.create方法
from django.shortcuts import render, HttpResponse from app01.models import Book # 引入创建的表的类 # Create your views here. def index(request): # 添加表记录方法一 # book_obj = Book(id=1, title=\'python红宝书\', price=100, pub_date=\'2012-12-12\', publish=\'人民出版社\') # 实例化Book对象 # book_obj.save() # 实例化对象的方法时,只有save一下数据才会保存到数据库中 # 添加表记录方法二 book_obj = Book.objects.create(title=\'linux从入门到精通\', price=80, pub_date=\'2013-01-25\', publish=\'人民邮电出版社\') # 此种方法无需再用save print(book_obj.title) # .objects方法创建的,带有返回值,可以将值打印出来. print(book_obj.price) print(book_obj.pub_date) print(book_obj.publish) return HttpResponse("ok")
四十. 单表操作之查询API
参考:https://www.cnblogs.com/yuanchenqi/articles/8933283.html查询表记录
40.1 单表查询接口,需要了解清楚的
QuerySet数据类型,Django特有的数据类型,该类型有点像列表,可以想列表一样来处理QuerySet,支持索引,支持for循环
弄清楚查询表的接口的,1每个方法的返回值 2以及每个方法是谁来调用的
在app01下的models.py中的Book类中添加def __str__(self)方法用于print打印指定的数据
# app01下的models.py from django.db import models # Create your models here. class Book(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32) pub_date = models.DateField() price = models.DecimalField(max_digits=8, decimal_places=2) publish = models.CharField(max_length=32) def __str__(self): # 此处功能是,在执行print命令时显示对象的title return self.title
40.2 all方法: 返回值是一个queryset对象, 该方法由Book.objects管理器直接调用
all方法返回全部数据,all方法源代码:
from django.shortcuts import render, HttpResponse from app01.models import Book # Create your views here. def index(request): # 查询表记录, all方法 book_qs = Book.objects.all() print(book_qs) print(\'-\') # 遍历 for obj in book_qs: print(obj.title, obj.price) # 输出书名,和价格 print(\'-\') # 索引访问 print(book_qs[1].title) # 输出第二本书书名 return HttpResponse(\'OK\')
后台输出效果:
40.3 firsh, last方法,调用者是queryset对象, 返回值是一个model对象
# 查询记录,first, last方法, 调用者是queryset对象, 返回值是一个model对象 book_obj = Book.objects.all().first() book_obj = Book.objects.all()[0] # 和上面的等价 book_obj = Book.objects.last()
40.4 filter()筛选(过滤), 根据条件筛选, 返回值:queryset对象. 该方法与all()方法类似. filter()方法也可以调用first()方法
# 查询记录, filter() 筛选, 返回:queryset对象 # filter()与all()的差别就是多个筛选条件,别的都一样. book_list = Book.objects.filter(price=80) # 筛选出价格是80的所有记录 print(book_list) # [obj1, obj2, ...] book_obj = Book.objects.filter(price=80).first()
40.5 get() 有且只有一个查询结果时才有意义, 有多个结果或没有查到时会报错. 返回值:models对象
# 查询记录, get() 有且只有一个查询结果时才有意义, 返回值:model对象 book_obj = Book.objects.get(price=100) # book_obj = Book.objects.get(price=80) # 因为有两本书价格是80,所以浏览器上会显示报错信息 print(book_obj.title)
40.6 本节app01下的views.py完整代码,包含上面4个常用的查询方法
from django.shortcuts import render, HttpResponse from app01.models import Book # Create your views here. def index(request): # 插入记录,方法一 # book_obj = Book(title=\'python从入门到精通\', price=100, pub_date=\'2018-02-21\', publish=\'人民出版社\') # book_obj.save() # 插入记录,方法二 # book_obj = Book.objects.create(title=\'python自动化\', price=80, pub_date=\'2019-05-02\', publish=\'清华大学出版社\') # print(book_obj.title) # print(book_obj.publish) # 查询表记录, all方法 # book_qs = Book.objects.all() # print(book_qs) # print(\'-\') # # 遍历 # for obj in book_qs: # print(obj.title, obj.price) # 输出书名,和价格 # print(\'-\') # # 索引访问 # print(book_qs[1].title) # 输出第二本书书名 # 查询记录,first, last方法, 调用者是queryset对象, 返回值是一个model对象 # book_obj = Book.objects.all().first() # book_obj = Book.objects.all()[0] # 和上面的等价 # book_obj = Book.objects.last() # 查询记录, filter() 筛选, 返回:queryset对象 # filter()与all()的差别就是多个筛选条件,别的都一样. # book_list = Book.objects.filter(price=80) # 筛选出价格是80的所有记录 # print(book_list) # [obj1, obj2, ...] # book_obj = Book.objects.filter(price=80).first() # 查询记录, get() 有且只有一个查询结果时才有意义, 返回值:model对象 book_obj = Book.objects.get(price=100) # book_obj = Book.objects.get(price=80) # 因为有两本书价格是80,所以浏览器上会显示报错信息 print(book_obj.title) return HttpResponse(\'OK\')
四十一. 单表操作之查询API-2
41.1 exclude()排除,方法,相当于!=不等于, 所有符合条件的,然后取反.与filter()方法相反.注意:可以设置多个条件(多个条件是与的关系)
返回值是一个queryset对象, 该方法由Book.objects管理器直接调用
# exclude()排除 ,返回值是:queryset对象. 调用者:Book.objects管理器 book_list = Book.objects.exclude(title=\'php\', price=80) # 可以设置多个排除条件,多个条件是与关系,也就是同时满足.filter()方法也是可以设多个条件的. print(book_list)
41.2 order_by()排序, 调用者:queryset对象, 返回值:queryset对象
# order_by()排序 ret = Book.objects.all().order_by(\'id\') # 按ID升序排列 ret = Book.objects.all().order_by(\'-id\') # 条件前面加负号,按ID降序排列 ret = Book.objects.all().order_by(\'price\', \'id\') # 先按照price排序, 如何price一样,则按照id排序 print(ret)
41.3 count()计数,调用者:queryset对象,返回值:int类型
# count() ret = Book.objects.all().count() print(ret)
41.4 exist()判断表是否为空,调用者:queryset对象,返回值:布尔值True,False
# exist()判断是否为空 # ret = Book.objects.all() # 这种判断是否为空的方法太消耗资源(执行查询全部数据) ret = Book.objects.all().exists() # exists()方法只查询表中的一条记录,所以节约资源 if ret: print(\'ok\')
41.5 queryset对象支持链式,多个方法串起来
四十二. 单表操作之查询API-3
42.1 values()方法, 获取对象中每个元素的值,输出列表套字典,返回值:queryset类型, 调用者:queryset类型
ret = Book.objects.all().values("price") # values方法获取每个元素的值, 默认可以省略all(),写成Book.objects.values("price") print(ret) print(ret[0].get(\'price\')) # 输出:<QuerySet [{\'price\': Decimal(\'100.00\')}, {\'price\': Decimal(\'80.00\')}, {\'price\': Decimal(\'80.00\')}]> """ valuers()方法的工作原理 temp = [] for obj in Book.objects.all(): temp.append({ \'price\': obj.price }) :return temp """
42.2 values_list()方法,类似values()方法,输出的是列表套元组,返回值:queryset类型, 调用者:queryset类型
# values_list()方法 ret = Book.objects.all().values_list(\'price\') # 这里只演示了一个price字段, 可以有多个字段, values()方法也可以有多个字段 print(ret) # 输出:<QuerySet [(Decimal(\'100.00\'),), (Decimal(\'80.00\'),), (Decimal(\'80.00\'),)]>
42.3 distinct()去重, 配合values(),values_list()方法使用.返回值:queryset类型, 调用者:queryset类型
# distinct()去重, 返回值:queryset类型, 调用者:queryset类型 ret = Book.objects.all().values(\'price\').distinct() # 获得图书的价格,去除相同价格的. print(ret) # 输出:<QuerySet [{\'price\': Decimal(\'100.00\')}, {\'price\': Decimal(\'80.00\')}]>
42.4 reverse():对查询结果反向排序.返回值:queryset类型, 调用者:queryset类型
注意该方法需要配合order_by()方法使用,该方法是个鸡肋.这个方法只有在已经定义顺序的queryset上调用,才能对结果反向排序
# reverse()反向排序, 鸡肋方法,没什么鸟用. ret = Book.objects.all().filter(price=80).order_by(\'id\') ret2 = Book.objects.all().filter(price=80).order_by(\'id\').reverse() print(ret) print(ret2)
42.4 queryset对象支持链式,多个方法串起来
Book.objects.all().filter().order_by().filter().reverse().first()
四十三. 单表操作之模糊查询
大于小于的时候就需要用模糊查询, 用双下划线. 注意得到的仍然是queryset类型
43.1 参数price__gt=80表示价格大于80
参数price__lt=200表示价格小于200
ret = Book.objects.filter(price__gt=80, price__lt=200) # 查询价格大于80,且小于200的图书 print(ret)
43.2 参数title__startswith=\'py\'表示标题以py开头的
参数title__contains=\'h\'表示标题中包含字母h的
参数title__icontains=\'h\'表示标题中包含字母h的,不区分大写
ret = Book.objects.filter(title__startswith=\'py\') # 查询书名以py开头的所有书 ret = Book.objects.filter(title__contains=\'h\') # 查询书名中包含h的所有书 ret = Book.objects.filter(title__icontains=\'h\') # 查询书名中包含h的所有书,不区分大小写 print(ret)
43.4 参数price__in=[80, 100, 200]表示只要是价格等于80或100或200
参数price__range=[80, 100]查询价格在80到100之间的,包含80和100
ret = Book.objects.filter(price__in=[80, 100, 200]) #查询只要是价格等于80或100或200的 ret = Book.objects.filter(price__range=[80, 100]) #查询价格在80到100之间的,包含80和100 print(ret)
43.5 参数pub_date__year=2018查询日期是2018年的,月份和日随意, 只有日期类型有此参数
参数pub_date__month=5查询月份是5月的.
ret = Book.objects.filter(pub_date__year=2021, pub_date__month=1) # 查询年份是2018且月份是1月的 print(ret)
43.6 不止以上的这些个参数哦
四十四. 单表操作之删除与修改操作
44.1 delete()方法,删除记录. 返回值是删除的数据条数
delete()方法的调用对象是queryset对象, model对象也可以调用它.
# 删除记录 # ret = Book.objects.filter(price=100).delete() # queryset对象调用delete() # print(ret) # 返回值:(1, {\'app01.Book\': 1}) Book.objects.filter(price=80).first().delete() # model对象调用delete()
44.2 update()方法,修改记录. update()的调用者只能是queryset对象.
# 修改记录 Book.objects.filter(title=\'PHP\').update(title=\'php零基础入门\') # 注意,筛选出多少条记录,就会修改多少条.返回值是条数
四十七. 实例:创建简单图书管理系统
系统功能简介:可以新建图书,查看图书数据,删除图书,修改图书信息.
1. 建立数据库的表结构,即在models.py文件中设计Book表的结构,只要创建一个python Book类就可以了.
2. 迁移数据库,即通过pycharm底部Terminal的,输入两条迁移命令.这里数据库使用了Django的默认sqlite3数据库.迁移完成后能看到在根目录中创建了db.sqlite3的数据库文件,pycharm窗口的的右侧可以打开查看db.效果如下,book表结构清晰可见:
(如果数据库未能显示出创建的book表,则有可能是pycharm连接sqlite3的驱动未安装(右侧db下有暗红色线),点击右侧的db,点击数据库连接测试按钮,就会自动安装驱动.解决办法参考:https://www.cnblogs.com/lldbyt/p/14360650.html)
3. 由于本系统需要用到bootstrap等,所以需要对文件目录进行修改
3.1 新增静态目录(包)static,用于存放bootstrap, 在settings.py中增加static的设置
3.2 复制bootstrap到static目录中
4. 由于数据库中暂时没有数据,所有,下面,进行\'新增图书\'的设计.
4.1 建立\'新增图书的\'路由设置
4.2 建立\'新增图书\'的视图创建
4.3 创建\'新增图书\'的模板,注意在表单部分的最前面增加{% csrf_token %},防止跨域403错误
模板注意使用class="container"制作外框,class="row" class="col-md-6 col-lg-push-3(右移3)"建立模型
<input type="submit" class="btn btn-success(按钮造型) pull-right(移到右边)">
<a class="btn btn-info(按钮造型)" href="/addbook/">新增图书</a>(新增图书按钮,把a标签变成按钮)
5. 所有\'图书\'信息查看的设计,基本步骤和4一致.注意图书出版日期的显示{{ book.pub_date|date:\'Y-m-d\' }}
6. \'删除\'功能.注意.注意路由设计re_path(r\'books/(\d+)/delete/\', views.delbook),
注意删除按钮的html代码:<a href="books/{{ book.id }}/delete/" class="btn btn-danger">删除</a>
7. 修改功能,注意点和6一样
8.完整代码:
from django.db import models # Create your models here. class Book(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32) pub_date = models.DateField() price = models.DecimalField(max_digits=8, decimal_places=2) publish = models.CharField(max_length=32)
from django.contrib import admin from django.urls import path, re_path from app01 import views urlpatterns = [ path(\'admin/\', admin.site.urls), path(\'addbook/\', views.addbook), path(\'books/\', views.books), re_path(r\'books/(\d+)/delete/\', views.delbook), re_path(r\'books/(\d+)/edit/\', views.editbook), ]
from django.shortcuts import render, HttpResponse, redirect # redirect重定向 from app01.models import Book # Create your views here. def addbook(request): if request.method == "POST": title = request.POST.get(\'title\') pub_date = request.POST.get(\'pub_date\') price = request.POST.get(\'price\') publish = request.POST.get(\'publish\') Book.objects.create(title=title, pub_date=pub_date, price=price, publish=publish) return redirect(\'/books/\') # 重定向前的斜杠代表根目录 return render(request, \'app01/addbook.html\') def books(request): boos = Book.objects.all() return render(request, \'app01/books.html\', locals()) def delbook(request, id): Book.objects.filter(id=id).delete() return redirect(\'/books/\') # 重定向前的斜杠代表根目录 def editbook(request, id): book_obj = Book.objects.filter(id=id).first() if request.method == "POST": title = request.POST.get(\'title\') pub_date = request.POST.get(\'pub_date\') price = request.POST.get(\'price\') publish = request.POST.get(\'publish\') Book.objects.filter(id=id).update(title=title, pub_date=pub_date, price=price, publish=publish) return redirect(\'/books/\') # 重定向前的斜杠代表根目录 return render(request, \'app01/editbook.html\', {\'book_obj\': book_obj})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>新增图书</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <style type="text/css"> .container{ margin-top: 100px; } .btn{ margin-top: 10px; } </style> </head> <body> <h2>新增图书</h2> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-push-3"> <form action="" method="post"> {% csrf_token %} <div> <label>图书名称</label> <input type="text" class="form-control" name="title"> </div> <div> <label>出版日期</label> <input type="date" class="form-control" name="pub_date"> </div> <div> <label>图书价格</label> <input type="text" class="form-control" name="price"> </div> <div> <label>出版社</label> <input type="text" class="form-control" name="publish"> </div> <div> <input type="submit" class="btn btn-success pull-right"> </div> </form> </div> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>所有图书</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <style type="text/css"> .container{ margin-top: 100px; } .btn{ margin-top: 10px; } </style> </head> <body> <h2>所有图书</h2> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-push-3"> <div> <a class="btn btn-info" href="/addbook/">新增图书</a> </div> <table class="table"> <thead> <tr> <td>序号</td> <td>图书名称</td> <td>出版日期</td> <td>价格</td> <td>出版社</td> <td></td> <td></td> </tr> </thead> <tbody> {% for book in boos %} <tr> <td>{{ book.id }}</td> <td>{{ book.title }}</td> <td>{{ book.pub_date|date:\'Y-m-d\' }}</td> <td>{{ book.price }}</td> <td>{{ book.publish}}</td> <td><a href="books/{{ book.id }}/delete/" class="btn btn-danger">删除</a></td> <td><a href="books/{{ book.id }}/edit/" class="btn btn-success">修改</a></td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>修改图书信息</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <style type="text/css"> .container{ margin-top: 100px; } .btn{ margin-top: 10px; } </style> </head> <body> <h2>修改图书信息</h2> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-push-3"> <form action="" method="post"> {% csrf_token %} <div> <label>图书名称</label> <input type="text" class="form-control" name="title" value="{{ book_obj.title }}"> </div> <div> <label>出版日期</label> <input type="date" class="form-control" name="pub_date" value="{{ book_obj.pub_date|date:\'Y-m-d\' }}"> </div> <div> <label>图书价格</label> <input type="text" class="form-control" name="price" value="{{ book_obj.price }}"> </div> <div> <label>出版社</label> <input type="text" class="form-control" name="publish" value="{{ book_obj.publish }}"> </div> <div> <input type="submit" class="btn btn-success pull-right"> </div> </form> </div> </div> </div> </body> </html>
完整目录结构:
五十一. 数据库表关系之一对多
参考:https://www.cnblogs.com/yuanchenqi/articles/8963244.html Django模型层(2)
51.1 多表,为啥要用多个表?
避免存储大量重复数据.把大表拆成多个表.
51.2 一对多表,一个出版社对应多本书.
51.3 总结:一旦确定表关系是一对多:创建关联字段,上图的publish_id是关联字段,创建关联字段要在多的表中
五十二. 数据库表关系之多对多
参考:https://www.cnblogs.com/yuanchenqi/articles/8963244.html Django模型层(2)
52.1 多对多表,一本书对应多个作者,一个作者对应多本书.
52.2 总结:一旦确定表关系是多对多:要创建第三张关系表,如上图Book2Author
五十三. 数据库表关系之一对一
53.1 一对一表,本质是把一张表中的几个字段抽离出来组建一个新表.
一对多与一对一的差别.一对一的关联字段加上unique约束.关联字段放在哪张表都可以.
53.2 总结: 一旦确定是一对一的关系: 在两张表中的任意一张表中建立关联字段+unique
五十四. 数据库表关系之关联字段与外键约束
建关联关系是为了查询,建约束关系是为了维护数据的完整.
约束关系存在的目的, 删除被关联表的一条数据,如果两表只有关联字段,无约束关系,则会直接删除.如果有约束关系,则无法直接删除,会有提示,无法删除.
如上表,如果删除publish表中的第1条数据,则再查下时将无法查出php书籍的出版社信息.所以增加了约束关系,使得操作者无法随意删除.
若只有关联关系而无约束关系,就可以直接删除.
五十五. 数据库表关系之sql创建关联表
上图的publsish_id字段是两表的关联字段,上图的foreign key就是建立约束关系
五十六. ORM生成表模型
参考:https://www.cnblogs.com/yuanchenqi/articles/8963244.html 多表操模型
下转本课笔记第二部分: 第六模块: WEB框架开发之Django框架开发学习笔记2
for...empty
创建任意 .py 文件,如:my_tags.py
objects.create