python学习笔记 Django
一、什么是web框架
框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演。
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) client.send("<h1 style='color:red'>Hello, yuan</h1>".encode("utf8")) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8001)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
-----------------------------Do a web framework ourselves---------------------------
from wsgiref.simple_server import make_server def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>'] httpd = make_server('', 8080, application) print('Serving HTTP on port 8000...') # 开始监听HTTP请求: httpd.serve_forever()
整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,
我们只负责在更高层次上考虑如何响应请求就可以了。
application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。
Python内置了一个WSGI服务器,这个模块叫wsgiref
application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
//environ:一个包含所有HTTP请求信息的dict对象;
//start_response:一个发送HTTP响应的函数。
在application()函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。
start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每
个Header用一个包含两个str的tuple表示。
通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。
然后,函数的返回值b'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,
通过start_response()发送Header,最后返回Body。
from wsgiref.simple_server import make_server import time def f1(res): f1 = open("index1.html", "rb") data1 = f1.read() f1.close() return [data1] def f2(res): f2 = open("index2.html", "rb") data2 = f2.read() f2.close() return [data2] def showtime(res): t1 = open("showtime.html","rb") data3 = t1.read() times = time.strftime("%Y-%m-%d-%X",time.localtime()) data3 = str(data3,"utf8").replace("!!time!!",times) return [data3.encode("utf8")]
#利用元祖将所有页面和对应的函数放在一起.通过遍历查找
def routers(): urlpatterns=( ("/index1",f1), ("/index2",f2), ("/showtime",showtime) ) return urlpatterns def application(environ, start_response): path = environ["PATH_INFO"] start_response('200 OK', [('Content-Type', 'text/html')]) urlpatterns = routers() func = None for item in urlpatterns: if item[0] == path: func = item[1] break if func:return func(environ) else: return ["<h1>404</h1>".encode("utf8")] httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()
二、MVC和MTV模式
著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层;他们之间以一种插件似的,松耦合的方式连接在一起。
模型负责业务对象与数据库的对象(ORM),视图负责与用户的交互(页面),控制器(C)接受用户的输入调用模型和视图完成用户的请求。
Django的MTV模式本质上与MVC模式没有什么差别,也是各组件之间为了保持松耦合关系,只是定义上有些许不同,Django的MTV分别代表:
Model(模型):负责业务对象与数据库的对象(ORM)
Template(模版):负责如何把页面展示给用户
View(视图):负责业务逻辑,并在适当的时候调用Model和Template
此外,Django还有一个url分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
三 、django的流程和命令行工具
django实现流程
django #安装: pip3 install django 添加环境变量 #1 创建project django-admin startproject mysite ---mysite ---settings.py ---url.py ---wsgi.py ---- manage.py(启动文件) #2 创建APP python mannage.py startapp app01 #3 settings配置 TEMPLATES STATICFILES_DIRS=( os.path.join(BASE_DIR,"statics"), ) STATIC_URL = '/static/' # 我们只能用 STATIC_URL,但STATIC_URL会按着你的STATICFILES_DIRS去找#4 根据需求设计代码 url.py view.py #5 使用模版 render(req,"index.html") #6 启动项目 python manage.py runserver 127.0.0.1:8090 #7 连接数据库,操作数据 model.py
django的命令行工具
django-admin.py 是Django的一个用于管理任务的命令行工具,manage.py是对django-admin.py的简单包装,每一个Django Project里都会有一个mannage.py。
<1> 创建一个django工程 : django-admin.py startproject mysite
当前目录下会生成mysite的工程,目录结构如下:
- manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库等。
- settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
- urls.py ----- 负责把URL模式映射到应用程序。
<2>在mysite目录下创建blog应用: python manage.py startapp blog
<3>启动django项目:python manage.py runserver 8080
这样我们的django就启动起来了!当我们访问:http://127.0.0.1:8080/时就可以看到:
<4>生成同步数据库的脚本:python manage.py makemigrations
同步数据库: python manage.py migrate
注意:在开发过程中,数据库同步误操作之后,难免会遇到后面不能同步成功的情况,解决这个问题的一个简单粗暴方法是把migrations目录下
的脚本(除__init__.py之外)全部删掉,再把数据库删掉之后创建一个新的数据库,数据库同步操作再重新做一遍。
<5>当我们访问http://127.0.0.1:8080/admin/时,会出现:
所以我们需要为进入这个项目的后台创建超级管理员:python manage.py createsuperuser,设置好用户名和密码后便可登录啦!
<6>清空数据库:python manage.py flush
<7>查询某个命令的详细信息: django-admin.py help startapp
admin 是Django 自带的一个后台数据库管理系统。
<8>启动交互界面 :python manage.py shell
这个命令和直接运行 python 进入 shell 的区别是:你可以在这个 shell 里面调用当前项目的 models.py 中的 API,对于操作数据,还有一些小测试非常方便。
<9> 终端上输入python manage.py 可以看到详细的列表,在忘记子名称的时候特别有用。
四、Django URL (路由系统)
补充知识staitc文件夹
#STATIC文件一般保存在以下位置:
#1、STATIC_ROOT:在settings里面设置,一般用来放一些公共的js,css,images等。
#2、app的static文件夹,在每个app所在文夹均可以建立一个static文件夹,然后当运行collectstatic时,
# Django会遍历INSTALL_APPS里面所有app的static文件夹,将里面所有的文件复制到STATIC_ROOT。因此,
# 如果你要建立可复用的app,那么你要将该app所需要的静态文件放在static文件夹中。
# 也就是说一个项目引用了很多app,那么这个项目所需要的css,images等静态文件是分散在各个app的static文件的,比
# 较典型的是admin应用。当你要发布时,需要将这些分散的static文件收集到一个地方就是STATIC_ROOT。
#3、STATIC文件还可以配置STATICFILES_DIRS,指定额外的静态文件存储位置。
# STATIC_URL的含义与MEDIA_URL类似。
staitc文件 静态文件图片、 jquery文件存放在里面,为了防止各应用之间的干扰,所以static文件夹放在应用文件夹下面(blog项目就放blog文件夹下)。
创建static文件夹,使用里面的文件分几步:
第一、需要在seting文件中添加path路径,STAICFILES_DIRS=(os. path. join(BASE_DIR,"blog","static"),)注意逗号必须有。
django在启动的时候,会先到seting文件中读取路径。
#为了后端的更改不会影响前端的引入,避免造成前端大量修改 #django对引用名和实际名进行映射,引用时,只能按照引用名来,不能按实际名去找 #<script src="/statics/jquery-3.1.1.js"></script> #------error-----不能直接用,必须用STATIC_URL = '/static/': #<script src="/static/jquery-3.1.1.js"></script>
第二、可以在前端页面使用需要引入的文件
也可以用变量的形式,推荐此种方式
URL控制系统
URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。
Django 如何处理一个请求
当一个用户请求Django 站点的一个页面,下面是Django 系统决定执行哪个Python 代码使用的算法:
-
Django 确定使用根 URLconf 模块。通常,这是
ROOT_URLCONF
设置的值,但如果传入HttpRequest
对象拥有urlconf
属性(通过中间件设置),它的值将被用来代替ROOT_URLCONF
设置。 -
Django 加载该 Python 模块并寻找可用的
urlpatterns
。它是django.urls.path()
和(或)django.urls.re_path()
实例的序列(sequence)。 -
Django 会按顺序遍历每个 URL 模式,然后会在所请求的URL匹配到第一个模式后停止,并与
path_info
匹配。 -
一旦有 URL 匹配成功,Djagno 导入并调用相关的视图,这个视图是一个Python 函数(或基于类的视图 class-based view )。视图会获得如下参数:
-
一个
HttpRequest
实例。 -
如果匹配的 URL 包含未命名组,那么来自正则表达式中的匹配项将作为位置参数提供。
-
关键字参数由路径表达式匹配的任何命名部分组成,并由
django.urls.path()
或django.urls.re_path()
的可选kwargs
参数中指定的任何参数覆盖。Changed in Django 3.0:在旧版本里,带有
None
值的关键字参数也可以由未提供的命名部分组成。
-
-
如果没有 URL 被匹配,或者匹配过程中出现了异常,Django 会调用一个适当的错误处理视图。参加下面的错误处理( Error handling )。
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), ]
注意:
- 要从 URL 中取值,使用尖括号。
- 捕获的值可以选择性地包含转换器类型。比如,使用
<int:name>
来捕获整型参数。如果不包含转换器,则会匹配除了/
外的任何字符。 - 这里不需要添加反斜杠,因为每个 URL 都有。比如,应该是
articles
而不是/articles
。
一些请求的例子:
/articles/2005/03/
会匹配 URL 列表中的第三项。Django 会调用函数views.month_archive(request, year=2005, month=3)
。/articles/2003/
将匹配列表中的第一个模式,而不是第二个,因为模式是按顺序测试的,第一个是要通过的第一个测试。您可以自由地利用顺序插入像这样的特殊情况。在这里,Django将调用该函数views.special_case_2003(request)
/articles/2003
不匹配这些模式中的任何一个,因为每个模式都要求URL以斜杠结尾。./articles/2003/03/building-a-django-site/
会匹配 URL 列表中的最后一项。Django 会调用函数views.article_detail(request, year=2003, month=3, slug="building-a-django-site")
。
路径转换器
下面的路径转换器在默认情况下是有效的:
str
- 匹配除了'/'
之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。int
- 匹配 0 或任何正整数。返回一个int
。slug
- 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site
。uuid
- 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00
。返回一个UUID
实例。path
- 匹配非空字段,包括路径分隔符'/'
。它允许你匹配完整的 URL 路径而不是像str
那样匹配 URL 的一部分。
使用正则表达式
如果路径和转化器语法不能很好的定义你的 URL 模式,你可以可以使用正则表达式。如果要这样做,请使用 re_path()
而不是 path()
。
在 Python 正则表达式中,命名正则表达式组的语法是 (?P<name>pattern)
,其中 name
是组名,pattern
是要匹配的模式。
这里是先前 URLconf 的一些例子,现在用正则表达式重写一下:
from django.urls import path, re_path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail), ]
这实现了与前面示例大致相同的功能,除了:
- 将要匹配的 URLs 将稍受限制。比如,10000 年将不在匹配,因为 year 被限制长度为4。
- 无论正则表达式进行哪种匹配,每个捕获的参数都作为字符串发送到视图。
当从使用 path()
切换到 re_path()
(反之亦然),要特别注意,视图参数类型可能发生变化,你可能需要调整你的视图。
使用未命名的正则表达式组
还有命名组语法,例如 (?P<year>[0-9]{4})
,你也可以使用更短的未命名组,例如 ([0-9]{4})
。
不是特别推荐这个用法,因为它会更容易在匹配的预期含义和视图参数之间引发错误。
在任何情况下,推荐在给定的正则表达式里只使用一个样式。当混杂两种样式时,任何未命名的组都会被忽略,而且只有命名的组才会传递给视图函数。
参数传递
urls路径分发:
urlpatterns = [ path('admin/', admin.site.urls), path('chouti/', views.chouti), re_path('test/(\d{4})/(\d{2})',views.test) ]
html前端文件:
form 表单中的action 可以用简写,django 会把ip地址和端口号加上
<form action="/login/" method="post"> /相当于127.0.0.1:8080/login/ <p>用户<input type="text" name="user"></p> <p>密码<input type="password" name="pwd"></p> <p>兴趣<input type="checkbox" name="xinqu"> 爱好<input type="checkbox" name="aihao"></p> <p><input type="submit"></p> </form>
views视图文件:
通过request提取到前端form表单穿传过来的数据
def login(request): print(request.POST.get("user")) print(request.POST.get("pwd")) if request.method=="POST": return HttpResponse("suecss") return render(request,"login.html")
re_path(正则,视图函数,别名)
re_path("/p",showblog,name="reg")
urlpatterns = [ path('admin/', admin.site.urls), # path('chouti/', views.chouti), # re_path('test/(\d{4})/(\d{2})',views.test), re_path('login/', views.login, name='reg'), #reg就是别名 ]
这个reg就是别名,可以用在form函数中
通过{%url “reg” %} 来跟urls文件中的别名对应
render在渲染的时候会自动把reg匹配
<form action="{% url "reg" %}" method="post"> <p>用户<input type="text" name="user"></p> <p>密码<input type="password" name="pwd"></p> <p>兴趣<input type="checkbox" name="xinqu"> 爱好<input type="checkbox" name="aihao"></p> <p><input type="submit"></p> </form>
<form action="{%url "reg %"}">
等同于 action="127.0.0.1:8080/showblog"
好处在于路径灵活了,不至于牵一发而动全身,改一下showblog的名称html不受影响
URL分发
单独应用应该有自己的urls文件,解耦合,某个应用问题不会干扰全局。
利用系统自带include()创建url分发。
比如blog应用下创建urls文件:
先在seting文件中urls中添加path("blog/",include("blog.urls")),
然后在blog文件夹下添加urls文件,里面写上对应的路径视图
path("index/",views.login)
此时再访问index就不能用127.0.0.1:8080/index,而是得加上blog 127.0.0.1:8080/blog/index
五、Django views视图函数
http请求中产生两个核心对象:
http请求:HttpRequest对象
http响应:HttpResponse对象
1、HttpRequest对象的属性和方法:
path 请求页面的全路径,不包括域名
#http://127.0.0.1:8080/blog/login/ def login(request): print(request.path)
#打印结果 /blog/login/
method 判断前端以哪种方式向服务器发送请求。全大写表示。例如
服务器收到空的POST请求的情况也是可能发生的,也就是说,表单form通过HTTP POST方法提交请求,但是表单中可能没有数据,因此不能使用 if request.POST来判断是否使用了HTTP POST 方法;应该使用 if req.method=="POST"
def login(request): if request.method=="POST": return HttpResponse("suecss") elif request.method=="GET": return render(request,"login.html")
GET: 包含所有HTTP GET参数的类字典对象
POST: 包含所有HTTP POST参数的类字典对象 比如:
def login(request): print(request.POST)
这样就可以通过request.POST.get("pwd") 获取到对应的值
方法:request.POST.getlist('')
以列表的形式提取key对应的多个值
<p>兴趣 电影<input type="checkbox" name="hoby" value="moive"> 唱歌<input type="checkbox" name="hoby" value="music"></p>
def login(request): print(request.POST.getlist('hoby'))
COOKIES: 包含所有cookies的标准Python字典对象;keys和values都是字符串。
后面补充
# FILES: 包含所有上传文件的类字典对象;FILES中的每一个Key都是<input type="file" name="" />标签中
name属性的值,FILES中的每一个value同时也是一个标准的python字典对象,包含下面三个Keys: # # filename: 上传文件名,用字符串表示 # content_type: 上传文件的Content Type # content: 上传文件的原始内容 # # # user: 是一个django.contrib.auth.models.User对象,代表当前登陆的用户。如果访问用户当前 # 没有登陆,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。你 # 可以通过user的is_authenticated()方法来辨别用户是否登陆: # if req.user.is_authenticated();只有激活Django中的AuthenticationMiddleware # 时该属性才可用 # # session: 唯一可读写的属性,代表当前会话的字典对象;自己有激活Django中的session支持时该属性才可用。
方法 get_full_path()
获取的路径包含了get请求的信息
比如:http://127.0.0.1:8000/blog/login/?name=123
def login(request): print(request.path) print(request.get_full_path())
get_full_path()与path的区别
第一: get_full_path()是一个方法,path属于request的一个属性
第二: get_full_path()获取的值包含了get请求的信息,而path获取的就是纯路径
2、HttpResponse对象:
对于HttpRequest对象来说,是由django自动创建的,但是,HttpResponse对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse对象。
HttpResponse类在django.http.HttpResponse
在HttpResponse对象上扩展的常用方法:
页面渲染:render()
render(request, "html文件", context)
context是一个字典形式的参数,render会将context内的参数渲染到Template模板中
def showinfo(request): name = 'liqinsan' return render(request, "index.html", {"name": name})
<body> <h1>hello {{ name }}</h1> </body>
render_to_response()与render
区别在于不用写request,不推荐这种方式,容易出现bug
补充 : locals()方法
def showinfo(request): name = 'liqinsan' return render(request, "index.html", locals())
达到的效果跟上面的一样
<body> <h1>{{name}} {{request.method}}</h1> </body>
前端的参数名称必须跟 视图函数中的变量名保持一致才能使用locals方法
同时request也是一个局部变量,也可以被前端使用
页面跳转:redirect(
"路径"
)
1 、如果 render的页面需要模板语言渲染,需要的将数据库的数据加载到html,那么所有的这一部分 除了写在showinfo的视图函数中,必须还要写在login中,代码重复,没有解耦.
2、 最重要的是: url没有跳转到/showinfo/, 而是还在/login/,所以当刷新后 页面又会重新回到登录页面,实际情况是登录以后应该进入个人首页.
name = '' def login(request): global name name = request.POST.get("user") if request.method=="POST": return redirect("/blog/showinfo/")
#return render(request,"/blog/showinfo.html") elif request.method=="GET": return render(request,"login.html") def showinfo(request): user = name return render(request, "index.html", locals())
html利用了别名 {% url "reg"%} reg实在path中设置的.
{#------------------login.html #} <form action="{% url "reg" %}" method="post"> <p>用户<input type="text" name="user"></p> <p>密码<input type="password" name="pwd"></p> <p>兴趣 电影<input type="checkbox" name="hoby" value="moive"> 唱歌<input type="checkbox" name="hoby" value="music"></p> <p><input type="submit"></p> </form>
{# ---------------index.html #}
<body> <h1>hello {{ user }}</h1> </body>
六、Template基础
模板系统的介绍
你可能已经注意到我们在例子视图中返回文本的方式有点特别。 也就是说,HTML被直接硬编码在 Python代码之中。
def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
尽管这种技术便于解释视图是如何工作的,但直接将HTML硬编码到你的视图里却并不是一个好主意。 让我们来看一下为什么:
-
对页面设计进行的任何改变都必须对 Python 代码进行相应的修改。 站点设计的修改往往比底层 Python 代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更设计,那将会方便得多。
-
Python 代码编写和 HTML 设计是两项不同的工作,大多数专业的网站开发环境都将他们分配给不同的人员(甚至不同部门)来完成。 设计者和HTML/CSS的编码人员不应该被要求去编辑Python的代码来完成他们的工作。
-
程序员编写 Python代码和设计人员制作模板两项工作同时进行的效率是最高的,远胜于让一个人等待另一个人完成对某个既包含 Python又包含 HTML 的文件的编辑工作。
基于这些原因,将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用 Django的 模板系统 (Template System)来实现这种模式,这就是本章要具体讨论的问题。
一模版的组成
组成:HTML代码+逻辑控制代码
二 逻辑控制代码的组成
1 变量(使用双大括号来引用变量):
语法格式: {{var_name}}
------Template和Context对象
>>> python manage.py shell (进入该django项目的环境) >>> from django.template import Context, Template >>> t = Template('My name is {{ name }}.') >>> c = Context({'name': 'Stephane'}) >>> t.render(c) 'My name is Stephane.' # 同一模板,多个上下文,一旦有了模板对象,你就可以通过它渲染多个context,无论何时我们都可以 # 像这样使用同一模板源渲染多个context,只进行一次模板创建然后多次调用render()方法渲染会 # 更为高效: # Low for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print (t.render(Context({'name': name}))) # Good t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print (t.render(Context({'name': name})))
Django 模板解析非常快捷。 大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。 这和基于 XML 的模板引擎形成鲜明对比,那些引擎承担了 XML 解析器的开销,且往往比 Django 模板渲染引擎要慢上几个数量级。
from django.shortcuts import render,HttpResponse from django.template.loader import get_template #记得导入 # Create your views here. import datetime from django.template import Template,Context # def current_time(req): #原始的视图函数 # now=datetime.datetime.now() # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) # def current_time(req): #django模板修改的视图函数 # now=datetime.datetime.now() # t=Template('<html><body>现在时刻是:<h1 style="color:red">{{current_date}}</h1></body></html>') # c=Context({'current_date':now}) # html=t.render(c) # return HttpResponse(html)
# def showtime(request):
# t = datetime.datetime.now()
# T = get_template('time.html') #get_template实例化的对象,用render方法只能传入字典,弃用了Context方法
# html = T.render({"t": t})
# return HttpResponse(html)
#另一种写法(推荐) def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
------深度变量的查找(万能的句点号)
在到目前为止的例子中,我们通过 context 传递的简单参数值主要是字符串,然而,模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。
在 Django 模板中遍历复杂数据结构的关键是句点字符 (.)。
# 1 首先,句点可用于访问列表索引,例如: >>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) 'Item 2 is carrots.' #2 假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点: >>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) 'Sally is 43 years old.' # 3同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 #year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性: >>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) 'The month is 5 and the year is 1993.' # 4.这个例子使用了一个自定义的类,演示了通过实例变量加一点(dots)来访问它的属性,这个方法适 # 用于任意的对象。 >>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name=self.first_name, self.last_name = first_name, self.last_name = last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) 'Hello, John Smith.' # 5.点语法也可以用来引用对象的方法。 例如,每个 Python 字符串都有 upper() 和 isdigit() # 方法,你在模板中可以使用同样的句点语法来调用它们: >>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) 'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) '123 -- 123 -- True' # 注意这里调用方法时并* 没有* 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的 # 方法。
------变量的过滤器(filter)的使用
语法格式: {{obj|filter:param}}
# 1 add : 给变量加上相应的值 # # 2 addslashes : 给变量中的引号前加上斜线 # # 3 capfirst : 首字母大写 # # 4 cut : 从字符串中移除指定的字符 # # 5 date : 格式化日期字符串 # # 6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值 # # 7 default_if_none: 如果值是None,就替换成设置的默认值,否则就使用本来的值 #实例: #value1="aBcDe" {{ value1|upper }}<br> #value2=5 {{ value2|add:3 }}<br> #value3='he llo wo r ld' {{ value3|cut:' ' }}<br> #import datetime #value4=datetime.datetime.now() {{ value4|date:'Y-m-d' }}<br> #value5=[] {{ value5|default:'空的' }}<br> #value6='<a href="#">跳转</a>' {{ value6 }} #直接这样书写前端会以文本的形式显示value6 的全部值 {% autoescape off %} #第一种方法正确渲染value6标签 {{ value6 }} {% endautoescape %} {{ value6|safe }} #第二种方法正确渲染value6标签 {{ value6|striptags }} #删除html标签,得到 --跳转 两个字符
#value7='1234' {{ value7|filesizeformat }} #将值格式化为文件尺寸 {{ value7|first }}<br> #第一个字符 {{ value7|length }}<br> #长度 {{ value7|slice:":-1" }}<br> #切片 #value8='http://www.baidu.com/?a=1&b=3' {{ value8|urlencode }}<br> #解决url中特殊字符的传输问题 value9='hello I am yuan'
2、 标签(tag)的使用(使用大括号和百分比的组合来表示使用tag)
语法格式: {% tags %}
------{% if %} 的使用
{% if %}标签计算一个变量值,如果是“true”,即它存在、不为空并且不是false的boolean值,系统则会显示{% if %}和{% endif %}间的所有内容
{% if num >= 100 and 8 %} {% if num > 200 %} <p>num大于200</p> {% else %} <p>num大于100小于200</p> {% endif %} {% elif num < 100%} <p>num小于100</p> {% else %} <p>num等于100</p> {% endif %} {% if %} 标签接受and,or或者not来测试多个变量值或者否定一个给定的变量 {% if %} 标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的: {% if obj1 and obj2 or obj3 %}
------{% for %}的使用
{% for %}标签允许你按顺序遍历一个序列中的各个元素,每次循环模板系统都会渲染{% for %}和{% endfor %}之间的所有内容
<ul> {% for obj in list %} <li>{{ obj.name }}</li> {% endfor %} </ul> #在标签里添加reversed来反序循环列表: {% for obj in list reversed %} ... {% endfor %} #{% for %}标签可以嵌套: {% for country in countries %} <h1>{{ country.name }}</h1> <ul> {% for city in country.city_list %} <li>{{ city }}</li> {% endfor %} </ul> {% endfor %} #系统不支持中断循环,系统也不支持continue语句,{% for %}标签内置了一个forloop模板变量, #这个变量含有一些属性可以提供给你一些关于循环的信息 1,forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1: 可以作为序号 {% for item in todo_list %} <p>{{ forloop.counter }} : {{ item }}</p> {% endfor %} 2,forloop.counter0 类似于forloop.counter,但它是从0开始计数,第一次循环设为0 3,forloop.revcounter 倒数 4,forloop.revcounter0 倒数到0 5,forloop.first当第一次循环时值为True,在特别情况下很有用: {% for object in objects %} {% if forloop.first %}
<li class="first">
{% else %}
<li></li>
{% endif %} {{ object }} {% endfor %} # 富有魔力的forloop变量只能在循环中得到,当模板解析器到达{% endfor %}时forloop就消失了 # 如果你的模板context已经包含一个叫forloop的变量,Django会用{% for %}标签替代它 # Django会在for标签的块中覆盖你定义的forloop变量的值 # 在其他非循环的地方,你的forloop变量仍然可用 #{% empty %} {{li }} {% for i in li %} <li>{{ forloop.counter0 }}----{{ i }}</li> {% empty %} <li>this is empty!</li> {% endfor %} # [11, 22, 33, 44, 55] # 0----11 # 1----22 # 2----33 # 3----44 # 4----55
------{%csrf_token%}:csrf_token标签
用于生成csrf_token的标签,用于防治跨站攻击验证。注意如果你在view的index里用的是render_to_response方法,不会生效
其实,这里是会生成一个input标签,和其他表单标签一起提交给后台的。
<form action="/showtime/" method="post"> <input type="text"> <input type="submit"> </form>
当form标签 用post模式传递数据时,需要CSRF验证。
Django对第一次提交post数据时会验证KEY,这个KEY是server端生成发给前端的。
<form action="/showtime/" method="post"> <input type="text"> <input type="submit"> {% csrf_token %} </form>
所以加上 {% csrf_token %},此时render渲染的时候就会从server端生成一个KEY发给前端,如下图:
这样就可以传post数据到server端了。
------{% url %}: 引用路由配置的地址,前面写过别名
<form action="{% url "bieming"%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>
------{% with %}:用更简单的变量名替代复杂的变量名
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
------{% verbatim %}: 禁止render
当前端需要显示{{hello}}时,Django会作为变量给渲染了,所以加上{% verbatim %}来禁止render当变量给渲染了
{% verbatim %}
{{ hello }}
{% endverbatim %}
------{% load %}: 加载标签库
3 自定义过滤器filter和自定义标签simple_tag
------a、在app中创建templatetags模块(必须的)
------b、创建任意 .py 文件,如:my_tags.py
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 @register.simple_tag def simple_tag_multi(v1,v2): return v1 * v2 @register.simple_tag def my_input(id,arg): result = "<input type='text' id='%s' class='%s' />" %(id,arg,) return mark_safe(result)
------c、在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py :{% load my_tags %}
------d、使用simple_tag和filter(如何调用)
-------------------------------.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 %} 传参数不用引号,切记
------e、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
注意:
filter参数只能传一个,simple可以传多个
filter可以用在if等语句后,simple_tag不可以
{% if num|filter_multi:30 > 100 %} {{ num|filter_multi:30 }} {% endif %}
extend模板继承
------extend(继承)模板标签
创建一个网页,肯定会有很多的页面比如下面的管理系统
这个网页中,分为三个区域,上面的标题区域,左边的菜单栏,右边的内容区
在网页开发过程中,上面的标题和右边的菜单对于每个页面都是一样的,如果直接写需要将首页的内容复制然后在基础上修改得到子页面
Django给我们提供了extend(继承)模板标签,这样在开发子页面的时候可以继承首页的内容,通过块标签 {% block 块名称(自定义)%} 区分出哪些需要修改
例如

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ margin: 0; padding: 0; } .nav{ line-height: 40px; width: 100%; background-color: yellowgreen; color: white; font-size: 20px; text-align: center; } .left{ width: 20%; min-height: 600px; overflow: auto; background-color: gainsboro; float: left; } .manage{ text-align: center; padding: 15px 0; margin: 20px 0; font-size: 18px; } .content{ width: 80%; min-height: 600px; background-color: #f3f0f0; float: left; text-align: center; } a{ text-decoration: none; color: black; } h1{ padding: 50px 0; text-align: center; } </style> </head> <body> <div class="outer"> <div class="nav">标题</div> <div class="left"> <div class="student manage"><a href="/backend/">首页</a></div> <div class="student manage"><a href="/student/">学生管理</a></div> <div class="teacher manage"><a href="">老师管理</a></div> <div class="course manage"><a href="">课程管理</a></div> <div class="clases manage"><a href="">班级管理</a></div> </div> <div class="content"> {% block content %} <h1>欢迎进入管理系统</h1> {% endblock %} </div> </div> </body> </html>
然后我们在开发学生管理页面的时候可以利用extend
{% extends 'index.html' %}必须写在第一行
student.html代码如下:
{% extends 'index.html' %} {% block content %} {% for i in student_list %} <h2>{{ i }}</h2> {% endfor %} {% endblock %}
如果此时也想继承父类content里面的内容 可以在子页面中使用
{{block.super}}
{% extends 'index.html' %} {% block content %} {{ block.super }} {% for i in student_list %} <h2>{{ i }}</h2> {% endfor %} {% endblock %}
extend的工作原理:
在加载 student.html 模板时,模板引擎发现了 {% extends 'index.html' %} 标签,注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 index.html 。
此时,模板引擎注意到index.html 中的一个 {% block content %} 标签,并用子模板的内容替换这些 block 。因此,网页的内容区一块将由studnet.html文件中的模块
{% block content %}替换。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:
<1> 创建 base.html 模板,在其中定义站点的主要外观。 这些都是不常修改甚至从不修改的部分。 <2> 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对base.html 进行拓展, 并包含区域特定的风格与设计。 <3> 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展相应的区域模板。
这个方法可最大限度地重用代码,并使得向公共区域(如区域级的导航)添加内容成为一件轻松的工作。
以下是使用模板继承的一些诀窍:
<1>如果在模板中使用 {% extends %} ,必须保证在第一行。 否则,模板继承将不起作用。
<2>一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此
你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话说,钩子越
多越好。
<3>如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模
板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
<4>不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。
也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个
相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
------include 模板标签
在讲解了模板加载机制之后,我们再介绍一个利用该机制的内建模板标签: {% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
这个标签可以用于插入一些广告或者其他模板页面
tes.html:
<body> <h1>学技术来懒翔</h1> </body>
student.html: 用{% include "test.html" %} 标签将test.html添加
{% extends 'index.html' %} {% block content %} {{ block.super }} {% for i in student_list %} <h2>{{ i }}</h2> {% endfor %} {% include "test.html" %} {% endblock %}
extend模板继承和include模板插入 最终的目的是为了减少冗余代码,提高开发效率
七、Models
数据库表与表之间的关系:
1、一对多 ,通过外键dep_id建立关系
一个人只会属于一个部分,一个部门可以有多个人,所以是一对多的关系
主表(部门表):因为不依赖别的表
子表(员工表):
2、多对多
一本书可以有多个作者写,一个作者可以写很多本书,所以就是多对多关系
书籍表:
作者表:
关系表: book_id是书籍表的外键,auth_id是作者表的外键
3、一对一
将个人信息中不常用的信息放到另外一个表,建立一对一的关系
把p_id字段设置成为unique唯一键,这样p_id的值就是唯一的
通过外键p_id 去建立一对一的关系。
员工表:
信息表:
数据库的配置
1 django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
2 mysql驱动程序
- MySQLdb(mysql python) -- python2
- mysqlclient
- MySQL
- PyMySQL(纯python的mysql驱动程序) -- python3
3 在django的项目中会默认使用sqlite数据库,在settings里有如下设置:
如果我们想要更改数据库,需要修改如下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'books', #你的数据库名称 'USER': 'root', #你的数据库用户名 'PASSWORD': '', #你的数据库密码 'HOST': '', #你的数据库主机,留空默认为localhost 'PORT': '3306', #你的数据库端口 } }
注意:
NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。 设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。见下方表创建.
ORM(对象关系映射)
用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。
优点: 1 ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来。
2 可以避免一些新手程序猿写sql语句带来的性能问题。
比如 我们查询User表中的所有字段: 新手可能会用select * from auth_user,这样会因为多了一个匹配动作而影响效率的。
缺点:1 性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载登来减轻这个问题。效果很显著。
2 对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。
3 通过QuerySet的query属性查询对应操作的sql语句
表(模型)的创建:
第一步、先创建一个类,类必须继承models.Model,这样python才知道这是一个数据库类
原生sql 语法是这样的 create table Book(name varchar(20),pub_date date, price int)
在ORM中 是利用类的属性来表示关键字和数据类型
一个数据表 对应一个类,一个实例化对象对应一条表记录.
class Book(models.Model): name = models.CharField(max_length=100) pub_date = models.DateField() price=models.IntegerField()
第二步、 生成同步数据库的脚本:python manage.py makemigrations
第三步、 同步数据库:python manage.py migrate
注意1:记得在settings里的INSTALLED_APPS中加入'app01',然后再同步数据库。
注意2: models.ForeignKey("Publish") & models.ForeignKey(Publish)
分析代码:
<1> 每个数据模型都是django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法。并提供了一个简介漂亮的定义数据库字段的语法。
<2> 每个模型相当于单个数据库表(多对多关系例外,会多生成一张关系表),每个属性也是这个表中的字段。属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)。大家可以留意下其它的类型都和数据库里的什么字段对应。
<3> 模型之间的三种关系:一对一,一对多,多对多。
一对一:实质就是在主外键(author_id就是foreign key)的关系基础上,给外键加了一个UNIQUE=True的属性;
一对多:就是主外键关系;(foreign key)
多对多:(ManyToManyField) 自动创建第三张表(当然我们也可以自己创建第三张表:两个foreign key)
<4> 模型常用的字段类型参数

<1> CharField #字符串字段, 用于较短的字符串. #CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. <2> IntegerField #用于保存一个整数. <3> FloatField # 一个浮点数. 必须 提供两个参数: # # 参数 描述 # max_digits 总位数(不包括小数点和符号) # decimal_places 小数位数 # 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: # # models.FloatField(..., max_digits=5, decimal_places=2) # 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: # # models.FloatField(..., max_digits=19, decimal_places=10) # admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField # 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; # 自定义一个主键:my_id=models.AutoField(primary_key=True) # 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> BooleanField # A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField # 一个容量很大的文本字段. # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField # 一个日期字段. 共有下列额外的可选参数: # Argument 描述 # auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. # auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间. #(仅仅在admin中有意义...) <9> DateTimeField # 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> ImageField # 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, # 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField # 一个文件上传字段. #要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, #该格式将被上载文件的 date/time #替换(so that uploaded files don't fill up the given directory). # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . #注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: #(1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. # (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 # WEB服务器用户帐号是可写的. #(2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django # 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). # 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField # 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField # 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 # 没有返回404响应). # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField # "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs # 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 # 以前的 Django 版本,没有任何办法改变50 这个长度. # 这暗示了 db_index=True. # 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate # the slug, via JavaScript,in the object's admin form: models.SlugField # (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField #一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField # 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. # 参数 描述 # path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. # Example: "/home/images". # match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. # 注意这个正则表达式只会应用到 base filename 而不是 # 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. # recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. # 这三个参数可以同时使用. # match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: # FilePathField(path="/home/images", match="foo.*", recursive=True) # ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16># CommaSeparatedIntegerField # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
<5> Field重要参数
<1> null : 数据库中字段是否可以为空 <2> blank: django的 Admin 中添加数据时是否可允许空值 <3> default:设定缺省值 <4> editable:如果为假,admin模式下将不能改写。缺省为真 <5> primary_key:设置主键,如果没有设置django创建表时会自动加上: id = meta.AutoField('ID', primary_key=True) primary_key=True implies blank=False, null=False and unique=True. Only one primary key is allowed on an object. <6> unique:数据唯一 <7> verbose_name Admin中字段的显示名称 <8> validator_list:有效性检查。非有效产生 django.core.validators.ValidationError 错误 <9> db_column,db_index 如果为真将为此字段创建索引 <10>choices:一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择。 如SEX_CHOICES= (( ‘F’,'Female’),(‘M’,'Male’),) gender = models.CharField(max_length=2,choices = SEX_CHOICES)
单表的操作(增删改查):
一个数据表 对应一个类,一个实例化对象对应一条表记录.
一、增:
第一种方式 ,通过类Book去实例化一个对象
b = Book(name='python基础', price='22', pub_data='2017-12-12', author='py') #通过实例对象来添加数据 b.save() #save方法必写,实例才会保存到数据库
第二种方式,调用create方法创建记录
Book.objects.create(name='PHP', price='32', pub_data='2017-12-10', author='php')
二、删:
Book.objects.filter(author='py').delete()
三、改:
第一种方式;推荐这种方法
Book.objects.filter(name='python基础').update(price='99')
第二种方式
b = Book.objects.get(author="py") #b是一个QuerySet对象,是django特有的对象集合 b.price = 44 b.save()
注意:
<1> 第一种方式修改不能用get的原因是:update是QuerySet对象的方法。而get返回的是一个model对象,它没有update方法。
filter返回的是一个QuerySet对象,可能有多条匹配的数据(filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)。
<2>在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
在seting文件中加入LOGGING可以显示每次操作对应的原生SQL语句。

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
四、查:
# 查询相关API:
# <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象
# <2>all(): 查询所有结果
# <3>get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
#-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()--------
# <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列
# <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象
# <6>order_by(*field): 对查询结果排序
# <7>reverse(): 对查询结果反向排序
# <8>distinct(): 从返回结果中剔除重复纪录
# <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
# <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。
# <11>first(): 返回第一条记录
# <12>last(): 返回最后一条记录
# <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。
惰性机制:
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:
<1> 可迭代的
<2> 可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一个行对象 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) # print(objs[1:4]) # print(objs[::-1])
QuerySet的高效使用:
<1>Django的queryset是惰性的 Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得 到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave") 上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数, 这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql. 为了验证这些,需要在settings里加入 LOGGING(验证方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行 (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset, 你不需要重复运行通用的查询。 obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) ## models.Book.objects.filter(id=3).update(title="GO") ## obj_new=models.Book.objects.filter(id=3) # for i in obj: # print(i) #LOGGING只会打印一次 <4> 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4) # exists()的检查可以避免数据放入queryset的cache。 if obj.exists(): print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。 objs = Book.objects.all().iterator() # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 for obj in objs: print(obj.name) #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 for obj in objs: print(obj.name) #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
模糊查询 万能双下划线 __
1、大于小于等于
book_list = Book.objects.filter(price__gt=40).values('name','price')
2、raw sql中的like in等模糊条件
book_list = Book.objects.filter(name__contains='P').values('name','price') #查询包含了大P的数据

#---------------了不起的双下划线(__)之单表条件查询---------------- # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # # startswith,istartswith, endswith, iendswith, #----------------了不起的双下划线(__)之多表条件关联查询--------------- # 正向查找(条件) # ret3=models.Book.objects.filter(title='Python').values('id') # print(ret3)#[{'id': 1}] #正向查找(条件)之一对多 ret4=models.Book.objects.filter(title='Python').values('publisher__city') print(ret4) #[{'publisher__city': '北京'}] #正向查找(条件)之多对多 ret5=models.Book.objects.filter(title='Python').values('author__name') print(ret5) ret6=models.Book.objects.filter(author__name="alex").values('title') print(ret6) #注意 #正向查找的publisher__city或者author__name中的publisher,author是book表中绑定的字段 #一对多和多对多在这里用法没区别 # 反向查找(条件) #反向查找之一对多: ret8=models.Publisher.objects.filter(book__title='Python').values('name') print(ret8)#[{'name': '人大出版社'}] 注意,book__title中的book就是Publisher的关联表名 ret9=models.Publisher.objects.filter(book__title='Python').values('book__authors') print(ret9)#[{'book__authors': 1}, {'book__authors': 2}] #反向查找之多对多: ret10=models.Author.objects.filter(book__title='Python').values('name') print(ret10)#[{'name': 'alex'}, {'name': 'alvin'}] #注意 #正向查找的book__title中的book是表名Book #一对多和多对多在这里用法没区别
多表操作
创建外键
#通过models.ForeignKey创建外键 关联主表 class Book(models.Model): name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() publish = models.ForeignKey('Publish', on_delete=models.CASCADE) class Publish(models.Model): name = models.CharField(max_length=20) city = models.CharField(max_length=32)
ForeignKey参数说明:
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值
CASCADE:此值设置,是级联删除。
PROTECT:此值设置,是会报完整性错误。
SET_NULL:此值设置,会把外键设置为null,前提是允许为null。
SET_DEFAULT:此值设置,会把设置为外键的默认值。
SET():此值设置,会调用外面的值,可以是一个函数。
一般情况下使用CASCADE就可以了。
那么,这个时候一个publish就会对应多个book,属于一对多的类型。
当我们查询一个组有那些用户的时候,就会用到当前的外健,
创建记录 并且,在class中定义了foreignKey之后,group还不存在的同时,user表也因为约束的原因,不能被进行创建
删除记录并且,在class中定义了foreignKey之后,user中记录存在的同时,group表中的记录也因为约束的原因,不能被进行删除
(1)一对多
一、增:
第一种方式
Book.objects.create(name='linux运维', price='32', pub_data='2017-12-10', author='php', publish_id=2)
publish_id是外键(_id是django自动添加的),对应的publish表一定要有值才能创建书本信息,要不然会报错。
第二种方式 publish=publish_obj没有对外键直接赋值,而是先找到 ‘北京出版社’对应的对象,然后传给外键publish
publish_obj = Publish.objects.filter(name="北京出版社")[0] # publish=publish_obj没有对外键直接赋值,而是先找到 ‘北京出版社’对应的id,然后赋值给publish_id Book.objects.create(name='GO', price='23', pub_data='2017-5-1', author='go', publish=publish_obj)
注意这里是publish不是publish_id。
二、查:
正向查询 通过子表查主表 方式是通过外键
反向查询 通过主表查子表 方式是通过表名
第一种方式
def addbook(request): #通过python查询到对应出版社信息
#正向查询,通过书籍对应的出版社对象,再取对应的出版社信息
book_obj = Book.objects.get(name='python') #查到到书名为python的实例对象 print(book_obj.name) #对象可以调用自己的属性name print(book_obj.price) #对象可以调用自己的属性price #一对多 book_obj.publish 一定是一个对象 print(book_obj.publish) #Publish object (1) 是一个publish对象 #此处book_obj.publish就是python对应publish_id=1出版社的一个对象 print(type(book_obj.publish)) #<class 'app01.models.Publish'> #那么通过对象就可以调用出版社对象的属性 print(book_obj.publish.name) #人民出版社 print(book_obj.publish.city) #北京
#查询人民出版社出的所有书籍 pub_obj = Publish.objects.filter(name='人民出版社')[0] ret = Book.objects.filter(publish=pub_obj).values('name') #publish不是出版社的类,而是外键,通过外键找到对象 print(ret) #<QuerySet [{'name': 'python'}, {'name': 'java'}]>
第二种方式
#查询人民出版社出的所有书籍
#pub_obj是人民出版社的对象,找到所有关联书籍对象 book_set.all() pub_obj = Publish.objects.filter(name='人民出版社')[0] print(pub_obj.book_set.all()) #<QuerySet [<Book: Book object (1)>, <Book: Book object (4)>]> # print(type(pub_obj.book_set.all())) #<class 'django.db.models.query.QuerySet'> print(pub_obj.book_set.all().values('name','price')) #<QuerySet [{'name': 'python', 'price': 32}, {'name': 'java', 'price': 91}]>
pub_obj可以对应多个表格,所以这里是一个set集合,book就是Book表(此处不区分大小写),book_set.all(),就是根据外键找到所有跟pub_obj关联的书籍对象
第三种方式 双下划线
# filter(__) values(__) 双下划线__ # publish__name中的publish是外键字段,并非是publish类 # 查询北京出版社出版了哪些书籍--------通过子表查主表>>>正向查询 ret = Book.objects.filter(publish__name="北京出版社").values("name") print(ret) # <QuerySet [{'name': 'GO'}, {'name': 'JAVA'}]> # python数对应的出版社名称--------通过主表查子表>>>反向查询 # book__name中的book是类的名字,反向查询的时候Publish类没有外键,所以用的book是类 ret2 = Publish.objects.filter(book__name='PYTHON').values('name') print(ret2) # <QuerySet [{'name': '上海出版社'}]>
# 双下划线也可以用于values中,在筛选出来结果中查找publish_name ret3 = Book.objects.filter(name='python').values("publish__name") print(ret3) # <QuerySet [{'publish__name': '上海出版社'}]> # 查询在北京的出版社出过的书籍 ret4 = Book.objects.filter(publish__city="北京").values('name') print("ret4", ret4) # ret4 <QuerySet [{'name': 'GO'}, {'name': 'JAVA'}]> # 查询某时间范围内出过的书籍 ret5 = Book.objects.filter(pub_data__gt='2019-8-1', pub_data__lt='2020-9-15').values('name') print(ret5) # 查询时间范围内哪些出版社出过多少书籍 ret6 = Publish.objects.filter(book__pub_data__gt='2019-8-1', book__pub_data__lt='2020-9-15').count() print('ret6', ret6) # ret6 2
(2)多对多
models.ManyToManyField('Author') django会自动创建一个关联表 app01_book_author
class Book(models.Model): name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() publish = models.ForeignKey('Publish', on_delete=models.CASCADE) authors = models.ManyToManyField('Author') #建立多对多的关系 django会自动创建一个app01_book_author的关联表 class Author(models.Model): name = models.CharField(max_length=20) age = models.IntegerField(default=20)
# 通过对象的方式绑定关系 book_obj = Book.objects.get(id=9) print('authors:', book_obj.authors.all()) # authors: <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]> print('authors.type:', type(book_obj.authors.all())) # authors.type: <class 'django.db.models.query.QuerySet'> #通过书籍关联作者 正向关系 book_obj = Book.objects.get(id=1) author_obj = Author.objects.all() book_obj.authors.all() # 这是一个集合对象,里面关联的是书本对象的作者信息 book_obj.authors.add(*author_obj) # 增加绑定关系,可以是单个,也可以是集合,如果是集合需要加星号*在参数前 book_obj.authors.remove(*author_obj) # 解除所有的与作者的绑定关系 book_obj.authors.remove(1) # 还可以根据id接触对应的关系,括号里的数字代表作者的id # 通过作者关联书籍,反向关系,没有authors键需要用book_set的集合 author_obj = Author.objects.get(id=3) book_obj = Book.objects.get(id=10) author_obj.book_set.add(book_obj) # 增加绑定关系,可以是单个,也可以是集合,如果是集合需要加星号*在参数前 author_obj.book_set.remove(10) # 还可以根据id接触对应的关系,括号里的数字代表书籍的id
#正向查询 #区分正向查询和反向的话,看哪个表里面是关联的键,MangToMangFile book_obj = Book.objects.get(id=9) #可以通过authors键找到书籍关联的作者信息 print(book_obj.authors.values('name')) # <QuerySet [{'name': 'xiaowei'}, {'name': 'shaoping'}]> #反向查询 author_obj = Author.objects.get(id=1) #方向因为作者表里面没有MangToMangFile,所以跟一对多一样,直接用book类的集合book_set查询作者关联的书籍 print(author_obj.book_set.values('name', 'price')) # <QuerySet [{'name': 'GO', 'price': 23}]>
如果就是想自己直接操作第三张关联表 ,可以自己创建一个表,然后根据外键创建关联
这种方式作为了解,我们还是用ManyToManyFile创建关联对象
#此时就不能用ManyToManyFile字段了 class Book(models.Model): name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() publish = models.ForeignKey('Publish', on_delete=models.CASCADE) class Author(models.Model): name = models.CharField(max_length=20) age = models.IntegerField(default=20) class Book_Author(models.Model): book = models.ForeignKey('Book',on_delete=models.CASCADE) author = models.ForeignKey('Author', on_delete=models.CASCADE)
# 添加第三张表信息 Book_Author.objects.create(book_id=1, author_id=3) # 查询 print(Book.objects.get(id=1).book_author_set.all()[0].author.name) #qinsan # 解析 先找到id=1的书籍对象 通过反向查询找到第三张表的对象 通过外键找到对应的author的对象,然后.name取出对应属性 # 双下划线查询 print(Book.objects.filter(book_author__author__name='qinsan').values('name')) # <QuerySet [{'name': 'linux运维'}]> print(Book_Author.objects.filter(author__name="qinsan").values('book')) #<QuerySet [{'book': 1}]>
ManyToManyFile方式下,推荐使用双下划线方式 filter() ,values()条件中加下划线
# 查找作者叫qinsan 关联的书籍 # 此处跟一对多一样,正向查询 authors是Book类里面的 关联键 ret = Book.objects.filter(authors__name='qinsan').values('name') print(ret) # <QuerySet [{'name': 'linux运维'}]> # 查找书籍id为1的作者名字和年纪 # 反向查询,book是书籍类,就是书籍表 ret2 = Author.objects.filter(book__id=1).values('name', 'age') print(ret2) #<QuerySet [{'name': 'qinsan', 'age': 18}, {'name': 'shaoping', 'age': 33}]>
聚合查询和分组查询
聚合查询和分组查询可以用于单表和复合查询,分组跟单表和复合没有关系
<1> aggregate(*args,**kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
键的名称是按照字段和聚合函数的名称自动生成出来的。
# 必须先导入这些方法才能使用 from django.db.models import Avg, Min, Sum, Max, Count # 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有 # 图书的集合。 ret = Book.objects.all().aggregate(Avg('price')) # {'price__avg': 270.4} # 原生SQL语句 SELECT AVG(`app01_book`.`price`) AS `price__avg` FROM `app01_book`; ret2 = Book.objects.aggregate(book_price_avg= Avg('price')) # 自定义标识符 AS一个别名 # 所有图书价格的最大值和最小值 ret3 = Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) # {'price__avg': 270.4, 'price__max': 999, 'price__min': 23} # 所有图书的价格和 ret4 = Book.objects.aggregate(Sum('price')) # {'price__sum': 1352} # 所有图书的数量 ret5 = Book.objects.aggregate(Count('name')) # {'name__count': 5} # 查询作者id为1关联的书籍的价格总和 ret6 = Book.objects.filter(authors__id=1).aggregate(Sum('price')) # {'price__sum': 1121} # 原生sql SELECT `django_migrations`.`id`, `django_migrations`.`app`, `django_migrations`.`name`, `django_migrations`.`applied` FROM # `django_migrations`;
<2> annotate(*args,**kwargs):
可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors__name
# 每个作者出书的价格总和 # 先利用values(authors_name)筛选出书籍的所有作者,然后通过annotate分类,然后根据分类求和 ret7 = Book.objects.values('authors__name').annotate(Sum('price')) # SELECT `app01_author`.`name`, SUM(`app01_book`.`price`) AS `price__sum` FROM `app01_book` LEFT OUTER JOIN `app01_book_auth # ors` ON (`app01_book`.`id` = `app01_book_authors`.`book_id`) LEFT OUTER JOIN `app01_author` ON (`app01_book_authors`.`author_id` = # `app01_author`.`id`) GROUP BY `app01_author`.`name` ORDER BY NULL LIMIT 21; args=() # <QuerySet [{'authors__name': 'qinsan', 'price__sum': 32}, {'authors__name': 'shaoping', 'price__sum': 32}, {'authors__name': 'xiao # wei', 'price__sum': 1121}, {'authors__name': None, 'price__sum': 199}]> # 查询各个出版社最便宜的书价是多少 ret8 = Book.objects.values('publish__name').annotate(Min('price')) #或者 ret9 = Publish.objects.values('name').annotate(Min('price')) # <QuerySet [{'publish__name': '上海出版社', 'price__min': 32}, {'publish__name': '北京出版社', 'price__min': 23}, {'publish__name': # '湖北出版社', 'price__min': 99}]>
F查询和Q查询
# F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q 构建搜索条件 from django.db.models import Q #1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 q1=models.Book.objects.filter(Q(title__startswith='P')).all() print(q1)#[<Book: Python>, <Book: Perl>] # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 Q(title__startswith='P') | Q(title__startswith='J') # 两个关键字只能表示and关系,怎么表示或 和 非呢 # 用Q包起来就可以了 ret10 = Book.objects.filter(Q(name='PYTHON') | Q(price=999)) print(ret10) # <QuerySet [<Book: PHP>, <Book: PYTHON>]> # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 除了python 其他的书籍 ret11 = Book.objects.filter(~Q(name='PYTHON')) print(ret11) # <QuerySet [<Book: linux运维>, <Book: GO>, <Book: PHP>, <Book: JAVA>]> # 4、应用范围: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects.get( Q(title__startswith='P'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) #sql: # SELECT * from polls WHERE question LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # import datetime # e=datetime.date(2005,5,6) #2005-05-06 # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 # 正确: Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P') # 错误: Book.objects.get( question__startswith='P', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
八、admin的配置
admin是django强大功能之一,它能共从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用,但是有时候,一些特殊的功能还需要定制,比如搜索功能,下面这一系列文章就逐步深入介绍如何定制适合自己的admin应用。
在创建sqlite数据库admin用户时创建用户和密码的方法
python manage.py createsuperuser
如果你觉得英文界面不好用,可以在setting.py 文件中修改以下选项
LANGUAGE_CODE = 'en-us' #LANGUAGE_CODE = 'zh-hans'
一 认识ModelAdmin
管理界面的定制类,如需扩展特定的model界面需从该类继承。
二 注册medel类到admin的两种方式:
<1> 使用register的方法
admin.site.register(Book,MyAdmin)
<2> 使用register的装饰器
@admin.register(Book)
示例:
from django.contrib import admin from app01.models import * # Register your models here. admin.site.register(Author) admin.site.register(Book) admin.site.register(Publish)
三 掌握一些常用的设置技巧
- list_display: 指定要显示的字段
- search_fields: 指定搜索的字段
- list_filter: 指定列表过滤器
- ordering: 指定排序字段
-
list_per_page = 3 : 指定分页显示的行数
-
fieldsets : 隐藏部分属性
from django.contrib import admin from app01.models import * # Register your models here. # @admin.register(Book)#----->单给某个表加一个定制 class MyAdmin(admin.ModelAdmin): list_display = ("title","price","publisher") search_fields = ("title","publisher") list_filter = ("publisher",) ordering = ("price",) fieldsets =[ (None, {'fields': ['title']}), ('price information', {'fields': ['price',"publisher"], 'classes': ['collapse']}), ] admin.site.register(Book,MyAdmin) admin.site.register(Publish) admin.site.register(Author)
参考文献:http://www.admin10000.com/document/2220.html
九、cookie和session
1、cookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生。
cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。
问题来了,基于http协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的cookie就起到桥接的作用。
我们可以给每个客户端的cookie分配一个唯一的id,这样用户在访问时,通过cookie,服务器就知道来的人是“谁”。
cookie示例:
urls.py:
from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), path('login/', views.login),
login.html:
<form action="/login/" method="post"> <p>用户名:<input type="text" name="user"></p> <p>密--码:<input type="password" name="password"></p> <input type="submit"> </form>
index.html:
<h1>hello {{ name }}</h1>
views.py:
from django.shortcuts import render,redirect # Create your views here. def login(request): print('cookie',request.COOKIES) print('session', request.session) if request.method == 'POST': print(request.POST) user = request.POST.get('user') pwd = request.POST.get('password') print(user,pwd) if user == 'liqinsan' and pwd == '123': return redirect('/index/') return render(request,'login.html') def index(request): name = 'liqinsan' return render(request,'index.html',locals())
此时未使用cookie,出现的问题是第一 index页面无法动态获取用户名,需要后台写死,第二是如下图效果,可以直接进入index页面
使用cookie:
修改views.py:
from django.shortcuts import render,redirect # Create your views here. def login(request): print('cookie',request.COOKIES) print('session', request.session) if request.method == 'POST': print(request.POST) user = request.POST.get('user') pwd = request.POST.get('password') print(user,pwd) if user == 'liqinsan' and pwd == '123': # return redirect('/index/') ret = redirect('/index/') ret.set_cookie('abc123', user) #cookie是一个键值对 #cookie {'csrftoken': 'SEzjbxhXUOZ7qLGindP04TTU8KFJqEoyuhhnKlLnUInGt41LIu3FOKJIuem7hdOJ', 'abc123': 'liqinsan'} return ret return render(request,'login.html') def index(request): name = request.COOKIES.get('abc123',None) if name == 'liqinsan': return render(request,'index.html',locals()) return redirect('/login/')
没有对应的cookie,直接进入index页面会直接跳回到登录页面,如果登录一次后有了cookie,就可以直接进入index页面,但是如果更换了浏览器没有cookie直接进入
index页面又会跳到login页面,如下图:
2、cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session。然后我们再根据不同的cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
cookie保密性不强,所有数据都传送到了客户端,通过浏览器就可以查看到,所以需要session
3、总结而言:cookie弥补了http无状态的不足,让服务器知道来的人是“谁”;但是cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过cookie识别不同的用户,对应的在session里保存私密的信息以及超过4096字节的文本。
4、另外,上述所说的cookie和session其实是共通性的东西,不限于语言和框架
前面的介绍中我们已经有能力制作一个登陆页面,在验证了用户名和密码的正确性后跳转到后台的页面。但是测试后也发现,如果绕过登陆页面。直接输入后台的url地址也可以直接访问的。这个显然是不合理的。其实我们缺失的就是cookie和session配合的验证。有了这个验证过程,我们就可以实现和其他网站一样必须登录才能进入后台页面了。
session增加安全性
先说一下这种认证的机制。每当我们使用一款浏览器访问一个登陆页面的时候,一旦我们通过了认证。服务器端就会发送一组随机唯一的字符串(假设是123abc)到浏览器端,这个被存储在浏览端的东西就叫cookie。而服务器端也会自己存储一下用户当前的状态,比如login=true,username=hahaha之类的用户信息。但是这种存储是以字典形式存储的,字典的唯一key就是刚才发给用户的唯一的cookie值。那么如果在服务器端查看session信息的话,理论上就会看到如下样子的字典
{'123abc':{'login':true,'username:hahaha'}}
因为每个cookie都是唯一的,所以我们在电脑上换个浏览器再登陆同一个网站也需要再次验证。那么为什么说我们只是理论上看到这样子的字典呢?因为处于安全性的考虑,其实对于上面那个大字典不光key值123abc是被加密的,value值{'login':true,'username:hahaha'}在服务器端也是一样被加密的。所以我们服务器上就算打开session信息看到的也是类似与以下样子的东西
{'123abc':dasdasdasd1231231da1231231}
借用一张别的大神画的图,可以更直观的看出来cookie和session的关系
知道了原理,我们下面就来用代码实现cookie+session
修改一下index.html
<div class="container"> <h2>cookie 内容是 {{ cookie_content }}</h2> <h2>session 内容是 {{ session_content }}</h2> <h2>登录用户名 :{{ username }}</h2> <a href="/logout/">注销</a> </div>
修改views.py
from django.shortcuts import render,redirect # Create your views here. def login(request): print('cookie',request.COOKIES) print('session', request.session) if request.method == 'POST': print(request.POST) user = request.POST.get('user') pwd = request.POST.get('password') print(user,pwd) if user == 'liqinsan' and pwd == '123': # return redirect('/index/') # ret = redirect('/index/') # ret.set_cookie('abc123', user) #cookie是一个键值对 #cookie {'csrftoken': 'SEzjbxhXUOZ7qLGindP04TTU8KFJqEoyuhhnKlLnUInGt41LIu3FOKJIuem7hdOJ', 'abc123': 'liqinsan'} # return ret # -------------session-------------------- # 设置session内部的字典内容 request.session['is_login'] = 'true' request.session['username'] = user # 登录成功就将url重定向到后台的url return redirect('/index/') return render(request,'login.html') def index(request): """ 这里必须用读取字典的get()方法把is_login的value缺省设置为False, 当用户访问index这个url先尝试获取这个浏览器对应的session中的 is_login的值。如果对方是登录成功的话,在login里就已经把is_login 的值修改为了True,反之这个值就是False的
有待研究:实际测试发现,不设置缺省值的话,如果没有session会得到一个None值,一样可以进行判断 """
is_login = request.session.get('is_login', False) # 如果为真,就说明用户是正常登陆的 if is_login: # 获取字典的内容并传入页面文件 cookie_content = request.COOKIES session_content = request.session username = request.session['username'] return render(request, 'index.html',locals()) else: """ 如果访问的时候没有携带正确的session, 就直接被重定向url回login页面 """ return redirect('/login/') def logout(request): """ 直接通过request.session['is_login']回去返回的时候, 如果is_login对应的value值不存在会导致程序异常。所以 需要做异常处理 """ try: #删除is_login对应的value值 del request.session['is_login'] except KeyError: pass #点击注销之后,直接重定向回登录页面 return redirect('/login/')
修改urls.py
from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), path('login/', views.login), path('logout/', views.logout), ]
index.html效果
从上图中我们看到有一下几点:
1、login页面正确登录的话,后台页面可以获取到浏览器携带的cookie的。
2、第一行的sessionid其实就是cookie值,是一个随机值
3、session的内容是加密的,从客户端获取不到session的内容
4、服务端可以通过预设的key值取出session的内容并打印到前段,比如用户名
从览器里查看cookie:
5、django的session默认是存储在数据库里的,我们再到数据库查看一下真正session内容
都是加密信息,无法直接看到数据本身的内容
6、Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
总结:
下面我们再来最后的总结一下cookie和session的知识点
一、操作Cookie
获取cookie:request.COOKIES[key]
设置cookie:response.set_cookie(key,value)
由于cookie保存在客户端的电脑上,所以,jquery也可以操作cookie。
二、操作Session(session默认在服务器端保存15天)
获取session:request.session[key]
设置session:reqeust.session[key] = value
删除session:del request.session[key]
(这个删除其实就是把数据库的session_data更新为一个其他的值了,并没有立即删除)
cookie and session设置过期时间示例:
cookie:
ret = redirect("/index/") #设置过期时间,max_age 和 expires 时间要设置的一样。 # ret.set_cookie("username", {"11": "22"}) ret.set_cookie("username", {"11":"22"},max_age=10,expires=datetime.datetime.utcnow()+datetime.timedelta(days=3)) return ret
session:
def index(request): request.session.set_expiry(5) #设置在index页面中 value值如下
request.session.set_expiry(value) * 如果value是个整数,session会在些秒数后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。
补充:还可以在setting.py中设置
session的超时时间设置
settings中
- SESSION_COOKIE_AGE=60*30 30分钟。
- SESSION_EXPIRE_AT_BROWSER_CLOSE False:会话cookie可以在用户浏览器中保持有效期。True:关闭浏览器,则Cookie失效。
- SESSION_COOKIE_DOMAIN 生效站点
- SESSION_COOKIE_NAME cookie中保存session的名称
Session使用比较简单,在request.session是一个字典类。session是保存在数据库中的。
-------------------------------------------------------
按手册和网上的方法在settings.py中设置“SESSION_COOKIE_AGE” 和 “SESSION_EXPIRE_AT_BROWSER_CLOSE” 均不生效。 通过查看django的源代码"middleware.py"才知道这两个参数只有在settings.SESSION_SAVE_EVERY_REQUEST为True时才有效。依此在settings.py中设置这个变量后问题解决。
从源代码看SESSION_EXPIRE_AT_BROWSER_CLOSE为True时 SESSION_COOKIE_AGE 不生效。也就是说用户只能二选一,在浏览器关闭时使session失效 或 超时失效。
总结:
-请求响应Http
-请求字符串
请求头
分割:\r\n\r\n
请求体
-响应字符串
响应头
分割:\r\n\r\n
响应体
1、发送HTTP请求
2、服务器接收,根据请求头中的url在路由关系表中进行匹配(从上到下)
3 、匹配成功后,执行指定的views函数
-----------URL -> 函数 -->FBV 利用函数
URLS.PY
urlpatterns = [ path('admin/', admin.site.urls), path('fbv', views.fbv), ]
VIEWS.PY
def fbv(request): if request.method == 'GET': return render(request, 'fbv.html') elif request.method == 'POST': return HttpResponse('fbv.post')
------------ URL -> 类 -->CBV 利用类
URLS.PY
urlpatterns = [ path('admin/', admin.site.urls), # as_view()是固定写法,跟用函数不同 path('cbv', views.CBV.as_view()), ]
VIEWS.PY
from django.views import View # 利用类,需要继承View类 class CBV(View): # 自定义dispatch, # 执行的流程是: # 1、浏览器发送请求,urls路由分发 # 2、先会到dispatch判断request method是get还是post # 3、然后再执行相应的方法,底层通过反射的方法取到自定义的get和post方法 # 4、get和post方法返回值也会先到dispatch然后再返回前端,所以下面dispatch方法必须接受一个值并返回给前端
def dispatch(self, request, *args, **kwargs): # 继承父类的所有方法和属性 result = super(CBV, self).dispatch(request, *args, **kwargs) return result # 根据请求头中的request method自动判断执行,这个判断来自于父类的方法dispatch def get(self, request): return render(request, 'cbv.html') def post(self, request): return HttpResponse('cbv.post')
以上两种方式利用哪一种要看具体情况,都可以使用。
def post(self, request): ret = HttpResponse('cbv.post') # 往响应头添加内容 ret['h1'] = 'v1' ret['h2'] = 'v2' ret.set_cookie('c1', 'h1') ret.set_cookie('c2', 'h2') return ret ''' 头: h1=v1 h2=v2 cookie:(c1=h1,c2=h2) 体 cbv.post '''
响应头:
响应体:
4、业务处理
-根据个人需求自定
-操作数据库
-原生SQL
-Django ORM
-响应内容
-响应头
-响应体
Djano ORM操作
1、类代表数据库表
2、类的对象指代数据库的一行记录
3、FK字段代指关联表中的一行数据(类的对象)
4、
-正向:FK键
-反向:默认小写的类名_set 也可以在创建Fk字段的时候通过related_name修改
5、正向和反向查找就像是mysql中 leftjoin和rightjoin
django中谁是主表就全部列出其数据,对于主表中跟子表没有关联值的用None代替
6、MTM字段 自动生成第三张表,依赖关联表对第三张表的间接操作
7、多对多的反向跟一对多一样
-正向:MTM键
-反向:默认小写的类名_set 也可以在创建Fk字段的时候通过related_name修改
在通过MTM键查关联表时,其实是通过第三张表直接获取关联表的值,也就是说obj.m.all()获取的值是班级对应的所有老师。
filter和all得到的是一个querset集合,里面是对象,可以调用对象的属性和方法
vaules得到的也是一个querset集合,里面存放的是字典类型的数据,由于是字典不能调用对象属性和方法
values_list里面存放的是元祖类型的数据,由于是元祖不能调用对象属性和方法
单表
class Classes(models.Model): title = models.CharField(max_length=32) # 增 Classes.objects.create(title='101') obj = Classes(title='101') obj.save() # 查 Classes.objects.filter(id=1) Classes.objects.filter(id=1, title='101') Classes.objects.all() Classes.objects.all().first() # 删 Classes.objects.filter(id=1).delete() # 改 Classes.objects.filter(id=1).update(title='102')
一对多
在取值的时候跨表可以用点。 在作为filter或value中参数时,跨表的需要用到双下划线。理论上可以一直跨表到最后一级。cs__fk__name跨多个表。
class Student(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() gender = models.BooleanField() cs = models.ForeignKey('Classes', on_delete=models.CASCADE) # 增 Student.objects.create(username='li', age=11, gender=0, cs_id=1) Student.objects.create(username='li', age=11, gender=0, cs=Classes.objects.filter(id=1).first()) # 上面两种效果相同,区别在于cs_id是Django为我们隐藏创建的,可以直接利用 # cs是Classes表中的一个对象,也就是一条数据,所以也可以用Classes中的一个对象来添加 # 查 ret = Student.objects.all() for item in ret: print(item.id) print(item.username) print(item.age) print(item.gender) print(item.cs_id) print(item.cs) # 上面说过CS是一个Classes对象,所以也可以调用对象的方法 print(item.cs.id) print(item.cs.title) # 删 Student.objects.filter(id=1).delete() Student.objects.filter(cs_id=1).delete() # cs作为一个Classes对象,filter或value作为值时,跨表取值需要用双下划线 Student.objects.filter(cs__id=1).delete() # cs__id == cs_id Student.objects.filter(cs__title='101').delete() # 改 Student.objects.filter(id=1).update(username='qin')
Students可以通过cs字段加双下划线跨表到Classes,那么classes怎么跨表到Students呢
通过Classes中隐藏的students_set就可以反向跨表,也可以在cs字段创建时通过related_name来修改反向关键字的名称。
class Classes(models.Model): title = models.CharField(max_length=32) # 反向跨表,通过小写类名__set obj=Classes.objects.filter(id=2).first() obj.student__set.all() # fitler,all方法都可以使用 # 第二种形式 class Student(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() gender = models.BooleanField() cs = models.ForeignKey('Classes', on_delete=models.CASCADE,related_name='sss') # 反向跨表,通过FK中定义的related_name来反向查找 obj=Classes.objects.filter(id=2).first() obj.sss.all()
多对多
class Classes(models.Model): title = models.CharField(max_length=32) m = models.ManyToManyField('Teachers') class Teachers(models.Model): name = models.CharField(max_length=32) # 增 obj = Classes.objects.filter(id=1).first() # 取出对象集合的第一个对象 obj.m.add(2) # classes.id为1的班级跟teachers.id为2的老师绑定关系 obj.m.add([3,4]) # 也可以是列表的形式绑定多个 # 删 obj.m.remove([3,4]) # 解除classes.id为1的班级跟teachers.id为2、3的老师的绑定关系 obj.m.clear() # 清空所有跟classes.id绑定的关系 # 重置 obj.m.set([2,3,5]) # 此时如果是绑定关系则保持不变。否则删除多余的然后重置到跟列表里一样 # 查 obj.m.all() #把classid为1班级的所有老师列举出来
多对多的反向查找跟FK一样
class Classes(models.Model): title = models.CharField(max_length=32) m = models.ManyToManyField('Teachers',related_name='ssss') class Teachers(models.Model): name = models.CharField(max_length=32) # 查 obj = Teachers.objects.filter(id=1).first() obj.classes__set.all().values('title') # 获取老师id1的所有班级 # 增,可以通过MTM键中related_name设置别名 obj.ssss.add([2,3]) # 可以以列表的形式绑定多个与老师id1的关系 # 删 obj.ssss.remove(3) # 重置 obj.ssss.set([1,2,3])
def test(request): obj = Classes.objects.all().values('title','m','m__name') print(obj) return HttpResponse('111')
<QuerySet [{'title': '103班', 'm': 1, 'm__name': 'li'}, {'title': '112班', 'm': 2, 'm__name': 'qin'}, {'title': '105班', 'm': None, 'm__name': None}]>
def test(request): obj = Teachers.objects.all().values('name','ssss','ssss__title') print(obj) return HttpResponse('111')
<QuerySet [{'name': 'li', 'ssss': 3, 'ssss__title': '103班'}, {'name': 'qin', 'ssss': 12, 'ssss__title': '112班'}, {'name': 'san', 'ssss': None, 'ssss__title':None}]>
注意:1、上面的两种不同结果:正向和反向查找就像是mysql中 leftjoin和rightjoin,django 中谁是主表就全部列出其数据,对于主表中跟子表没有关联值的用None代替
2、values获取的是字典类型的queryset集合,里面的m或者ssss不是对象,不能调用对象的方法和属性。