django是目前流行很广泛的python-web框架,提供一站式的web开发支持,重量级框架,为python-web项目的开发提供了极大的便利。
B/S结构(Browser/Server,浏览器/服务器模式),web项目主流构建结构。
由于python的第三方类库很多样化,而且不同的python项目所需要的第三方依赖库不尽相同,所以如果想在单台主机上运行不同的项目,需要开辟虚拟环境
在当前主机上的,一个独立于本地环境的一个python运行环境
python3后自带了虚拟环境工具pyvenv。python3的类库中增加了一个venv模块
- python -m venv env9 #创建虚拟环境,在当前目录下 (注意虚拟环境的目录中不能有中文)
激活虚拟环境,才可使用虚拟环境
在虚拟环境目录下的Scrips下执行 :
activate
安装依赖的不同版本
pip install "pillow > 4.3"
pip install "pillow==5"
pip install "Django==2.0.2"
pip install django
关闭虚拟环境
在虚拟环境下执行
deactivate
即可
linux中指令(了解)
激活:
source evn9/bin/activate
关闭:.evn99/bin/deactive
虚拟环境中,安装django:
pip install "django==2.0.2"
django内置了一款服务器,用于开发过程的web项目测试使用
python manage.py runserver
#即可开启服务器设置服务器的ip和port
ALLOWED_HOSTS = ['*']
python manage.py runserver 192.168.0.3:8989
开启服务器,并设置服务器的ip和端口http://192.168.0.3:8989
即可访问服务器,并且此时服务器可以在整个网络中提供服务!!project的开发过程中,会有模块化开发,如电商系统中的用户模块,订单模块,OA系统中的财务模块,人力模块等。每个模块都是project的一个APP,APP内是相关模块的功能集合,包含所有相关的功能及完整的实现。将一个project划分为多个APP是一个解耦的过程,整个项目结构松散,利于维护。
在虚拟环境下执行项目目录下的manage.py文件:
python manage.py startapp first_app
即可在project中构建一个APP:在当前目录下生成一个目录,目录名:“first_app”
APP的目录结构如下
注意:每个App都必须安装在project中
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'first_app', #安装自己的app
]
在app的views.py中定义视图,每个视图都是一个函数
1.接收http请求,给出响应
2.调度Model,组织数据 (忽略)
3.转发Template(V),渲染数据,得到一个承载着model查询出的数据的网页(html)(忽略)
#View函数,可以接收、处理客户端请求
def hello_world(request):
print("hello world")
return HttpResponse("hey this is 服务器") #响应请求
在urls.py文件中为视图指定访问路径
from django.contrib import admin
from django.urls import path
from first_app.views import hello_world #导入自己的view
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/',hello_world) #为自己的view,设置访问路径
]
##则,http://localhost:8000/hello/可以访问到自己的view
View而接受访问,并会给以响应
http://localhost:8000/hello/
http://192.168.0.3:8989/hello/
template:动态生成html,进而可以更好的响应client(浏览器)
模板文件就是一个html格式的文件
在settings.py文件中告知django,模板文件的所在目录
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #模板目录:project根目录下的templates目录
'APP_DIRS': True,#每个app下的templates目录也是模板目录
....
}
文件位置:在app下创建templates目录,或使用project下的templates目录均可
模板文件中的所有内容,将会成为响应内容,传输到客户端
ht1.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<p>hello <span style="color:red">worldspan>p>
body>
html>
通过模板解析获得响应内容
from django.shortcuts import render,HttpResponse,loader
from first_app.models import User
def xxx(request):
temp = loader.get_template("ht1.html") #构建模板对象,指向ht1.html
content = temp.render(request=request) #模板解析,返回响应内容
return HttpResponse(content) #响应
快捷方式
return render(request, "ht1.html") #响应
注意:此时的页面,只是静态页面;要做动态页面需要数据的介入
数据格式:Dict
示例数据:{“name”:“臧红久”}
#1.从数据库中获取数据
conn = MySQLdb.connect(...)
cursor = conn.cursor(DictCursor)
cursor.execute("select id,name from t_user where id=1")
user = cursor.fetchone()
#2.获取模板
temp = loader.get_template("ht1.html") #构建模板对象,指向ht1.html
#3.将数据传入模板
content2 = temp.render({"name": user.get("name")},request)
#4.响应
return HttpResponse(content2)
#响应并传递数据 的快捷方式
return render(request, "ht1.html", {"name": "臧红久"})
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
{{ name }}
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<p>hello <span style="color:red"> {{ name }} span>p>
body>
html>
如此,则模板中除了静态内容外,还有了动态内容 {{name}}
1> 项目中的 css文件 ,js文件,image文件,属于静态资源
2> 所谓静态,是指这些文件的内容是不变的
3> 和View函数不同,静态资源没有自己的访问路径配置,那么该如何访问静态资源?
settings.py中
STATIC_URL = '/static/'
则访问路径以“/static/”为前缀,则会被认为是要访问静态资源。(否则请求只会去访问View函数)
<link rel="stylesheet" href="/static/aaa.css"> #访问aaa.css文件
<img src="/static/bbb.jpg" width="200px"/> #访问bbb.jpg文件
<script src="/static/ccc.js">script> #访问ccc.js文件
那么问题是:django到哪里为请求去寻找静态资源?
"已安装"的APP中的static目录是默认寻找根路径
<link rel="stylesheet" href="/static/aaa.css"> #到静态资源根路径下查找 aaa.css文件
<img src="/static/bbb.jpg" width="200px"/> #到静态资源根路径下查找 bbb.jpg文件
<script src="/static/c133/ccc.js">script> #到静态资源根路径下查找 c133目录,从c133目录下找 ccc.js
注意:/static/并不参与静态资源查找,只是表名该请求是要找静态资源
/static/bbb.jpg
:/static/ ==要找静态资源
bbb.jpg ==要找的静态资源是 bbb.jpg
然后找到静态资源根路径,去查找bbb.jpg
如果多个APP下有同名静态文件,会如何?
解决方案:每个APP的静态资源都有自己的独立目录即可
: 在每个APP的static目录下都建立一个命名独立的目录
新增自己的静态文件根目录
除默认的静态文件根目录外,增加几个静态文件的根目录
settings.py
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static9'),
"E:\\view"]
当然,在自己的新增的目录中也要为APP开辟独立的目录,防止APP之间静态资源命名名冲突
补充:静态资源访问路径 软编码
问题:
如果settings.py中 STATIC_URL = '/static/'发生改变,将有较大的维护成本
解决方案:{%load static %} 加载static标签
自动补全前边的 STATIC_URL
第三方包:virtualenv 和 virtualenvwrapper-win
pip install virtualenv #安装virtualenv包,功能和 venv一致
pip install virtualenvwrapper-win #管理虚拟环境的包(linux下是 : pip install virtualenvwrapper)
#安装之后,配置环境变量
WORKON_HOME=E:\zhj\venv99 #此目录用来统一存放所有创建的虚拟环境,默认目录是:c:\users\用户名=用户目录
#创建虚拟环境
mkvirtualenv env9 #即可在WORKON_HOME目录下创建名为env9的虚拟环境,且在创建后,自动激活
rmvirtualenv evn9 #删除虚拟环境
#激活虚拟环境
workon evn9 #如此即可
#退出虚拟环境
deactivate
#查看虚拟环境列表
workon
Django中提供了View组件用来充当MVC中的C-controller
主要职责:
- 接收请求
- 调度Model
- 转发Template(显示数据相关逻辑)
1. 定义在App下的views.py中,每个View都是一个函数,函数中需要一个参数,用来接收请求对象
def hello(request):#request=请求对象
print("abc")
2. 为每个View函数定义响应
注意:每个View必须有响应,即必须返回一个HttpResponse
from django.shortcuts import render,HttpResponse
def hello(request):
print("abc")
return HttpResponse("hilo~~~") #接收客户端请求后,给以响应
3. 为每个View函数定义访问路径
urls.py
from django.contrib import admin
from django.urls import path
from view_test_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/',views.hello) #添加自己的view配置
]
4. 启动服务器
django内置了一个供开发使用的 web-服务器,虽然不适合生产环境,但用户开发测试,特别便利
python mange.py runserver
#默认ip和端口 localhost:8000
python manage.py runserver 192.168.0.3:8989
#修改了默认的ip和port注意:修改了ip和port后,需要到settings.py中增加一个设置:ALLOWED_HOSTS = ["*"]
5. 访问View
http://localhost:8000/hello/
http://192.168.0.3:8989/hello/
from django.contrib import admin
from django.urls import path
from view_test_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/',views.xxx) # http://localhost:8000/hello/
path('hello/world/',views.xxx) # http://localhost:8000/hello/world/
path('hello/world/a/b/',views.xxx) # http://localhost:8000/hello/world/a/b/
]
re_path
re_path(r'hello/\d{3}/',views.hello2) #http://127.0.0.1:8000/hello/125/
re_path(r'hello/\d{2,5}/\w+/',views.hello2) #http://127.0.0.1:8000/hello/45343/ab1_2/
#http://127.0.0.1:8000/zzzzhello/45343/ab1_2/zzzz/zzzz
re_path(r'hello/[a-d]*/$',views.hello2) #http://127.0.0.1:8000/hello/abccdda/
#http://127.0.0.1:8000/zzzzhello/abccdda/
#建议写法:通过^和$明确限定起始和结尾
re_path(r'^hello/[a-d]*/$',views.hello2) #http://127.0.0.1:8000/hello/abccdda/
**注意:在使用正则时,^ 和 $可以保证精确匹配 **
一个Project会有很多App,每个App会有很多View函数,如果都在一个urls.py中配置,则项目的管理成本将急剧上升
每个App不仅逻辑独立,也希望在url-pattern的配置上也相互隔离
便于管理 维护(各司其职,职责单一)
1.在App目录下,新建一个urls.py,在其中配置App内的每个view
from django.urls import path,re_path from view_test_app import views urlpatterns=[ re_path(r'^hello1/(?P
\d{3})/$' , views.hello), re_path(r'^hello2/(?P\d{2,5})/(?P , views.hello2), path('hello3/', views.hello3), ] #如上三个View的访问路径前要加上全局urls.py中的路径“test1/”: # http://localhost:8000/test1/hello1/123 # http://localhost:8000/test1/hello2/123/zhj # http://localhost:8000/test1/hello3/\w+)/$' 2.在全局的urls.py中使用 include 包含各个app的urls文件
from django.urls import path,include urlpatterns = [ ... path('test1/',include("view_test_app.urls"))#导入外部的urls模块 #如上的“test1”将成为 “view_test_app.urls”每一个path路径的前缀 ]
3.如此:每个App都在全局的urls.py中有一个自己的前缀;在自己的urls.py中定义自己的path;
实现了对于访问路径的高效管理
1> 诸多view的配置,分发到每个app中各自管理,秩序不乱
2> 每个app在全局的urls中登记,设置访问前缀,利于维护访问路径名
3> 每个app的访问前缀,可以描述具体的逻辑,使得路径有明确的逻辑区分
4.进一步管理:如果在一个App的众多path中,又有关系逻辑关联紧密的可以进一步管理
from django.urls import path,re_path,include from . import views urlpatterns=[ path(...), ...., path('user/vip/',include([ path('a/',views.hello), #http://.../test1/user/vip/a/ path('b/',views.hello2), #http://.../test1/user/vip/b/ path('c/d/',views.hello3), #http://.../test1/user/vip/c/d/ ]) ), path(...), ... ]
5.至此:View的url配置则有了管理方案:
1> 每个App定义自己的urls.py,其中定义自己的url-conf
2> Project的全局urls.py中引入各个App的url-conf,并定义访问前缀,区分、隔离各个App
3> App内部的url-conf可以根据逻辑进一步区分、隔离
#http://192.168.0.3:9000/test1/user/vip/a/
<a href="/test1/user/vip/a/">go~</a>
<form action="/test1/user/vip/a/" method="get/post">
<input type="text"/>
...
<input type="submit" value="提交"/>
</form>
http请求最常用的两种方式:GET / POST
- 超链接发送的是get方式的http请求
- 表单可以发送get方式的请求,也可以post方式的http请求 (默认发送get方式的请求)
实用场景:1> 需要用户输入信息使用Form,否则可以选择超链接。
2>form中如果不涉及隐私数据,且用户输入项较少,选择get请求方式;
反之选择post请求方式。
客户端(浏览器)访问服务器时,需要向服务器传递数据(请求参数),以让服务器可以区别处理
比如:
1.登录时,请求中要携带用户名和密码两个参数,服务器才知道哪个用户要登录
2.删除时,请求中要携带一个id参数,服务器才知道要删除哪个数据
url?key=value&key=value…
key是参数名,value是参数值
其中【?】是请求路径和请求参数的分隔
其中【&】是多个请求参数之间的分隔
<a href="/test1/user/vip/a/?age=18">xxx</a>
<a href="/test1/user/vip/a/?id=1&name=zhj">xxx</a>
表单中的控件,name属性值为参数名,value属性值为参数值
#param: age=xx 和 name=xx 和 gender 和 language
<form action="/test1/user/vip/a/">
<input type="text" name="age"/>
<input type="text" name="name"/>
<input type="radio" name="gender" value="male"/>男<br/>
<input type="radio" name="gender" value="female"/>女<br/>
<select name="language">
<option value="en">英文option>
<option value="zh">中文option>
<option value="fr">法文option>
select>
<input type="submit" value="提交"/>
form>
1.超链接:http://localhost:8000/test1/user/vip/a/?age=18&name=zhj
2.method="get"的form:
<form action="http://localhost:8000/test1/user/vip/a/" method="get">
<input type="text" name="age"/>
<input type="text" name="name"/>
<input type="radio" name="gender" value="male"/>男
<input type="radio" name="gender" value="female"/>女
<select name="language">
<option value="en">英文option>
<option value="zh">中文option>
<option value="fr">法文option>
select>
<input type="submit" value="提交"/>
form>
#接收请求参数 request.GET['参数名'] 或 request.GET.get("参数名") 均可
def test(request):
request.GET['age'] # 18(不建议,容易抛出异常)
request.GET.get('age') # 18 注意参数类型!
request.GET.get("name") #zhj 注意参数类型!
request.GET.get("gender") # 选中的radio中的value
request.GET.get("language") #选中的option的value
<form action="http://localhost:8000/test1/user/vip/a/" method="post">
{% csrf_token %}
<input type="text" name="age"/>
<input type="text" name="name"/>
<input type="radio" name="gender" value="male"/>男
<input type="radio" name="gender" value="female"/>女
<select name="language">
<option value="en">英文option>
<option value="zh">中文option>
<option value="fr">法文option>
select>
<input type="submit" value="提交"/>
form>
#接收请求参数 request.POST['参数名'] request.POST.get("参数名")
def test(request):
request.POST['age'] # 18
request.POST.get('age') # 18
request.POST.get("name") #zhj
request.POST.get("gender") # 选中的radio中的value
request.POST.get("language") #选中的option的value
注意
request.GET[‘age’] 如果没有值则报错
request.GET.get(‘age’) 如果没有值返回None
POST同样!
#age有两个值
http://localhost:8000/test1/user/vip/a/?age=18&age=19name=zhj
#hobby会有多个值
<form action="http://localhost:8000/test1/user/vip/a/" method="post">
<input type="checkbox" name="hobby" value="football"/>足球
<input type="checkbox" name="hobby" value="basketball"/>篮球
<input type="checkbox" name="hobby" value="volleyball"/>排球
<input type="submit" value="提交"/>
form>
如上,请求参数中同一个key上有多个值,View中如何接收?如下代码是错误的!
def test(request):
age = request.GET.get("age") #能否接收多个值,只能收最后一个值
hobby = request.POST.get("hobby")#能否接收多个值,只能收最后一个值
应该用getlist,才可以接收一个参数上的多个值!
def test(request):
age = request.GET.getlist("age") #接收多个值 返回list = ["18","19"]
hobby = request.POST.getlist("hobby")#接收多个值 list = ["footbal","basketball"]
# :名称为“age”的路径,此种路径可以匹配任何内容
# http://localhost:8000/hello/18/ # http://localhost:8000/hello/19/
path('hello//' ,views.abc)
# 同样
# http://localhost:8000/hello/18/zhj/
path('hello///' ,views.def)
def abc(request,age):#age 接收路径捕获的值
....
def def(request,age,name):#age和name分别接收 捕获的值
...
re_path(r'^hello1/(?P\d{3})/$' , hello1) #定义名为age的正则命名路径
re_path(r'^hello2/(?P\d{2,5})/(?P\w+)/$' , hello2)#定义名为age和name的正则命名路径
def hello(req,age):# age 接收捕获组的值
print("hello world",a)
return HttpResponse("hey~~")
def hello2(req,age,name):# age 接收捕获组的值 name 接收捕获组的值
print("hello world",a,b)
return HttpResponse("hey~~")
注意:如果定义了命名路径,则View中必须接收参数
转换器
非正则命名路径中可以使用转换器,约束每个路径捕获数据的类型
- int:接收整数
- str:字符串(了解)
- slug:接收[数字,字母,下划线,中划线]四种字符(了解)
如果路径上的值不符合转换器类型,则访问不到对应view函数 (404页面)
path('hello////' ,hello)
#http://ip:port/hello/12/abc/asdf123_sdf-/
def hello(req,age,name,nick):#注意age此时不再是str,而是int
print("hello world",age,name,nick)
return HttpResponse("hey~~")
View中参数request,类型为HttpRequest
GET = QueryDict {“key”:[xx,xx],“key2”:[xx,xx,xx]
url?id=1&name=zhj&hobby=ft&hobby=bt
GET={“id”:[“1”],“name”:[“zhj”],“hobby”:[“ft”,“bt”]}
GET.get(“id”) 返回列表中的最后一个值
GET.getlist(“id”) 返回整个列表
POST = QueryDict
method = 请求方式
path = 请求路径
@require_http_methods(["GET"]) #此View,只接收Get请求
def test1(request):
....
@require_http_methods(["GET","POST"]) #此View,接收Get请求和POST请求
def test1(request):
....
响应对象,负责给浏览器客户端响应内容
from django.shortcuts import HttpResponse
return HttpResonse("hello world")#响应内容为:“hello world”
如果要显示更复杂的内容,或者说,实际的开发过程,响应是需要使用模板的
即把要响应的内容定义在模板中,然后将模板处理后的内容响应给浏览器客户端
temp = loader.get_template("test.html")#加载模板文件
content = temp.render();#模板渲染,返回渲染后的内容
return HttpResponse(content)#将内容响应给client
建议使用更简易的:
return render(request, "test.html")
以上是响应的最基本使用,接下来要系统分析,响应的处理
响应要更好的处理,势必会用到跳转,如图:
不同组件间的功能衔接,即,跳转
需要跳转的情景有:
一个View接到请求后,请求需要的逻辑处理完后,最后都需要给出响应。
而,响应内容的生成一般不由View负责,Template是专业的响应内容生成者,则此时需要
View和Template之间做衔接、跳转
不同的功能由不同的View负责,则当功能之间需要衔接时,就会涉及
View和View间做衔接、跳转
跳转方式有
转发
转发发生在一个请求内的跳转,用于View到Template间的跳转,
render(request, "test.html")
即为在一个请求内由View转发跳转到Template重定向
重定向发生在不同请求之间,用于View之间的跳转
redirect("另外的View的请求路径:/a/b/c/")
def hello(req):
print("hello world")
return redirect("/test/hello2/") # 重定向到View
#return HttpResponseRedirect("/test/hello2/")
def hello2(req):
print("hello world2")
return render(req,"test.html") # 转发到Template
重定向和转发区别:
- 转发用于View和Template,重定向用于View和View
- 转发是一次请求内的跳转(地址栏不变),重定向会自动触发第二次请求(地址栏会改变)
- 重定向可以访问其他域的位置
0.技术点练习,熟练
1.ems-regist
2.ems-login
搭建ORM,简化python和数据库的通信
mysqlclient、pymysql… conn cursor 繁琐
当和数据库通信时,直接使用python操作数据库,通信编码过程繁琐。
ORM是解决此问题的主要流行方案。
ORM – Object Relational Mapping 对象关系映射
- python对象 关系表 搭建映射
- 对象属性 表中的列 搭建映射
- 使得对象和关系表可以直接通信,极大简化通信过程
class User:
def __init__(self,id,name,age):
self.id=id
self.name=name
self.age=age
t_user
id name age
1 zs 18 ==> User(1,“zs”,18)
2 ls 19 ==> User(2,“ls” ,19)
User.all()===[User,user,user,user]
Django的Model章节,也不例外的践行了ORM,为model层的开发提供可极大便利
Django中的ORM的搭建,核心是model类,所以Model章节的内容,将围绕model类展开
安装数据库驱动:pip install mysqlclient-1.3.13-cp35-cp35m-win_amd64.whl
设置数据库连接参数(settings.py)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', #连接类型
'NAME': 'db9', #数据库名称
'USER':'root', #用户名
'PASSWORD':'222222', #密码
'HOST':'localhost', #ip
'PORT':'3306' # 端口
}
}
#注意 NAME USER HOST 等,必须大写
安装app;设置开发模式;设置时区(细节补充配置)
INSTALLED_APPS = [
'django.contrib.admin',
.....
'my_app' #安装自己的app
]
DEBUG = True #开发模式,可以看到更多日志
#时区设置
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
定义model类
class User(models.Model):
name = models.CharField(max_length=20) # varchar(20)
age = models.IntegerField() # int
birth = models.DateTimeField() # datetime
生成移植文件
会扫描已安装app中的Model类
会在app目录下的migrations目录中生成一些文件,用户数据库移植
python manage.py makemigration
执行移植文件
通过移植文件中的信息,在数据库中生成数据表
python manage.py migrate
可以生成的表结构如下:
CREATE TABLE "my_app_user" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(20) NOT NULL,
"age" integer NOT NULL,
"birth" datetime NOT NULL
);
测试Model操作
1> 在terminal中:python manage.py shell #进入测试环境
2> 导入自己的Model类:from my_app.models import User
3> User.objects.all() #查询所有用户信息
4> User.objects.get(pk=1).delete() #删除id为1的用户
如上,在操作数据时,完全没有接触数据库先关的任何内容,只是通过操作python的Object,就对数据库完成了对应操作,则至此,ORM搭建完毕,通信过程变得直接,简便
补充:
1> 如果修改了model,则可以再次“生成移植文件”然后“执行移植文件”重新同步数据库即可
AutoField 一般不用直接使用,Model中默认的主键类型
Django默认给每个Model一个主键字段(如果没有自己定义的话)
id = models.AutoField(primary_key=True)
IntegerField 4字节 int
BigIntegerField 8字节 bigint
SmallIntegerField 2字节 smallint
FloatField 底层采用float类型 double
DecimalField(max_digits=5,decimal_places=2) 底层采用Decimal类型 decimal
必填:max_digits=5 #共有几位数
必填:decimal_places=2#其中小数位占几位
CharField(max_length=20) varchar(20)
max_length是必填属性
TextField longtext
BooleanField 接收“True/False” tinyint
NullBooleanField 接收“True/False/None” tinyint
DateTimeField datetime
DateField date
可选:auto_now=true 可做修改时间记录
可选:auto_now_add=True 可做首次添加时间
注意:以上两属性之一指定后,该字段不允许被编辑
TimeField time
FileField varchar
ImageField varchar
ForeignKey(to=关系对方的类或类名或‘self’,on_delete=级联选项)
OneToOneFiled(to=关系对方的类或类名或‘self’,on_delete=级联选项)
ManyToManyFiled(to=关系对方的类或类名或‘self’)
null 默认False,不能为空
name = models.CharField(max_length=20,null=True) #可为空
default 定义默认值
name = models.CharField(max_length=20,default="zhj9")
age = models.IntegerField(default=18)
birth = models.DateTimeField(default="2018-12-12")
默认值不会作用在数据库上,而是django自己的约定,通过Model.save()时,会使用默认值
primary_key
id = models.AutoFiled(primary_key=True) #默认追加,不用自己定义
id = models.CharField(primary_key=True,max_length=32)#如果需要字符串类型的ID可以自己定义
unique 列是否唯一
name = models.CharField(max_length=20,unique=True)
db_column 自定义列名,默认和field同名
name = models.CharField(max_length=20,db_column="name9")
db_index 是否在列上建立索引
name = models.CharField(max_length=20,db_index=True)
db_table 设置表名
unique_together 设置联合唯一约束
class MyModel(models.Model):
salary = models.DecimalField(...)
.....
age = models.xxx
name = models.xxx
class Meta:
#unique_together = (("salary", "salary2"),("age","age2","age3"))
unique_together = ("salary", "salary2")
db_table="my_user"
补充:ORM搭建方式
1> 建立Model,然后通过移植创建表;
2> 建立Model,手动创建表;
在Model上声明要映射的表名;
在Model的每个Field中声明要映射的列名。
增加
user = User(name="zhj",age=18,birth=datetime.now())
user2 = User(name="zhj",age=18,birth='2018-12-12 12:12:40')
user.save()
user2.save()
或者
user = User.objects.create(name="zzz",age=19,birth=datetime.now()) #创建对象并保存数据,一步完成
##注意,save()没有返回值,create()返回保存的对象
删除
User.objects.get(pk=3).delete()
User.objects.all().delete()
更新
user = User.objects.get(pk=3)
user.name="new_name"
user.save()
- Model继承关系:User(Model(ModelBase))
- ModelBase为User指派一个属性:objects=Manager()
- objects,存储一个Manager对象,用来支持查询功能。
- Manager对象中查询功能的相关方法是由QuerySet提供实现
如下所有查询方法都是QuerySet实现的,只不过每个Model中都会有一个Manager,Manager接收到如下方法的调用时,会去调用QuerySet的方法,最终实现数据查询。
所以QuerySet这个类 是查询动作的核心支撑
Manager的任务是管理QuerySet,即,在需要的时候去调用QuerySet的对应方法
all() - QuerySet
User.objects.all() #返回QuerySet,其中是所有User的数据
#, ,...]>
get() - 对象
#id=1
User.objects.get(pk=1) #返回查到的数据,没有数据或数据多于1条 报错
#name='zhj' 而且 age=18的数据
User.objects.get(name="zhj",age=18)#返回查到的数据,没有数据或数据多于1条 报错
#id=1
User.objects.filter(pk=1) #返回一个QuerySet,其中是满足条件的数据
User.objects.filter(name='zhj',age=18) #可以有多条数据,如果没有数据,就返回一个空白的QuerySet
exclude() -QuerySet (了解)
User.objects.exclude(pk=1) #和filter相反,取不满足条件的数据,返回QuerySet
User.objects.exclude(gender=True)# True/False 或者 1/0 和bool的filed比较
first() - 对象 (了解)
User.objects.first() #获取QuerySet中的第一个元素,返回一个对象或None
User.objects.all().first()
User.objects.filter(xxxx).first()
last() - 对象 (了解)
User.objects.last() #获取QuerySet中的最后一个元素,返回一个对象或None
User.objects.filter(pk=1,name="zhj").last()
exists() - Boolean (了解)
User.objects.exists() #User对应的表中是否有数据,判断数据表是否为空。返回True/False
count()
User.objects.count() #数据数量
User.objects.all().count() #数据数量 和上面等价
User.objects.filter(age=18).count()
order_by() - QuerySet
User.objects.all().order_by("pk") #根据id升序,返回QuerySet
User.objects.all().order_by("age")
User.objects.all().order_by("-age")#根据age 降序排列
User.objects.all().order_by("age","name")
User.objects.all().order_by("age","-name") #根据age升序,age相同的根据name倒序
QuerySet使用补充
list(User.objects.all()) #转成list
for e in User.objects.all(): #可以直接遍历
print(e.age)#e=User对象
print(e.name)
if User.objects.filter(name="zhj"):#直接用于判断,QuerySet中没有数据时作为False使用
print("至少有一个User数据")
User.objects.all().query #query属性可以返回当前查询使用的sql语句
all() , filter() , exclude() , order_by() 都会返回QuerySet对象,QuerySet对象可以接受限制操作
User.objects.all()[0] #返回QuerySet中第一个对象
User.objects.filter(pk=1,name="zhj")[2] #返回QuerySet中第3个对象
uname="zhj" #可以使用变量在查询中
User.objects.filter(pk=1,name=uname)[2] #返回QuerySet中第3个对象
User.objects.exclude(pk=1,name="zhj")[1:3] #返回包含第[2,4)个对象(前闭后开)的新的QuerySet (子集)
User.objects.exclude(pk=1,name="zhj")[:3] # 等价于[0:3]
User.objects.exclude(pk=1,name="zhj")[2:] # 返回第3个到最后一个对象的新的QuerySet
在众多查询方法中适合做条件查询的有:get()、filter()
get(pk=1,name=“zhj”) 如此只是等值查询,远不能满足查询需求
filter(pk=1,name=‘zhj’)
比较
不支持 "> < >= <= "这些符号,替换为关键字 lt、gt、lte、gte
lt==less than
gt==greater than
lte==less than equal
User.objects.filter(pk__lt=10) #id小于10
User.objects.get(pk__gt=100,age__lte=18) #id大于100,age小于等于18
#日期比较也可以
User.objects.exclude(birth__gte="2018-5-22 12:12:12") #yyyy-mm-dd HH:MM:SS格式
User.objects.filter(birth__gt=datetime.now())
dd = "2018-5-22 12:12:12"#使用变量
User.objects.exclude(birth__gte=dd,name="xxx") #使用变量
模糊查询
contains、icontains (iignore忽略大小写)
startswith、istartswith
endswith、iendswith
User.objects.filter(name__contains="s") #name中包含"s"
User.objects.filter(name__icontains="s") #name中包含"s"或"S" 忽略大小写
User.objects.filter(name__startswith="s")
User.objects.filter(name__istartswith="s")# name是以"s"或"S"开始 忽略大小写
User.objects.filter(name__endswith="s")
User.objects.filter(name__iendswith="s")# name是以"s"或"S"结尾 忽略大小写
范围
in
range
User.objects.filter(name__in=("zhj","zz")) # where name in("zhj","zz")
User.objects.filter(age__in=(18,19,22))
User.objects.filter(age__range=(18,20)) # where age between 18 and 20 [18,20]
User.objects.filter(birth__range=('2018-05-10','2018-06-14'))
空值:null
isnull
User.objects.filter(age__isnull=True)
日期(了解)
year
month 1-12
day
hour
minute
second
week 一年的第几周 1-52 or 53
week_day 周几 周日=1 周六=7
User.objects.filter(birth__year="2018")
User.objects.filter(birth__month="1") # 生日是1月 可选值1-12
User.objects.filter(birth__week_day__gt=1) # 生日日期大于周日
select * from t_user;查询全部列 User.objects.all()
select id,name from t_user;查询部分列–映射查询
#查询部分列(返回QuerySet,其中每个元素都是一个dict)
User.objects.all().values("pk") #等价上面写法:select id from t_user;
User.objects.filter(age__lt=20).values("name") # select name from t_user where age<20
#select id,age from t_user where name like "%zhj%"
User.objects.filter(name__contains="zhj").values("pk","age")
#返回, ] >
users = User.objects.only("pk","name") #会率先查询id和name,其他列暂时不查
#即,QuerySet内的User中暂时只有id和name有值
#依然可以取到值
users[0].age #用到其他列数据时,才会查询它
#此处涉及到延缓查询的特性:lazy-load
from django.db.models import Count, Max,Avg,Min,Sum
#返回 {'pk__max': 4, 'name__min': 'zhj'}
User.objects.aggregate(Max("pk"),Min('name'))#selecet max(id),min(name) from t_user;
#可以定义别名,影响返回值结构 -- 返回 {'mm': 4, 'name__min': 'zhj'}
User.objects.aggregate(Min('name'),mm = Max("pk"))#selecet max(id) as mm,min(name) from t_user;
#select 分组条件列,聚合1,聚合2 from 表 group by 分组条件列
Models.objects.values(‘分组条件列’).annotate(聚合1,聚合2)
Models.objects.values(‘age’).annotate(聚合1,聚合2) == 对每种age都做聚合1 聚合2的统计
Model133.objects.values(“age2”).annotate(Max(“salary”),Min(“salary”))
select max(salary),min(salary),age2 from xx group by age2
#每种年龄中的用户的最大id和用户的最小生日
#select age,max(id),min(birth) from t_user group by age
User.objects.values("age").annotate(Max('pk'),Min('birth'))
#加别名:select age,max(id),min(birth) as 'mm' from t_user group by age
User.objects.values("age").annotate(Max('pk'),mm=Min('birth'))
#大于18的每种年龄中的用户的最大id和用户的最小生日
#加where : select age,max(id),min(birth) from t_user where age>18 group by age
User.objects.values("age").filter(age__gt=18).annotate(Max('pk'),Min('birth'))
#查询最大id大于3的每种年龄中 用户的最小生日,和最大id
#加having : select age,max(id),min(birth) from t_user group by age having max(id)>3
User.objects.values("age").annotate(Min('birth') ,ma = Max('id')).filter(ma__gt=3)
#加排序
User.objects.values("age").annotate(Min('birth'),mm=Max('pk')).order_by('mm')
User.objects.filter(id__gt=1) #id大于1
问题:查询id大于age的用户? 尝试:User.objects.filter(id__gt=“age”)–错误
当查询条件中需要另外的列时,可以使用F
User.objects.filter(id__gt=F(‘age’))–正确
from django.db.models import F
User.objects.filter(id__lt=F('age')) #id小于age
User.objects.filter(id__lt=F('age')+1) #id小于age+1
User.objects.filter(id__lt=F('age')+F('salary')) #id小于age+salary
User.objects.filter(id__lt=1,name__contains=“zhj”) #id小于1 而且 name包含“zhj”
问题:查询id小于1 或 name含有“zhj”的数据?
查询name不含有“zhj”的数据?
当需要 “或 (|) 非(~)” 逻辑时,可以使用Q
from django.db.models import Q
User.objects.filter(Q(id__gt=3)|Q(id__lt=2)) #id大于3 或 id小于2
User.objects.filter(Q(id__gt=3)|~Q(name__contains="zhj")) #id大于3 或 name不含有“zhj”
User.objects.filter(Q(id__lt=3)|Q(age__gt=F("id")*10)) #id小于3 或 age大于id*10
User.objects.filter(Q(id__gt=3)|Q(name__contains="zhj"),age__lt=2) #id大于3 或 name包含"zhj" 且 age小于2
User.objects.filter(Q(id__gt=3)|Q(name__contains="zhj"),~Q(age__lt=2) #id大于3 或 name包含"zhj" 且 age不小于2
select xxxx,xxx,xxxx from xx where xxx group by xx having order by
直接暴露的Model底层封装的Connection和Cursor
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT age,name FROM t_user WHERE baz = %s", [self.baz])
#返回tuple of tuple
result = cursor.fetchall() #(("zhj",18),(..),)
with connection.cursor() as cursor:
cursor.execute("SELECT age,name FROM t_user WHERE baz = %s", [self.baz])
#columns = ["age","name"]
#row=(1,"zhj")
#zip(columns,row) = [("age",1),("name","zhj")]
#dict(zip(columns,row)) = {"age":1,"name":"zhj"}
columns = [col[0] for col in cursor.description]
#返回 list of dict : [{"age":1,"name":"zhj"},{...}]
result = [dict(zip(columns, row)) for row in cursor.fetchall()]
有,从属的关系
数据库的表之间都是有联系的,正如我们之前讲的,有关联关系的存在
关联关系的种类:1对1 , 1对多 , 多对多
在Model中体现关联关系,进而更好的在多表之间操作数据
1:*
class Category(models.Model):
ctitle = models.CharField(max_length=20)
create_time = models.DateField(auto_now_add=True)
class Meta:
db_table="t_category"
class Goods(models.Model):
gtitle = models.CharField(max_length=20,unique=True)
gprice = models.FloatField(db_column='price')
#to=关系对方Model ForeignKey=一对多关系,并且当前方为多
#关系属性:对应外键列:db_column="cate_id"
cate9 = models.ForeignKey(to=Category,on_delete=models.CASCADE,db_column="cate_id")
#关系属性:对应外键列:cate_id == 关系属性名_id
#cate = models.ForeignKey(to=Category, on_delete=models.CASCADE)
class Meta:
db_table = "t_goods"
1:1
class Passport(models.Model):
note = models.CharField(max_length=20)
person = models.OneToOneField(to="Person",on_delete=models.CASCADE) #关系属性
class Meta:
db_table="t_passport"
class Person(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
class Meta:
db_table="t_person"
*:*
class Student(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField
class Meta:
db_table="t_student"
class Course(models.Model):
title = models.CharField(max_length=30)
expire = models.SmallIntegerField()
students = models.ManyToManyField(to=Student) #关系属性
class Meta:
db_table="t_course"
1对多关系 Category(1):没有关系属性 Goods(*):其中有关系属性cate9
#单独查一方:Category
cs = Category.objects.all()
cs[0].goods_set # 没有多方的关系属性,通过"多方类名_set"获得对方的多个数据
# 此时只是返回一个RelatedManger,尚未查询,需要如下调用all或filter等查询方法
cs[0].goods_set.filter(gprice__gt=200) #第一个类别中价格大于200的所有商品 (如此才有值)
cs[0].goods_set.all() # 第一个类别的所有商品(如此才有值)
#单独查询一方:Goods
gs = Goods.objects.all()
gs[0].cate9 #通过关系属性,获得商品的类别对象
gs[0].cate9.ctitle #获得对象后 自然可以继续获取属性
#关联查询:以类别为条件查询商品(获取对方用关系属性,获取对方的属性获用【__】)
Goods.objects.filter(cate9__pk__gte=1) # 类别id大于1的商品
Goods.objects.filter(cate9__ctitle__contains="s")# 列名标题含有"s"的商品
#关联查询:以商品为条件查询类别(获取对方用对方小写类名,获取对方的属性获用【__】)
Category.objects.filter(goods__pk__gt=1)#id大于1的商品的类别
Category.objects.filter(goods__gtitle__contains="a")#标题中含有"a"的商品的类别
#关联查询:保留双方数据(获取对方的属性获用【__】)
Goods.objects.filter(cate9__ctitle__contains="s").values("cate9__ctitle","pk","gprice")
#注意在进行联合查询时,可能会由重复数据出现,解决:
list(set(Category.objects.filter(goods__gprice__gt=200)))
多对多关系 Course:有对方关系属性students Student:没有对方关系属性
#查询一方:Student
stus = Student.objects.all()
stus[0].course_set #返回ManyRelatedManager,但并未查询数据
stus[0].course_set.all() #获得对方所有数据
stus[0].course_set.filter(title__contains='s') #获得第一个学生的 标题中含有"s"的课程
#查询一方:Course
cours = Course.objects.all()
cours[0].students.all()
cours[0].students.filter(age__gt=18)
#关联查询:以课程为条件 查询学生
Student.objects.filter(course__title__contains="h") #标题中含有"h"的课程中的学生
Student.objects.filter(course__expire__gt=2) #课时大于2的课程中的学生
#关联查询:以学生为条件 查询课程
Course.objects.filter(students__name="zzz") #姓名为zzz的学生参加的课程
Course.objects.filter(students__age__gt=18) #年龄大于18的学生参加的课程
#关联查询:保留双方数据
Course.objects.filter(students__name="zzz").values("title","expire","students__name")
1对1关系: Person:没有关系属性 Passport:有关系属性person
#单独查一方:Person
per = Person.objects.all()
pass[0].person #通过关系属性名 获取关系对方
per[0].passport #没有关系属性时,通过关系对方全小写的类名,获得对方
#单独查一方:Passport
pass = Passport.objects.all()
pass[0].person.name #继续获取属性值
per[0].passport.note #继续获取属性值
#关联查询
Person.objects.filter(passport__note__contains="a")
Person.objects.filter(passport__note="a")
Person.objects.filter(passport__pk=1)
#关联查询
Passport.objects.filter(person__pk=1)#id为1的person的Passport
Passport.objects.filter(person__pk__gt=1)#id大于1的person的Passport
Passport.objects.filter(person__name__contains="z")#id大于1的person的Passport
单独增加主表方(没有外键一方) 和单表增加无差异
Category(title="男装",...).save()
为已存在的主表方附加从表数据**(常见的增加情形)**
c = Category.objects.get(pk=1)
#goods_set = RelatedManager
c.goods_set.create(title="zzz6",price=100) #不需要再save
或
c = Category.objects.get(pk=1)
g=Goods(title="zzz6",price=100,cate9=c)#为商品的关系属性赋值
g.save() #需要save
#注意,如果是1:1 则只能用第二种
同时增加主从数据
g = Goods(title="zzz6",price=100)
c = Category(title="类别1",note="xx")
g.save() #此时,外键的值为null
c.save()
c.goods_set.add(g) #会同步数据库,补充外键值
或
c = Category(title="类别1",note="xx")
c.save()
g = Goods(title="zzz6",price=100,cate9=c)
g.save() #此时,good是有外键值的
#注意如果是1:1只能用第二种
单独删除从表方,和删除单表无差异
Goods.objects.get(pk=1).delete()
删除主表方,此时要看从表方的级联设置,会影响到从表方
级联选项 : 在关联关系中删除主表时,对于从表可以有级联操作
- CASCADE 级联删除
- SET_NULL 外键置空(如果允许空的话)
- PROTECT 不允许直接删除主表
- SET_DEFAULT 需要为外键列设置默认值,默认值也应该是合法的外键值,可以在表中预留一个id
- SET 将外键设置为某个值,值应该是合法的外键值,可以在表中预留一个id
- DO_NOTING dango什么也不做,由数据库决定是否合法
1:1 支持 CASCADE SET_NULL(前提:外键列允许为null) DO_NOTION
1:* 都支持
* 不支持级联删除
#如果要删除主,所有从的外键置null (重要)
per = models.OneToOneField(to=Person,on_delete=models.SET_NULL,null=True)
#如果要删除主,所有从一起删除
per = models.OneToOneField(to=Person,on_delete=models.CASCADE,null=True)
#如果要删除主,django什么也不做,有数据库决定是否合法
per = models.OneToOneField(to=Person,on_delete=models.DO_NOTHING,null=True)
#如果要删除主,所有从的外键置为6 (重要)
cate115 = models.ForeignKey(to="Category",on_delete=models.SET(6))
#如果要删除主,所有从的外键置为默认值5 (重要)
cate115 = models.ForeignKey(to="Category",on_delete=models.SET_DEFALUT,default="6")
#如果要删除主,如果有从数据存在,不允许删除
cate115 = models.ForeignKey(to="Category",on_delete=models.PROTECT)
查询出数据,修改属性,然后save()即可,和单表跟新无任何差异
cs = Category.objects.all()
c = cs[0]
c.title="new_title"
c.save()
坑:
Category.objects.all()[0].title="new_title" #查询一次,并修改
Category.objects.all()[0].save() #查询一次,并保存(不能更新任何数据)
QuerySet在查询数据时,是延迟执行,直到真正使用了数据,才发起查询
a = Person.objects.all() #未查询
b = Category.objects.all() #未查询
print(a) #使用了a,则查询a
#如上代码只执行了一次查询
c = Category.objects.all() #未查询
print(c) #使用了c,查询c
goods = c[0].goods_set.all() #未查询goods
print(goods) #使用了goods,查询goods
测试方式
找到mysql的安装目录下的my.ini文件,添加配置:
[mysqld] ..... log = "E:/mysql_log.sql" #设置日志文件,记录sql语句执行 或 general-log=1 general_log_file=E:/mysql_log.sql
net stop mysql
net start mysql
django的事务管理:代码块内没有异常则事务提交,如果出现异常则事务回滚
from django.db import transaction
with transaction.atomic():
Person(name="tx_name22", age=18).save()
Person(name="tx_name33", age=18).save()
# Person(name="tx_name4", age="abc").save() #异常,此时事务回滚
# a = 10 / 0 #异常,此时事务回滚
# raise ValueError("xx") #异常,此时事务回滚
控制事务,并处理其中异常
可以捕获事务内发生的异常,注意要在事务外部捕获;
如果在事务内部捕获,而没有抛出异常的话,会改变异常的正常行为。
import traceback
try:
with transaction.atomic():
Person(name="tx_name22", age=18).save()
Person(name="tx_name33", age=18).save()
Person(name="tx_name4", age="zzz").save()
# a = 10 / 0
# raise ValueError
except ValueError as e:
print("exept~~")
traceback.print_exc()#打印异常栈信息
便捷事务控制,以函数为整体,函数内如果出现异常则回滚,否则提交(了解)
@transaction.atomic
def abc():
Person(name="tx_name2211", age=18).save()
Person(name="tx_name3311", age=18).save()
Person(name="tx_name4", age="zzz").save()
# a = 10 / 0
# raise ValueError
abc()
1.知识点练习 2遍
2.完善ems,在login和register中引入model
##请求状态保持
- 如上两个View分别接收各自的请求,当test1中的请求数据,test2中是无法使用的
- Http协议请求,无状态;
- 请求内的数据:请求参数,逻辑运算得到的数据等,都会随着请求的结束而销毁
- 实际的业务需求中,有很多数据需要在多个请求之间共享。所以需要保持数据。
- 数据保持的手段:cookie,session
小甜品,饼干…
由服务器生成,存储在浏览器客户端的一小块数据 ( 有长度限制,不适合存储大量数据 )
用于持续保持用户的状态,常用于做用户状态的保持,常见的"记住我",“自动登录” 即是。
写出cookie是HttpResponse的功能
为Response设置好cookie后,在响应到客户端时,cookie会随之存入客户端
cookie的数据格式: [key:value]
def write9(request):
#1.数据
name = User.objects.get(pk=2).name
#2.将数据存入Response
res = HttpResponse("hello this is test1~~")
rese.set_cookie("realname",name)
#3.响应
return res
def write9(request):
#获得重定向时的响应对象
res = redirect("/test/hello2/")
#在Response对象中设置cookie数据
res.set_cookie("name","zhj") #key="name" value="zhj"
res.set_cookie("age",18) #key="age" value=18
return res #响应
def write99(req):
#获得转发时的响应对象
res = render(req,"test.html")
#在Response对象中设置cookie数据
res.set_cookie("name","zhj") #key="name" value="zhj"
res.set_cookie("age",18) #key="age" value=18
return res #响应
#cookie的生命周期设置
res.set_cookie("password","123") #存活一次会话,浏览器关闭时,失效
res.set_cookie("password","123",max_age=100)#cookie存活100s
res.set_cookie("password",max_age=0)#cookie存活0s,生命结束,删除cookie
请求服务器时,当再次访问项目时,会携带本项目的所有cookie到达服务器,通过request对象可以读取cookie中的数据
def hello3(req):
#返回所有cookie
print(req.COOKIES) #{'password': '123', 'password2': '456'}
print(type(req.COOKIES)) #dict
req.COOKIES['password'] #"123"
req.COOKIES.get("password") # "123"
补充:cookie 中文问题
cookie中不允许存中文,如果有,需要对中文做编码处理
#写cookie
res.set_cookie("password", str("臧红久".encode("utf-8"),'latin-1'))
#读cookie
str(req.COOKIES['password'].encode('latin-1'),"utf-8")
测试实例:记住我
保持请求状态,由服务器生成,且存储于服务器
用作多个请求之间,共享数据
####启用Session功能
settings.py中已有如下设置
保障session可以使用
INSTALLED_APPS = [
...
'django.contrib.sessions',
...
]
MIDDLEWARE = [
...
'django.contrib.sessions.middleware.SessionMiddleware',
...
]
在使用session前,需要为django.contrib.sessions做移植 生成数据表,用于存储session数据
python manage.py migrate #创建 “django-session”表
默认存活两周
可以修改为一个会话周期,在settings.py中:
SESSION_EXPIRE_AT_BROWSER_CLOSE=True
#注意,django默认的session存储位置为数据库的django_session表,需要通过python manage.py migrate生成
def xxx(req):
req.session['username']="臧红久"
req.session['login']=1
...
def xxx(req):
print(req.session['username'])
print(req.session.get('login'))
...
注意session没有中文问题,没有长度限制,相比存在客户端的cookie更安全
req.session.flush() #清除数据,置空cookie,清除数据表中的记录
req.session.clear() #清除数据
del req.session['username'] #清除一个key的数据
如上图:session创建时有自己的ID,且ID随着响应存入浏览器,在一个cookie中。
后续请求时,服务器可以从cookie中获取session的ID,进而获取对应session
测试实例:多请求共享用户名
http请求的状态码 200=成功 404=资源未找到 500=服务器错误 400=bad request… 403=请求拒绝
可以为常见的错误定制错误页面
1> 关闭调试模式,设置allow_host
2> 在templates下新建:404.html 500.html 400.html,
3> 在出现对应错误时,会自动跳转对应错误页面
def testtx3(request):
try:
with transaction.atomic():#开始一个事务 环境,with结束时,如果没有异常,事务提交;否则回滚
Person(name="tx_name22", age=18).save()
Person(name="tx_name33", age=18).save()
a=10/0
return render(request,"success.html")
except:
traceback.print_exc() #打印异常栈
print("error") #此时的异常已经回滚
return render(request,"error.html") #还可以为错误提供专用的视图页面
设置ATOMIC_REQUESTS,则per-view-per-transaction
在view开始前开启事务,如果response成功构建,则提交事务
在response之前如果出现异常,则回滚事务
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db9',
...
'ATOMIC_REQUESTS':True,
}
}
#运行View前已经开启事务
def testtx2(r):
Person(name="tx_name22", age=18).save()
Person(name="tx_name33", age=18).save()
a=10/0 #出现异常,事务自动回滚
return HttpResponse("abc")
#view结束,没有异常,事务提交
坑:如下情况,事务依然提交
则,基于View的事务控制,不能处理异常,否则事务行为被破坏
如果在view中try了异常,django就认为整个View没有异常,则事务正常提交
def testtx(request):
try:
Person(name="tx_name22", age=18).save()
Person(name="tx_name33", age=18).save()
a=10/0
except Exception:
print("error")
return render(request,"error1.html")
return render(request,"success.html")
# 技术点熟悉,练习
# 登录 注册
# 注册后自动跳转登录页面
# 登录后记住我 一周 -- cookie
# 登陆后 在后续的请求中 显示用户真实姓名
# 强制登录 -- sesion
##Template概述
模板格式:html文件
模板作用:生成html,作为响应内容 (更好的定制响应内容)
return HttpRespones("hello")
return render(request,"xx.html")
模板运行方式
模板不会独立运行,而是在View之后会跳转一个模板
def xx(request): print("hello") #跳转Template return render(request,"abc.html")
abc.html {# 静态内容 #} go {# 动态内容 #} {{ request.session.username }} {{ request.COOKIES.name }}
{{username}}{{realname}}模板的搜索根目录:Project下的templates目录 和 “已安装App”下的templates目录
模板文件管理方式: 1> 每个App自己持有,且有自己的独立目录防止多个App中的模板命名冲突
2> 统一放在Project中,且有自己的独立目录防止多个App中的模板命名冲突 (建议)
模板渲染
即从View跳转到Template后,Template的执行过程渲染过程静态内容原样输出,动态内容逻辑运算
渲染结果:静态内容 + 动态渲染代码运算结果
tem = loader.get_template("user/abc.html") #到模板根目录查找abc.html result = tem.render(request=request) #模板渲染,返回【静态+动态的运算结果】!!! return HttpResponse(result) #将模板渲染结果响应给client
return render(request,"user/abc.html") #等价如上过程
模板语言:Django内置有自己的模板语言(模板引擎);第三方的模板语言比较流行的有jinja
Django官方表示,如果没有不得不换的原因,推荐内置模板语言
Django Template Language(DTL)
模板文件中,包含的动态渲染代码,使用的就是DTL {{xxx}}
模板语言安装:
settings.py
模板的正确使用,是衔接View,接收View传递的数据,将数据渲染在html网页中
值:View中通过Model组织得到的数据
传递:由View 传递给 Template
目标:将数据渲染到一块静态内容(html)中 , 定制动态内容,作为请求的响应内容
传值示例:
{key:value,key2:value2}
def xx(request): user = User.objects.get(pk=1) #获得数据 # 跳转abc.html,并向其传值:{"user":user},abc.html就可以使用如此数据 return render(request,"abc.html",{"user":user})
abc.html ... hello,{{user}} ...
从本节开始,讲解DTL的详细语法
变量,是要讲解,在模板中如何去获取View传递来的数据
简单数据
View : render(...,{"name":"zhj","id":18})
Template:
{{ id }}
<input type="text" name="" id="" value="{{ id }}">
<span style="color:red">{{ id }}</span>
<a href="/tempate/test2/?id={{ id }}">{{ id }}</a>
<script>
var id = "{{ id }}";
</script>
Model对象 (重点)
View:render(...,{"user":User(id=1,name="zhj",age=18,order=Order(price=100.5))})
render(...,{"user2":User.objects.get(pk=1)})
render(...,{"user3":User.objects.filter(pk=1)})
Template:{{user}} #获取User对象
{{user.id}} #User对象的id属性
{{user.name}} #User对象的name属性
{{user.age}} #User对象的age属性
{{user.order.price}} #User对象的order属性的price属性
{{user2.order.price}} #User对象的order属性的price属性
{{user2.order_set}} #返回一个RelatedManger
{{user2.order_set.all}} #RelateManager.all() ,返回一个QuerySet
{{user2.order_set.all.0}} #返会第一个Order
{{user3.0.age}} #User对象的order属性的price属性
列表 (了解)
View: render(...,{"list":[12,34,56]})
Template:{{list}}
{{list.0}} #list[0]
{{list.1}} #list[1]
dict (了解)
View:render(...,{"dict":{"name":"zhj","age":18}})
Template:{{dict.name}} #dict['name']
{{dict.age}} #dict['age']
方法 : 无参方法 (了解)
class User: def abc(): return "abc"
View:render(...,{"user":User(...)})
Template:{{user.abc}} {# 输出方法的返回值 #}
则如上,变量中**"."** 是一个很强大的字符 “对象属性,列表下标,字典的key,对象的方法”
{# if #}
{% if user.id > 1%}
<input type="text"/> {# 如果if成立则输出input #}
{% endif %}
{# 0/非0 None/非None ""/非"" False/True 空QuerySet/非空QuerySet#}
{% if vvv %}
<input type="text"/> {# 如果if成立则输出input #}
{% endif %}
{# if ..elif ..#}
{% if user.id > 1%}
<input type="text"/> {# 如果if成立则输出input #}
{% elif user.abc == "thisisfn" %}
this is fn {# 如果if成立则输出这句平文 #}
{% endif %}
{# if ..elif ..else#}
{% if user.id > 1%}
<input type="text"/> {# 如果if成立则输出input #}
{% elif user.abc == "thisisfn" %}
this is fn {# 如果if成立则输出这句平文 #}
{% else %}
<p>这是一个段落</p> {# 如果以上都不满足输出一个p标签 #}
{% endif %}
{# 比较运算 == != >= <= > < #}
{% if name == "zhj" %}
{% if id >= 10 %}
{# 逻辑运算 #}
{% if 1 or 2 %} {# and or not 与或非 拼接多个条件 #}
...
{% elif id > 100 and not name %}
....
{% elif not id > 100 and not name %}
....
{% endif %}
{# in / not in 包含于/不包含于 用于字符串 和 序列 和 dict 和 QuerySet#}
{% if user.name not in "aazhjzzzaaa" and users.0 in users%}
name in zhjasdf
{% endif %}
注意:if中的表达式 如 name == “zhj” 中 “name” “==” “zhj” 三个部分之间要用空格分开
for标签中,可以选择添加一个empty子标签,在数据不存在时执行
{% for user115 in users%} {# {% for user in users reversed%} 倒叙遍历 #}
{{user115.age}} {# {{..}} 获取遍历中的数据 #}
{{user.name}}
{% empty %}
<p> 如果遍历的数据为[],或不存在,执行empty,输出这个段落</p>
{% endfor %}
<table>
...
<tbody>
{% for g in goods4 %}
<tr>
<td>{{g.id}}</td>
<td>{{ g.title }}</td>
<td>{{ g.price }}</td>
</tr>
{% empty %}
<tr><td colspan="3" align="center">没有数据</td></tr>
{% endfor %}
</tbody>
</table>
{% for a,b in dict9.items %} {# 遍历字典 dict.items #} (了解)
{{ a }} {{ b }}
{% empty %}
<p> 如果遍历的数据为[],或不存在,执行empty,输出这个段落</p>
{% endfor %}
{% for a,b in ll9 %} {# 遍历list of list #} [[1,2],['a','c']] (了解)
{{ a }} {{ b }}--
{% endfor %}
{% for ... %}
...
{{ forloop.counter0 }} 遍历索引从0开始
{{ forloop.counter }} 遍历索引从1开始
{{ forloop.revcounter }} 倒序索引
{{ forloop.revcounter0 }}
{{ forloop.first }} 是否为第一次遍历
{{ forloop.last }}
...
{% endfor %}
{% for ... %}
...
{% if forloop.first %}
第一次遍历
{% else %}
...
{% endfor %}
补充:{% now “Y-m-d H:i:s”%} 获取当前的服务器时间,注意时区问题
获取值的过程中,可以对值进行处理,通过 过滤器 完成
{{ xx }} 正常取值
{{ xx|过滤器 }}
{{birth|date:'Y/m/d H:i:s'}}
{{name|length}} {#获取长度#}
{{list|length}}
{{dict|length}}
{{queryset|length}}
{{name|length_is:'4'}}
{{name|lower}} {# 所有字符变小写 #} (了解)
{{name|upper}} (了解)
{{name|default:'zhj'}} {# 没值、False、None、""、0 #}(了解)
{{ list115.1|add:'1' }} {# 加法 #}
{{ list115.1|add:'-1' }} {# 减法 #}
{{ list115.1|divisibleby:'2' }}
{# % 求模运算 #}
{% widthratio 5 10 100 %} {# a/b*c 5/10*100 百分比#}
{% widthratio 5 1 2 %}
{% widthratio list115.1 1 list115.2 %} {# 乘法运算 #}
{% widthratio list115.1 list115.2 1 %} {# 除法运算 #}
#所有的过滤器,也可以用在标签中(if for)
{% if user.birth|date:'Y/m/d H:i:s'|length > 10%}
<p>length gt 10</p>
{% endif %}
项目的多个页面中会有重复的部分,会导致在多个模板文件中会有重复的代码
此时,会造成代码冗余,不利于项目的管理
模板继承,可以很好的解决这种问题
{% extends "fruit_ex/super1.html" %} {% block content_super1 %} {% endblock %}
定义父类模板,其中定义多个模板中都重复的内容
不重复的位置用如下标签占位:
{% block super1%}
{% endblock %}
block块
之间的内容是不重复的部分,由子模板自己定制
block块
之外的其他内容,子类模板直接继承
base.html
...
<body>
<div style="background-color:lavender;margin-bottom: 100px">
欢迎你,{{ user.name }}
</div>
{% block super1%}{% endblock %}
<div style="font-size: 12px;color:#999;margin:0 auto;text-align: center;padding:20px 0">
Copyright © 2013-2018
<strong><a href="#" target="_blank">百知</a></strong>
<strong><a href="#" target="_blank">baizhi.com</a></strong> All Rights Reserved.
</div>
</body>
继承父模板
{% extends “base.html” %} 在页面第一行定义继承逻辑
子模板中只能定义父模板中
block块
中的内容,其他内容都是继承自父模板则实现了子模板中不用定义父模板中已经定义的内容,进而消除重复
sub.html
{% extends "base.html" %}
{% block super1 %} {!-- 覆写父模板block --}
<div style="height: 100px;background-color: #999999">
这是子类模板自己的内容
</div>
{% endblock %}
坑:在子模板中,只有block块内的内容,才是有效的内容,block块外的内容,不被解析,视为无效
如下在模板或View中 访问View时,将View的访问路径是硬编码
则当view的路径配置改变时,维护成本会很高!
urlpatterns = [
path('admin/', admin.site.urls),
path('test/',include("app1.urls")),
]
urlpatterns=[
path('a/',views.testconf1)
]
template:<a href="/test/a/">...</a> <form action="/test/a/"..
view: redirect("/test/a/")
1> 除了给View访问路径外,额外给View一个命名
2> 需要访问View的时候,不再直接写View的访问路径,而是写View的命名
urlpatterns=[
path('a/',views.testconf1,name="zhj") #name=为view命名
]
#使用View命名访问View
template:<a href="{% url 'zhj'%}">ssss</a> #返回ssss
view: redirect("zhj") #重定向中会自动反向解析路径, 跳转:/test/a/
如上代码无论View的路径如何改变,代码的维护成本都很低
如果多个App中有相同命名的View,则自然会有View的命名冲突!
管理:保证每个APP都有自己的独立命名,如下:
#1.全局的urls
urlpatterns=[
path('admin/',admin.site.urls),
# 为app1 定义独立命名:"user"
path('aa/',include(('app1.urls','app11')),
# 为app2 定义独立命名:"order"
path('bb/',include(('app2.urls','app22')),
]
#2.App1/urls.py
urlpatterns=[
path('a/',views.xxx,name="query") #相同的命名:query
]
#3.App2/urls.py
urlpatterns=[
path('b/',views.xxx,name="query") #相同的命名:query
]
#4.使用View命名访问View ("一级命名:二级命名")
template:<a href="{% url 'app11:query'%}">ssss</a> #http://.../aa/a/
<a href="{% url 'app22:query'%}">ssss</a> #http://.../bb/b/
view: redirect("app11:query") #重定向中会自动反向解析路径, 跳转:/test/a/
redirect("app11:query") #重定向中会自动反向解析路径, 跳转:/test/a/
进一步管理
一级命名下有多个二级命名
二级命名下分为多个三级命名
三级命名对应具体的View
urlpatterns=[
path('admin/',admin.site.urls),
path('aa/',include(('app1.urls','app11')) #一级命名
]
#In App1/urls.py
urlpatterns=[
...
path('query/',include(([path('one/',views.testconf1,name="one9"),#三级命名
path('all/',views.testconf8,name="all9")
],
"query9") #进一步独立命名(二级命名)
)
),
...
]
template:<a href="{% url 'app11:query9:one9'%}">ssss</a> #http://.../aa/query/one/
view:redirect("app11:query9:one9")#跳转 /aa/query/one/
urls.py
path("hello///", views.hello, name="a"),
re_path(r'^hello2/(?P\w+)/$', views.hello2, name="b"),
Views:
redirect("a") 错误!!!
redirect("a",age=18,name="zhj") #/hello/18/zhj/
redirect("a",age=19,name="zwl") #/hello/19/zwl/
redirect("b",name="zhj") #/hello2/zhj/
Template:
{% url "a" %} 错误!!
{% url "a" "19" "zhj" %} #/hello/19/zhj/
{% url "b" "zhj"%} #/hello2/zhj/
作业:
1.技术点
2.ems:显示所有员工,删除员工,添加员工,更新员工(选作)
要求:两个APP
login/register APP(admin) t_admin
员工操作 APP(employee) t_employee
在常规的Form表单使用中,验证码是常用的组件,用于更好的保障请求的合法性,防止无效访问,恶意访问,暴力破解等攻击
在服务器端,生成一个随机的code:“aecd1” ,将code画到一张图片中,最终将图片写出给client
注意:依赖第三方包:pillow
pip install pillow
导入第三方验证码工具:
将文件拷贝到自己的项目中:2个py文件,1个data文件夹,保证三个处于项目中的同一个目录中
编码,生成验证码,输出给客户端浏览器
import random,string
def getcaptcha(request):
#从image.py中导入ImageCaptchar类
from xx.image import ImageCaptcha
#为验证码设置字体 获取当前目录下的xxx目录下的segoesc.ttf文件
image = ImageCaptcha(fonts=[os.path.abspath("xxx/segoesc.ttf")])
#随机码
#大小写英文字母+数字,随机抽取5位作为验证码 ['x','x','x','x','x']
code_list = random.sample(string.ascii_lowercase+string.ascii_uppercase+string.digits,5)
code = "".join(code_list)#获得随机的5为验证码
#将验证码存入session,以备后续验证
request.session['code']=code
#将生成的随机字符拼接成字符串,作为验证码图片中的文本
data = image.generate(code)
#写出验证图片 给客户端
return HttpResponse(data,"image/png")
在模板中定义显示验证码的img标签
Tem中:
<script>
function refresh(){
//重新为img的src赋值,将触发一次图片的请求
$("#num").attr("src","/cap/test/?"+new Date().getTime())
}
</script>
<form>
....
<!--用户在此输入验证码-->
<input type="text" name="code"/>
<!--请求 开发过程-2中的view,获得输出的验证码图片-->
<img id="num" src="{% url "xxx" %}" style="width: 70px" onclick="refresh();"/>
....
</form>
开发过程-3 中的form,应该请求此View函数
验证用户输入的验证码是否正确
def login(request):
#从session中获取真实的验证码
rcode = request.session.get("code")
#接收"开发过程-3"表单中用户输入的验证码
code = request.POST['code']
if rcode.lower() == code.lower():#验证是否输入正确
#执行登录验证逻辑
else:
#再次跳回到登录页面
上传:将客户端的文件传送给服务
实例:用户注册中,头像上传
设计:将上传来的文件存在服务器本地目录中,将文件的存放目录路径存于t_user表中(ImageField)
注意:ImageField依赖第三方包:pillow
为用户定义form表单,选择要上传的文件
**注意:method=“post” enctype=“multipart/form-data” **
<form action="{% url "upload" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="text" name="name"/>
<input type="file" name="source"/>
<input tppe="submit" value="shangc~"/>
</form>
接收form发来的数据 (文件+其他数据)
def upload9(request):
name = request.POST.get("name")
file = request.FILES.get('source') #接收文件
print("文件原始名为:",file.name," 文件类型为:",file.content_type)
return HttpResponse("提交成功")
保存数据(文件+其他参数)
1>将文件存储到服务器本地目录中,并将文件的保存路径存储到数据库中
2>其他参数存储到数据库中
通过Model完成保存任务!
用于完成上传数据的存储
class User(models.Model):
name = models.CharField(max_length=20)
pic = models.ImageField(upload_to="user_pic")
class Meta:
db_table = "t_user"
#有了Model后,要有对应的库表,可以自己创建,也可以移植。
注意ImageFiled,会帮助完成文件的保存,所以它需要知道文件该存到哪个目录:
1> settings.py中:
MEDIA_ROOT = os.path.join(BASE_DIR,"media9")
== 项目根目录下的media9目录 2> ImageFiled中:
upload_to="user_pic"
如此上传来的文件,会被ImageField存储在
media9
目录下的user_pic
目录下
Model.save() 即可
def upload9(request):
name = request.POST.get("name")
file = request.FILES.get('source') #接收文件
#将收到的参数存入Model中
user = User(name=name,pic=file)
#保存User对象,此时会自动将文件存入指定目录,并将文件保存路径存入数据表,其他上传的参数也存入数据库
user.save()
...
注意,此时数据库中存储的路径是相对于MEDIA_ROOT的路径
回显图片
1> 首先将MEDIA_ROOT目录,设置为静态资源根目录,以便于访问到上传的图片
2> 通过ImageFiled的url属性,可以获得图片的访问路径(相对于静态资源根目录的 相对路径)
#查询到用户信息
user = User.objects.filter(pk=1)
return render(request,"xx.html",{"user":user}) #user传给模板
<!-- 模板中显示 user.pic.url 即数据库中存储的文件路径 -->
<img src="/static/{{user.pic.url}}"/> 或
<img src="{% static user.pic.url %}"/>
{%static 'a/b/c/aa.jpg'%}==> /static/a/b/c/aa.jpg
<img src="{% static user.pic.url %}"/> ==> /static/a/c/d/c.jpg
python原生文件操作代码
def test_upload2(r):
import os,uuid
b = r.FILES.get("source") # 获取上传的文件
unique_name = str(uuid.uuid4()) # 唯一文件名
ext = os.path.splitext(b.name)[1] # 文件后缀
name = unique_name + ext # 拼接文件名
with open(file=os.path.join(os.path.abspath("media/just_test/"),name),mode="wb") as output:
for chunk in b.chunks(): #如果超过2.5M则分块,分为64Kb的块依次读取
#chunks是django.core.files.base.File中的方法
output.write(chunk)
return HttpResponse("ok")
数据很多时,无法在一张页面中显示所有数据,所以需要分页显示
#通过paginator获得Page对象,每页3条数据;获取第一页(从1开始)
page = Paginator(object_list=Goods.objects.all(),per_page=3).page(1)
#获得页号的序列 [1,2,3,4]
page.paginator.page_range
#共有多少页
page.paginator.num_pages
#是否还有上一页,下一页
page.has_previous()
page.has_next()
#获取下一页,上一页的页号
page.previous_page_number() 获取上一页编号 如果没有下一页则报错
page.next_page_number() 获取下一页编号 用于template中的上一页链接的参数绑定中 如果没有上一页则报错
#获得当前页号
page.number 当前页号,多用于template中对当前页号追加样式
#获得当前页的数据
page.object_list
当前页数据展示
#此View的访问路径为:"/page/"
def testquerypage(request):
#要查询第num页(从1开始)
num = request.GET.get("num")
#当前页的对象
page = Paginator(User.objects.all(),3).page(num)
return render(request,"users.html",{"page":page})
{# 展示所有数据 #}
{% for user in page.object_list %}
{{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}
展示当前页 + 所有页号
{# 展示所有数据 #}
{% for user in page.object_list %}
{{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}
{# 输出所有页号 #}
{% for page_num in page.paginator.page_range %}
<a href="/page/?num={{ page_num }}">{{ page_num }}</a>
{% endfor %}
上一页 + 下一页
{# 展示所有数据 #}
{% for user in page.object_list %}
{{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}
{# 如果有上一页显示 下一页链接 #}
{% if page.has_previous %}
<a href="/page/?num={{ page.previous_page_number }}">上一页</a>
{% endif %}
{# 显示所有的页号 链接 #}
{% for page_num in page.paginator.page_range %}
<a href="/page/?num={{ page_num }}">{{ page_num }}</a>
{% endfor %}
{# 如果有下一页显示 下一页链接 #}
{% if page.has_next %}
<a href="/page/?num={{ page.next_page_number }}">下一页</a>
{% endif %}
###View-4
样式优化
.a{
width:34px;
height: 34px;
border:1px solid #e1e2e3;
cursor:pointer;
display: inline-block;
text-align: center;
line-height: 34px;
}
.b{
border:0;
width:34px;
height: 34px;
cursor:pointer;
display: inline-block;
text-align: center;
line-height: 34px;
}
.c{
height: 34px;
padding: 0 18px;
border:1px solid #e1e2e3;
line-height: 34px;
display: inline-block;
}
.c:hover{
border:1px solid #38f;
}
a{
text-decoration:none;
}
{#显示当前页数据#}
{% for user in page.object_list %}
{{ user.age }}--{{ user.name }}--
<img src="{% static user.head_pic.url %}" height="30px"/><br/>
{% endfor %}
{# 判断是否显示上一页链接 #}
{% if page.has_previous %}
<a href="/page/test/?num={{ page.previous_page_number }}">
<span class="c">上一页</span>
</a>
{% endif %}
{# 显示所有页号的链接 #}
{% for num in page.paginator.page_range %}
<a href="/page/test/?num={{ num }}">
{% if num == page.number %}
<span class="b">{{ num }}</span>
{% else %}
<span class="a">{{ num }}</span>
{% endif %}
</a>
{% endfor %}
{# 判断是否显示下一页链接 #}
{% if page.has_next%}
<a href="/page/test/?num={{ page.next_page_number }}">
<span class="c">下一页</span>
</a>
{% endif %}
<a href="{% url 'page:list' %}?num={{ page.paginator.num_pages }}">
<span class="c">末页</span>
</a>
作用:在View之前和之后执行,追加扩展View中的逻辑
常用作,View中冗余功能的抽取,如强制登录
定义中间件
class MyMiddleAware(MiddlewareMixin):
def __init__(self,get_response):#初始化
super().__init__(get_response)
print("init1")
#view处理请求前执行
def process_request(self,request):
print("request:",request)
#在process_request之后View之前执行
def process_view(self,request, view_func, view_args, view_kwargs):
print("view:",request,view_func,view_args,view_kwargs)
#view执行之后,响应之前执行
def process_response(self,request,response):
print("response:",request,response)
return response #必须返回response
#如果View中抛出了异常
def process_exception(self,request,ex):#View中出现异常时执行
print("exception:",request,ex)
**中间件中常用的两个过程:**process_request , process_response
注册中间件:每当请求时,所有中间件都会执行自己的生命周期
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
....
'middleware115.middlewares.MyMiddleware',#注册自定义中间件,尽量放在最后注册
]
强制登录实例
class MyMiddleAware2(MiddlewareMixin):
#如果验证成功,则什么一个不用做,否则返回HttpResponse即可响应请求(中断)
def process_request(self,request):#强制登录判断
if "login" not in request.path and "register" not in request.path:
#路径中如果没有"login" 和 "register" http://ip:port/xx/xxx/x
print("登录验证")
session = request.session #获取session
if session.get("login"): #判断是否有登录的标记
print("已登录")
else:
print("未登录")
#return render(request,"login.html") #未登录则,跳转登录页面
return redirect("xx:xx")
else:
print("正在登录") #如果路径中"login"则是登录动作本身
def process_response(self,request,response):
print("response:",request,response)
return response #持续返回响应
(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”
django解决办法:
1> 在form中添加{% csrf_token %},且使用post
2> django 的中间件 django.middleware.csrf.CsrfViewMiddleware
django的实现思路:
1>在请求响应时,生成一个随机的token(令牌),存放于form单的隐藏域和cookie中
2>当次form,发送请求时,会携带隐藏域令牌和cookie中的令牌,此时CsrfViewMiddleware 会先于View
执行,去判断两块令牌是否一致,验明正身
适合小型项目,快速构建自己的后台系统
1.创建管理员账户
python manage.py createsuperuser
2.注册model,在App的admin.py中
admin.site.register(Person)
admin.site.register(Passport)
admin.site.register(User)
admin.site.register(Order)
admin.site.register(Student)
admin.site.register(Course)
3.确保,在settins.py中安装了admin,其会扫描每个app的admin.py,进而支持后台管理
INSTALLED_APPS = [
'django.contrib.admin',
...
]
4.启动服务器
python manage.py runserver
5.访问后台管理页面:http://localhost:8000/admin
操作选项位置
@admin.register(User)#等价于 admin.site.register(User)
class UserAdmin(admin.ModelAdmin):
#操作选项位置:数据列表页中的Action选项
actions_on_top = True
actions_on_bottom = True
###定制2
定制列名
class User(models.Model):
name = models.CharField(max_length=20,)
def cName(self):
return self.name
cName.short_description ='姓名' #改列名
cName.admin_order_field = 'name' #保证改列名后依然可以排序
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
#在所有数据的列表页面,显示那些列,并使列名是中文 +++++
list_display = ['cName', 'gender','birth']
修改Model名
class User(models.Model):
...
class Meta:
verbose_name="用户" #修改Model名
verbose_name_plural="用户" #复数Model名:默认为"Users"
过滤查询栏
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
...
list_display = ['cName', 'gender','birth']
#为哪些属性定制右侧过滤栏
list_filter = ['name', 'gender','birth']
设置分页
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
...
list_per_page = 5 #每页显示多少条,分页展示数据
设置搜索框
class UserAdmin(admin.ModelAdmin):
search_fields = ['name'] #可以以那列为条件搜索,会增加搜索框
数据显示格式
class User(models.Model):
age = models.SmallIntegerField()
name = models.CharField(max_length=20)
birth = models.DateTimeField()
head_pic = models.ImageField(upload_to="c115")
def __str__(self): # 使得后台页面中的列表展示页面,显示结果为 str的返回值
return str(self.age)+" "+self.name+" "+datetime.strftime(self.birth,"%Y-%m-%d")
可操作的列
class UserAdmin(admin.ModelAdmin):
fields = [('name', 'pic'),'age'] #增加和修改页显示的列,且name和pic列在一行显示
列分组显示
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
#分组显示,组标题分别为 ‘基本信息’ 和 ‘头像’
fieldsets = (
('基本信息',{"fields":[('name','age')]}),
('头像',{"fields":["pic"]})
)
关联显示
class OrderInline(StackedInline):#TabularInline
model = Order #Order是否已经注册无所谓
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
inlines = [
OrderInline
]
可以快速定制前端的form表单,并提供参数接收,数据验证。功能看似很全面
但实则使用价值不大,因为有众多前端框架,可以更专业的定制前端逻辑,而django如此定制有些越俎代庖,而且会影响前端框架的建设,无法和前端人员或前端技术对接(如:easyUI,bootstrap等)
所以,此章节请自学,了解即可!
class UserForm(forms.ModelForm):
class Meta:
model = User2
#fields=["name","age","gender"]
exclude=["salary2"]
def aa(request):
print("goto a template")
render(request,"xx.html",{"form":UserForm()})
xx.html
<form action = "xxx" method="post">
{% csrf_token %}
<table>
{{ form }}
<tr>
<td colspan="2" align="center"><input type="submit" value="Submit" /></td>
</tr>
</table>
</form>
def test3(request):
form = UserForm(request.POST)
if form.is_valid():
print("表单数据合法")
#{'name': 'zzz', 'age': 18, 'gender': True, 'birth': datetime.date(2018, 11, 12),...}
data = form.cleaned_data
print(data)
return render(...)
作业
1.员工增加+头像+员工列表回显
2.员工列表分页
3.员工增删改+分页状态的保持
4.强登