Django
-
一、什么是web框架?
框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演。
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
-
自制一个简单的web框架
-
第一步:了解请求信息和响应信息
from wsgiref.simple_server import make_server def application(environ, start_response): print(environ) # 请求信息 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。
-
第二步:根据不同的url_path进行不同的操作
from wsgiref.simple_server import make_server import time def foo1(): f = open('index2.html','rb') data = f.read() return data def foo2(): f = open('index1.html','rb') data = f.read() return data def application(environ, start_response): print('path',environ['PATH_INFO']) path=environ['PATH_INFO'] start_response('200 OK', [('Content-Type', 'text/html')]) if path=='/alex': return [foo1()] #return [b'<h1>hello alex</h1>'] elif path=='/yuan': return [foo2()] else: return [b'<h1>404</h1>'] httpd = make_server('', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()
- 第三步:把url_path加入路由分发
from wsgiref.simple_server import make_server import time def foo1(req): f = open('index2.html','rb') data = f.read() return data def foo2(req): f = open('index1.html','rb') data = f.read() return data def login(req): print(req['QUERY_STRING']) return b'welcome!' def signup(req): pass def show_time(req): times=time.ctime() #return ('<h1>time:%s</h1>' %str(times)).encode('utf-8') f = open('show_time.html','rb') data = f.read() data=data.decode('utf-8') data=data.replace('{{time}}', str(times)) return data.encode('utf-8') def router(): url_patterns=[ ('/login',login), ('signup',signup), ('/yuan',foo2), ('/alex',foo1), ('/show_time',show_time) ] return url_patterns def application(environ, start_response): print('path',environ['PATH_INFO']) path=environ['PATH_INFO'] start_response('200 OK', [('Content-Type', 'text/html')]) url_patterns = router() func = None for item in url_patterns: if item[0] == path: func=item[1] break if func: return [func(environ)] else: return [b'404'] httpd = make_server('', 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 #安装: pip install django 添加环境变量 #1、创建project django-admin startproject mysite ---mysite ---settings.py ---url.py ---wsgi.py ---- manage.py(启动文件) #2、创建APP——一个应用创建一个app python mannage.py startapp app01 #3、settings配置 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', # 手动创建应用时需要添加到这里,用pycharm创建应用时自动添加到这里 ] STATIC_URL = '/static/' # 手动创建静态文件夹和添加静态目录 STATICFILES_DIRS=( os.path.join(BASE_DIR,"statics"), ) # 我们只能用 STATIC_URL,但STATIC_URL会按着你的STATICFILES_DIRS去找#4 根据需求设计代码 url.py view.py #5、使用模版 render(request, "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的配置文件(settings)
-
静态文件设置:
一、概述: #静态文件交由Web服务器处理,Django本身不处理静态文件。简单的处理逻辑如下(以nginx为例): # URI请求-----> 按照Web服务器里面的配置规则先处理,以nginx为例,主要求配置在nginx. #conf里的location |---------->如果是静态文件,则由nginx直接处理 |---------->如果不是则交由Django处理,Django根据urls.py里面的规则进行匹配 # 以上是部署到Web服务器后的处理方式,为了便于开发,Django提供了在开发环境的对静态文件的处理机制,方法是这样: #1、在INSTALLED_APPS里面加入'django.contrib.staticfiles', #2、在urls.py里面加入 if settings.DEBUG: urlpatterns += patterns('', url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT }), url(r'^static/(?P<path>.*)$', 'django.views.static.serve',{'document_root':settings.STATIC_ROOT}), ) # 3、这样就可以在开发阶段直接使用静态文件了。 二、MEDIA_ROOT和MEDIA_URL #而静态文件的处理又包括STATIC和MEDIA两类,这往往容易混淆,在Django里面是这样定义的: #MEDIA:指用户上传的文件,比如在Model里面的FileFIeld,ImageField上传的文件。如果你定义 #MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/")#,上传的文件就会被保存到c:\temp\media\abc #eg: class blog(models.Model): Title=models.charField(max_length=64) Photo=models.ImageField(upload_to="photo") # 上传的图片就上传到c:\temp\media\photo,而在模板中要显示该文件,则在这样写 #在settings里面设置的MEDIA_ROOT必须是本地路径的绝对路径,一般是这样写: BASE_DIR= os.path.abspath(os.path.dirname(__file__)) MEDIA_ROOT=os.path.join(BASE_DIR,'media/').replace('\\','/') #MEDIA_URL是指从浏览器访问时的地址前缀,举个例子: MEDIA_ROOT=c:\temp\media\photo MEDIA_URL="/data/" #在开发阶段,media的处理由django处理: # 访问http://localhost/data/abc/a.png就是访问c:\temp\media\photo\abc\a.png # 在模板里面这样写<img src="{{MEDIA_URL}}abc/a.png"> # 在部署阶段最大的不同在于你必须让web服务器来处理media文件,因此你必须在web服务器中配置, # 以便能让web服务器能访问media文件 # 以nginx为例,可以在nginx.conf里面这样: location ~/media/{ root/temp/ break; } # 具体可以参考如何在nginx部署django的资料。 三、STATIC_ROOT和STATIC_URL、 STATIC主要指的是如css,js,images这样文件,在settings里面可以配置STATIC_ROOT和STATIC_URL, 配置方式与MEDIA_ROOT是一样的,但是要注意 #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类似。 # ---------------------------------------------------------------------------- #注意1: #为了后端的更改不会影响前端的引入,避免造成前端大量修改 STATIC_URL = '/static/' #引用名 STATICFILES_DIRS = ( os.path.join(BASE_DIR,"statics") #实际名 ,即实际文件夹的名字 ) #django对引用名和实际名进行映射,引用时,只能按照引用名来,不能按实际名去找 #<script src="/statics/jquery-3.1.1.js"></script> #------error-----不能直接用,必须用STATIC_URL = '/static/': #<script src="/static/jquery-3.1.1.js"></script> #注意2(statics文件夹写在不同的app下,静态文件的调用): STATIC_URL = '/static/' STATICFILES_DIRS=( ('hello',os.path.join(BASE_DIR,"app01","statics")) , ) #<script src="/static/hello/jquery-1.8.2.min.js"></script> #注意3: STATIC_URL = '/static/' {% load static %} # <script src={% static "jquery-1.8.2.min.js" %}></script>
-
static静态文件配置流程和对应关系:
1、创建static文件夹
2、在settings.py文件中添加:
STATICFILES_DIRS=( os.path.join(BASE_DIR, 'static'), )
3、前端html页面引用静态文件:
方式一: <script src="/static/jquery-3.3.1.js"></script> 方式二:建议用这种方式 {% load static %} <script src="{% static 'jquery-3.3.1.js' %}"></script>
4、文件夹名和别名对应关系图:

5、在不同应用中添加static静态文件夹:

-
五、Django URL(路由系统)
URL配置(URLconf)就像Django所支撑网站的目录,它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表;就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码
url.py文件:
from django.contrib import admin from django.urls import path from django.conf.urls import url, include # 老版本django默认引入这种形式,可以写正则 from blog import views # 不同应用导入相应的views.py模块 from app01 import views as v1 from app02 import views as v2 urlpatterns = [ path('admin/', admin.site.urls), url(r'^show_time/', views.show_time), path('login/', v1.login), url(r'^register/', v2.register), url(正则表达式, views视图函数, 参数, 别名), ]
-
1、最基本的url配置:

-
2、无名分组和有名分组的url配置:

-
3、对url进行别名配置:
在前端html页面里引用url时,利用别名便于维护,通过 {% url '别名' %} 标签引用

-
4、在url中传入参数:
参数可以直接传给该url对应的views视图函数里,进行逻辑处理

-
5、对url进行下级分发:
对不同的应用可以创建不同的urls.py文件,通过对url进行二级分发,达到方便管理的目的,通过 include 方式进行分发,此时应用blog下的url样例为:http://127.0.0.1:8001/blog/article/2004

配置流程如下:

-
六、Views(视图函数)

http请求中产生两个核心对象:
http请求:HttpRequest对象
http响应:HttpResponse对象
所在位置:django.http
之前我们用到的参数request就是HttpRequest 检测方法:isinstance(request,HttpRequest)
-
1、HttpRequest对象的属性和方法:
path:请求页面的全路径,不包括域名 method:请求中使用的HTTP方法的字符串表示。全大写表示。例如 if req.method=="GET": do_something() elif req.method=="POST": do_something_else() GET:包含所有HTTP GET参数的类字典对象 POST:包含所有HTTP POST参数的类字典对象 服务器收到空的POST请求的情况也是可能发生的,也就是说,表单form通过HTTP POST方法提交请求,但是表单中可能没有数据,因此不能使用if req.POST来判断是否使用了HTTP POST 方法,应该使用 if req.method=="POST" 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/register?a=3&b=6; req.get_full_path() -----> /blog/register?a=3&b=6; req.path -----> /blog/register
注意一个常用方法:request.POST.getlist('')
-
2、HttpResponse对象:
对于HttpRequest对象来说,是由django自动创建的,但是,HttpResponse对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse对象。
HttpResponse类在django.http.HttpResponse
from django.shortcuts import render, HttpResponse, redirect
在HttpResponse对象上扩展的常用方法:
返回字符串:HttpResponse('..') 页面渲染:render(request, "模板")(推荐)--> render(request,'register.html') render_to_response("模板") -->render_to_response('register.html')
区别:render需要在参数中写request,但是render_to_response可能会在某些情况下出现一些问题,建议使用render locals():可以直接将函数中所有的变量传给模板,包括request里的内容 return render(request,'index.html',locals()) 前端接收: <h1>hello {{ name }} 时间 {{ t }} {{ request.method }}</h1> 指定传某些context变量: return render(request, 'index.html', {'time':t, 'name':name}) return render_to_response('index.html', {'time':t}) 页面跳转:redirect("路径") -->redirect('/login/') -->redirect('www.baidu.com')
补充:
总结: render和redirect的区别:
1、如果render的页面需要模板语言渲染,需要将数据库的数据加载到html,那么所有的这一部分除了写在login的视图函数中,必须还要写在register中,代码重复,没有解耦
2、更重要的是url没有跳转到/login/,而是还在/register/,所以当刷新后又得重新登录
def register(request): if request.method=='POST': print(request.POST.get('user')) print(request.POST.get('age')) user=request.POST.get('user') if user=='yuan': return redirect('/login/') #name='yuan' # 代码重复 #return render(request,'login.html', locals()) #url没有变化 return HttpResponse('success!') return render(request,'register.html') def login(request): name='yuan' return render(request, 'login.html', locals())
redirect跳转结果为:

render结果为:

-
七、Template
-
模板系统的介绍
import datetime def show_time(request): t = datetime.datetime.now() return HttpResponse("<html><body>It is now %s</body></html>" %t)
在例子视图中返回文本的方式有点特别,也就是说,HTML被直接硬编码在Python代码之中,尽管这种技术便于解释视图是如何工作的,但直接将HTML硬编码到你的视图里却并不是一个好主意。 原因如下:
-
对页面设计进行的任何改变都必须对 Python 代码进行相应的修改。 站点设计的修改往往比底层Python代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更设计,那将会方便得多;
-
Python代码编写和 HTML 设计是两项不同的工作,大多数专业的网站开发环境都将他们分配给不同的人员(甚至不同部门)来完成。 设计者和HTML/CSS的编码人员不应该被要求去编辑Python的代码来完成他们的工作;
-
程序员编写Python代码和设计人员制作模板两项工作同时进行的效率是最高的,远胜于让一个人等待另一个人完成对某个既包含Python又包含HTML文件的编辑工作。
基于这些原因,将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用Django的模板系统 (Template System)来实现这种模式。
***********************************模板语法*****************************************
-
一、模版的组成
组成:HTML代码+逻辑控制代码
-
二、逻辑控制代码的组成
-
1 、变量(使用双大括号来引用变量):
语法格式:{{ var_name }}
******************************** Template和Context对象 *****************************************
#python解释器中输入: >>>python manage.py shell (进入该django项目的环境) >>> from django.template import Context, Template >>> t = Template("<h1>hello {{ name }}</h1>") >>> c = Context({"name":"yue"}) >>> t.render(c) #template对象调用render方法
结果为:

# 同一模板,多个上下文,一旦有了模板对象,你就可以通过它渲染多个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 #记得导入 import datetime from django.template import Template,Context #HttpResponse方式 # def current_time(req): #原始的视图函数 # now=datetime.datetime.now() # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) #template和context对象方式 # def current_time(req): #django模板修改的视图函数 # now=datetime.datetime.now() # t=Template('<html><body>现在时刻是:<h1 style="color:red">{{current_date}}</h1></body></html>') #t=get_template('current_datetime.html') # c=Context({'current_date':now}) # html=t.render(c) # return HttpResponse(html) #最终方式--render def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
******************************** 深度变量的查找(万能的句点号) *************************************
在到目前为止的例子中,我们通过context传递的简单参数值主要是字符串,然而,模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。
在Django模板中遍历复杂数据结构的关键是句点字符"."
views函数:
class Animal(): def __init__(self,name,sex): self.name=name self.sex=sex def query(request): #列表 l=['村长','正文','cjk'] #字典 d={'name':'建文','age':40,'hobby':'nan'} #对象 c=Animal('alex','公') #python对象的公用方法
var = 'yue'
return render(request,'index.html',locals())
template页面:
<!--深度变量的查找(万能的句点号)--> <h1>hello {{ l.0 }}</h1> <h1>hello {{ l.1 }}</h1> <h1>hello {{ l.2 }}</h1> <h1>hello {{ d.name }}</h1> <h1>hello {{ d.age }}</h1> <h1>hello {{ d.hobby }}</h1> <h1>hello {{ c.name }}</h1> <h1>hello {{ c.sex }}</h1>
/*注意这里调用方法时并没有使用圆括号,而且也无法给该方法传递参数;你只能调用不需参数的方法*/
<h1>hello {{ var }}-{{ var.upper }}-{{ var.isdigit }}</h1>
结果为:

*************************************** 变量的过滤器(filter)的使用 *********************************************
语法格式:{{ obj|filter:param }}
# 部分过滤器 1、add:给变量加上相应的值 2、addslashes: 给变量中的引号前加上斜线\ 3、capfirst:首字母大写 4、cut:从字符串中移除指定的字符 5、date:格式化日期字符串 6、default:如果值是False,就替换成设置的默认值,否则就用本来的值 7、default_if_none: 如果值是None,就替换成设置的默认值,否则就用本来的值 8、safe:不进行安全转换,保留标签的样式 9、striptags:删除所有HTML标签 10、filesizeformat:转换为文件大小 11、first:第一个元素 12、length:长度 13、slice:切片 14、urlencode:转换为url编码格式
views函数:
def query(request): test = 'hello world!' test1 = 'hello"world' test2 = 'h ell o wor ld!' t = datetime.datetime.now() e = [] a = "<a href=''>click</a>" b = "<h1>hello</h1>" value7 = '1234' value8 = 'http://www.baidu.com/?a=1&b=3' return render(request,'index.html',locals())
template页面:
<h1>zjw的真实年龄{{ d.age|add:12 }}</h1> <h1>{{ test1|addslashes}}</h1> <h1>{{ test|capfirst }}</h1> <h1>{{ test2|cut:' ' }}</h1> <h1>{{ t|date:'Y-m-d' }}</h1> <h1>{{ e|default:'空的列表'}}</h1> <h1>{{ a|safe }}</h1>
/*同下方式效果一致*/ {% autoescape off%} <h1>{{ a }}</h1> {% endautoescape %} <h1>{{ b|striptags }}</h1> <h1>{{value7|filesizeformat}}</h1> <h1>{{value7|first}}</h1> <h1>{{value7|length}}</h1> <h1>{{value7|slice:"1:-1"}}</h1> <h1>{{value8|urlencode}}</h1>
结果为:

补充:mark_safe 效果同 safe 和 {% autoescape off%}
views函数中导入 mark_safe:
def test(request): txt = "<input type='text' />" from django.utils.safestring import mark_safe txt = mark_safe(txt) return render(request, 'test.html', {'txt':txt})
template页面直接调用变量即可:
{{ txt }}
-
2、标签(tag)的使用(使用大括号和百分号的组合来表示使用tag)
语法格式:{% tags %}
*************************************** {% if %} 的使用 *********************************************
{% if %}标签计算一个变量值,如果是“true”,即它存在、不为空并且不是false的boolean值,系统则会显示{% if %}和{% endif %}间的所有内容
{% if d.age > 20 %} {% if d.age < 50 %} <h1>{{ d.name }}的年龄大于20小于50</h1> {% else %} <h1>hello {{ d.name }}</h1> {% endif %} {% elif d.age > 10 %} <h1>{{ d.name }}的年龄大于10小于20</h1> {% else %} <h1>{{ d.name }}的年龄小于10</h1> {% endif %} 注: 1、{% if %} 标签接受and,or或者not来测试多个变量值或者否定一个给定的变量 2、{% if %} 标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的: {% if obj1 and obj2 or obj3 %}
*************************************** {% for %} 的使用 *********************************************
{% for %}标签允许你按顺序遍历一个序列中的各个元素,每次循环模板系统都会渲染{% for %}和{% endfor %}之间的所有内容
# 标准样例: l = ['村长','正文','cjk'] {% for name in l %} <h1>{{ name }}</h1> {% endfor %} # 在标签里添加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 %} 注:系统不支持中断循环break,系统也不支持continue语句 # {% for %}标签内置了一个forloop模板变量,这个变量含有一些属性可以提供给你一些关于循环的信息 1、forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1; 2、forloop.counter0类似于forloop.counter,但它是从0开始计数,第一次循环设为0; 3、序号倒叙:forloop.revcounter,forloop.revcounter0 {% for name in l %} <h1>{{ forloop.counter0 }}:{{ name }}</h1> <h1>{{ forloop.revcounter0 }}:{{ name }}</h1> {% endfor %} 4、forloop.first当第一次循环时值为True,在特别情况下很有用; 5、{% empty %}表示当循环里的内容为空时执行,放到与{% for %}同级的位置; {% for name in l %} {% if forloop.first %} <li class="first"> {% else %} <li> {% endif %} {{ name }}</li> {% empty %} <h1>没有相关文章</h1> {% endfor %} 注: 1、forloop变量只能在循环中得到,当模板解析器到达{% endfor %}时forloop就消失了; 2、如果你的模板context已经包含一个叫forloop的变量,Django会用{% for %}标签替代它; 3、Django会在for标签的块中覆盖你定义的forloop变量的值; 4、在其他非循环的地方,你的forloop变量仍然可用;
*************************************** {% csrf_token %}:csrf_token标签 *********************************************
生成csrf_token的标签,用于防治跨站攻击验证,注意如果你在views的index里用的是render_to_response方法,不会生效,其实这里是会生成一个input标签,和其他表单标签一起提交给后台,不写该标签提交form表单时会报错,也可以通过注释掉settings文件里的csrf中间键解决报错问题
settings文件注释掉该行:

使用方式:
<form action="{% url 'login' %}" method="post"> <p>姓名 <input type="text" name="user"></p> <p>密码 <input type="text" name="pwd"></p> <p><input type="submit"></p> {% csrf_token %} </form>
本质是在第一次访问该页面时生成了一个input标签,包括一个键值对,用于提交表单信息时进行验证:

*************************************** {% url %}: 引用路由配置的地址 *********************************************
给url设置别名
在urls文件中,通过name属性设置别名,在template中可以直接引用:

template中语法格式:
"{% url 'login' %}"
*************************************** {% with %}:用更简单的变量名替代复杂的变量名 *********************************************
使用格式:
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
只对{% with %}和{% endwith %}之间的内容有效
*************************************** {% verbatim %}:禁止渲染变量 *********************************************
不对{% verbatim %}和{% endverbatim %}之间的变量进行渲染,只当普通字符串处理
使用格式:
{% verbatim %}
{{ test }}
{% endverbatim %}
结果为:

-
3、自定义filter和simple_tag
*************************************** {% load %}: 加载标签库 *********************************************
步骤:
1、在app中创建templatetags模块(必须的)
2、创建任意 .py 文件,如:myTag.py

from django import template from django.utils.safestring import mark_safe register = template.Library() #register的名字是固定的, 不可改变 @register.filter # 自定义过滤器 def filter_multi(x,y): print(x,y) return x*y @register.simple_tag # 自定义标签 def simple_tag_multi(x,y,z): return x*y*z
3、在使用自定义filter和simple_tag的html文件中导入之前创建的myTag.py:通过{% load myTag %},放在html页面的首行
4、使用filter和simple_tag
{% load myTag %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--传参数只能传一个,可通过传入一个列表的方式进行处理多个参数的需求--> {{ d.age|filter_multi:3 }} <!--不能用在控制语句里面(if,else), for循环--> {% simple_tag_multi d.age 5 6 %} </body> </html>
5、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag,一般已经默认配置好了
注:filter可以用在if,for等控制语句中,simple_tag不可以;变量{{ var }}在{% 标签 %}中就不需要再加"{}"了,如下
{% if d.age|filter_multi:3 > 100 %}
{{ d.age|filter_multi:3 }}
{% endif %}
-
4、extend模板继承
*************************************** {% include "xx.html" %}模板标签 *********************************************
利用模板标签{% include %}允许在模板中包含其它的模板的内容,标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串,每当在多个模板中出现相同的代码时,就应该考虑是否要使用{% include %}来减少重复,使用时需要在页面上面调用加载:{% load static %}
加载页面:(extend样例中用到)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1 style="color:blue">hello world</h1> </body> </html>
*************************************** extend(继承)模板标签:{% extends "base.html" %};{% block xx %}{% endblock %};{{ block.super }} *********************************************
在实际应用中,将用 Django 模板系统来创建整个 HTML 页面,这就带来一个常见的 Web 开发问题:在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?
解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。事实上,Django 通过 {% include %}支持了这种方法,但是用 Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承,本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载

模板页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> {% block styles%} {% endblock%} <style> .nav{ line-height:40px; width:100%; background-color:#2459a2; color:white; font-size:20px; text-align: center; } .left{ width:20%; min-height: 600px; overflow:auto; background-color: lightgrey; } .manage{ text-align: center; padding:20px 0px; margin:20px 0px; font-size:18px; } .left,.content{ float:left; } .content{ width:70%; min-height:600px; } a{ text-decoration: none; } h1,h2{ text-align: center; } </style> </head> <body> <div class="outer"> <div class="nav">标题</div> <div class="left"> <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="classes manage"><a href="">班级管理</a></div> </div> <div class="content"> {% block content %} <h1>WELCOME TO LOGIN</h1> {% endblock %} </div> </div> </body> </html>
这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在本站点的所有页面中使用。子模板的作用就是重载、添加或保留那些块的内容。我们使用模板标签:{% block %},所有的{% block %}标签告诉模板引擎,子模板可以重载这些部分,每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖
现在我们已经有了一个基本模板,我们可以修改 student.html 模板来使用它
继承页面:
{% extends 'base.html' %} {% load static %} {% block styles %} <style> h2{ color:red; } </style> {% endblock %} {% block content %} {{ block.super }} {% for student in student_list %} <h2>学生{{ student }}</h2> {% endfor%} {% include 'test.html'%} {% endblock %}
使用模板继承的一些注意事项:
<1> 如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记,即放到最上面,否则,模板继承将不起作用;
<2> 一般来说,基础模板中的 {% block %} 标签越多越好,子模板不必定义父模板中所有的代码块,因此可以用合理的对一些代码块进行填充,只对子模板所需的代码块进行重定义,俗话说,钩子越多越好
<3> 如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中,如果需要访问父模板中的块的内容,可以使用 {{ block.super }} 这个标签来继承父模板中该块的内容,同时还可以定制子模板中该块与父模板不同的内容,如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了
<4> 不允许在同一个模板中定义多个同名的 {% block %} ,存在这样的限制是因为block 标签的工作方式是双向的,也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容,如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容
继承之后的student.html对应的位置解析:

-
八、Models
-
数据库的配置
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中使用
注意:在python3中使用mysql,需要找到django项目名文件夹(即settings文件所在文件夹)下的__init__文件,在里面写入:
import pymysql pymysql.install_as_MySQLdb()
3、在django的项目中会默认使用sqlite数据库,在settings里有如下设置:

如果要修改为使用mysql数据库,需要将其修改为:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'Django_ORM', #你的数据库名称 'USER': 'root', #你的数据库用户名 'PASSWORD': '123456', #你的数据库密码 'HOST': '', #你的数据库主机,留空默认为localhost 'PORT': '3306', #你的数据库端口 } }
-
ORM(对象关系映射)
用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作
优点:
1、ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑SQL语句,快速开发,由此而来
2、可以避免一些新手程序员写sql语句带来的性能问题
缺点:
1、性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载登来减轻这个问题,效果很显著
2、对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql
3、得通过QuerySet的query属性查询对应操作的sql语句
注意:想显示对应ORM操作的raw sql,需要在settings中加上日志记录部分
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
-
表(模型)的创建:
models.py文件中创建class对象,一个类代表一个表,一条实例对象代表表里的一条记录:
from django.db import models # Create your models here. class Book(models.Model): name=models.CharField(max_length=20) price=models.IntegerField() pub_date=models.DateField() author=models.CharField(max_length=32, null=False) # null=False 表示字段不能为空 def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32)
同步数据表需要在python终端中执行语句:
>>> python manage.py makemigrations
>>> python manage.py migrate
分析代码:
<1> 每个数据模型都是django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法
<2> 每个模型相当于单个数据库表(多对多关系例外,会多生成一张关系表),每个属性是这个表中的字段,属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)
<3> 模型之间的三种关系:一对一,一对多,多对多
一对一:实质就是在主外键(author_id就是foreign key)的关系基础上,给外键加了一个UNIQUE=True的属性
一对多:就是主外键关系(foreign key)
多对多:(ManyToManyField) 自动创建第三张表(当然我们也可以自己创建第三张表:两个foreign key)
<4> 模型常用的字段类型参数
<5> Field重要参数
-
表的操作(增删改查):
需要在views.py文件中导入models.py:

from app01.models import * 或者 from app01 import models
*************************************** 单表操作 *********************************************
-
表记录的添加
方式一:Book() b = Book(name='python基础', price=99, author='yuan',pub_date='2017-12-12') b.save()
方式二:Book.objects.create() Book.objects.create(name='老男孩linux', price=78, author='oldboy', pub_date='2016-12-12') # 如果字典的键和表的字段名相同,也可以用以下方式创建 Book.objects.create(**dict)
from app01.models import * #create方式一: Author.objects.create(name='Alvin') #create方式二: Author.objects.create(**{"name":"alex"}) #save方式一: author=Author(name="alvin") author.save() #save方式二: author=Author() author.name="alvin" author.save()
- 表记录的修改
方式一: b=Book.objects.get(author='oldboy') b.price=123 b.save() 方式二:推荐 Book.objects.filter(author='yuan').update(price=999)
注意:
<1> 第二种方式修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)
<2> 模型的save()方法会更新一行里的所有列,效率会降低,update则只更新改动的列,所以建议用方式二
- 表记录的删除
Book.objects.filter(author='oldboy').delete()
- 表记录的查询(重点)
查询API:
# 查询相关API: <1> filter(**kwargs):包含了与所给筛选条件相匹配的对象 <2> all():查询所有结果 <3> get(**kwargs):返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误 <4> exclude(**kwargs):包含了与所给筛选条件不匹配的对象 #-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter().values()-------- <5> values(*field):返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列 <6> values_list(*field):它与values()非常相似,但返回的是一个元组序列 <7> order_by(*field):对查询结果排序 <8> reverse():对查询结果反向排序 <9> distinct():从返回结果中剔除重复纪录 <10> count():返回数据库中匹配查询(QuerySet)的对象数量 <11> first():返回第一条记录 <12> last():返回最后一条记录 <13> exists():如果QuerySet包含数据,就返回True,否则返回False
book_list = Book.objects.filter(id=2) # 即使id是唯一的,但是用filter取的仍然是一个QuerySet集合 book_list = Book.objects.exclude(author='yuan').values('name', 'price') # 排除筛选项的其他条目 book_list=Book.objects.all() book_list = Book.objects.all()[:3] # 取3条,相当于limit 3 book_list = Book.objects.all()[::2] # 隔一条取一条,直到取完 book_list = Book.objects.all()[::-1] # 倒序取 # first,last,get取到的是一个实例对象,并非一个QuerySet的集合对象 book_list = Book.objects.first() book_list = Book.objects.last() book_list = Book.objects.get(id=2) #只能取出一条记录时才不报错,没有或者多条都报错 # 取某一部分字段 ret1 = Book.objects.filter(author='yuan').values('name','price') #<QuerySet [{'name': 'python', 'price': 999}, {'name': 'PHP', 'price': 45}]> ret2 = Book.objects.filter(author='yuan').values_list('name', 'price') #<QuerySet [('python', 999), ('PHP', 45)]> book_list = Book.objects.all().values('name').distinct() #得对某些字段去重,因为存在主键,所以对整体去重没有意义 book_count = Book.objects.all().values('name').distinct().count()
模糊查询:万能的双下划线__
#---------------了不起的双下划线(__)之单表条件查询---------------- models.Tb1.objects.filter(id__lt=10, id__gte=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") # like models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 models.Tb1.objects.filter(id__range=[1, 2]) # 范围 between and startswith,istartswith, endswith, iendswith...
book_list = Book.objects.filter(name__icontains='p').values('name', 'price') #前面有i,不区分大小写 book_list = Book.objects.filter(id__gt=5).values('name', 'price')
class Teachers(models.Model): """ 老师表 """ name = models.CharField(max_length=32) # 单表操作----以Teachers表为例 # 创建数据--增 #1 Teachers.objects.create(name='root') #2 obj = Teachers(name='root') obj.save() # 获取数据--查 Teachers.objects.all() Teachers.objects.filter(id=1) Teachers.objects.filter(id=1,name='root') Teachers.objects.filter(id__gt=1) result = Teachers.objects.filter(id__gt=1) # [obj(id,name),obj(id,name)] result = Teachers.objects.filter(id__gt=1).first() # obj对象 # 删除数据--删 Teachers.objects.filter(id=1).delete() # 更新数据--改 Teachers.objects.all().update(name='alex') Teachers.objects.filter(id=1).update(name='alex')
*************************************** 多表操作(一对多) *********************************************
以书籍表Book关联出版社表Publish为例,models.py文件内容如下:
from django.db import models # Create your models here. #alter table app01_book change name name char(32) character set utf8; class Book(models.Model): name=models.CharField(max_length=20) price=models.IntegerField() pub_date=models.DateField() publish=models.ForeignKey('Publish',on_delete=models.CASCADE) #外键需要建在一对多的多的一方,foreignkey里的表名如果不想加引号,则需要注意两个表的加载顺序 def __str__(self): return self.name class Publish(models.Model): name=models.CharField(max_length=32) city=models.CharField(max_length=32) def __str__(self): return self.name
- 添加记录
方法一:通过"外键_id",例如:publish_id=2 Book.objects.create(name='linux运维', price=77, pub_date='2017-12-12', publish_id=2) 方法二:通过创建对象,例如:publish=object publish_obj=Publish.objects.filter(name='人民出版社')[0] Book.objects.create(name='GO', price=23, pub_date='2017-05-12', publish=publish_obj)
- 查询记录
# 查询人民出版社出过的所有书籍的名字和价格 方式一:正向查询(通过对象) pub_obj=Publish.objects.filter(name='人民出版社')[0] ret=Book.objects.filter(publish=pub_obj).values('name','price') 方式二:反向查询(通过对象)-> 小写类名_set 或者 related_name pub_obj = Publish.objects.filter(name='人民出版社')[0] pub_obj.book_set.all().values('name','price') 方式三:(通过filter values 的双下划线 __ ) ret=Book.objects.filter(publish__name='人民出版社').values('name','price') #Book里有外键,此处publish__name的publish为外键名 -------------------------------------------------------------------- #python这本书出版社的名字 ret2=Publish.objects.filter(book__name='python').values('name') #Publish里没有外键,所以此处book__name的book为类名 #python这本书出版社的名字 ret3=Book.objects.filter(name='python').values('publish__name') #北京的出版社出版书的名字 ret4=Book.objects.filter(publish__city='北京').values('name') #2017年上半年出版过书的出版社的名字 ret5=Book.objects.filter(pub_date__lt='2017-07-01',pub_date__gt='2017-01-01').values('publish__name')
""" 班级: id name 1 3班 2 6班 学生: id username age gender cs_id 1 小东北 18 男 1 2 小东北1 118 男 2 """ #一对多表操作----以班级表(一)关联学生表(多)为例 # 增 #1 Student.objects.create(username='小东北', age=18, gender='男', cs_id=1) #这种方式好 #2 Student.objects.create(username='小东北', age=18, gender='男', cs=Classes.objects.filter(id=1).first()) # 查 """ ret = Student.objects.all() #[] #[obj(..),] #[obj(1 小东北 18 男 1),obj(2 小东北1 118 男 2),obj(..),] for item in ret: print(item.id) print(item.username) print(item.age) print(item.gender) print(item.cs_id) print(item.cs.id) #cs代指一行数据:1 3班 print(item.cs.name) #cs代指一行数据:1 3班 """ # 删 Student.objects.filter(id=1).delete() Student.objects.filter(cs_id=1).delete() cid = input('请输入班级ID') Student.objects.filter(cs_id=cid).delete() cname = input('请输入班级名称') Student.objects.filter(cs__name=cname).delete() #在filter条件里要跨表需要使用双下划线,for循环里用点 # 改 Student.object.filter(id=1).update(username='alex', age=19, gender='男', cs_id=2) Student.object.filter(id=1).update(age=19, cs_id=2)
# 正向查通过foreignkey字段进行跨表查询,反向查通过小写类名_set或者relate_name进行跨表查询: class Classes(models.Model): """ 班级表 """ title = models.CharField(max_length=32) #通过Student表里的foreignkey致使此表存在隐含的字段student_set,可以用于反向查,默认字段命名规则:小写类名_set,也可以通过foreignkey里设置related_name进行修改,manytomany同理 class Student(models.Model): """ 学生表 """ username = models.CharField(max_length=32) age = models.IntegerField() gender = models.BooleanField() cs = models.ForeignKey(Classes, related_name='ssss', on_delete=models.CASCADE) #cs代指foreignkey表里的一行数据: 1 3班 # 正向查: ret = models.Student.objects.filter(cs__title='2班') # 反向查: obj = models.Classes.objects.filter(title='2班').first() print(obj.id) print(obj.title) print(obj.student_set.all()) print(obj.ssss.all()) ret = models.Classes.objects.all().values('id','title','ssss','ssss__username') # 注意谁是主表,相当于主表left outer join ret_student = models.Student.objects.all().values('username', 'cs__title') ret_classes = models.Classes.objects.all().values('title', 'ssss__username')
from django.db import models 班级: id title 1 3班 2 4班 3 5班 class School: name = models.CharField(max_length=32) class Classes(models.Model): """ 班级表 """ title = models.CharField(max_length=32) m = models.ManyToManyField('Teachers') sch = models.ForeignKey(School, on_delete=models.CASCADE) 老师: id name 1 Alex 2 老妖 3 xialv 4 Eric class Teachers(models.Model): """ 老师表 """ name = models.CharField(max_length=32) 学生: id username age gender cs_id 1 小东北 18 男 1 2 小东北1 118 男 2 class Student(models.Model): """ 学生表 """ username = models.CharField(max_length=32) age = models.IntegerField() gender = models.BooleanField() cs = models.ForeignKey(Classes,on_delete=models.CASCADE) # cs是Classes的一个对象(title, sch是School的对象(name)) 1、类代表数据库表 2、类的对象代指数据库的一行记录 3、FK字段代指关联表中的一行数据(类的对象) 4、 - 正向查:foreignkey字段 (推荐用正向查) - 反向查:小写类名_set (默认) ==> related_name='ssss' 5、谁是主表?就全部列出其数据 models.Student.objects.all().values('username', 'cs__title') models.Classes.objects.all().values('title', 'ssss__username') 6、M2M字段,自动生成第三张表,依赖关联表对第三张表间接操作 示例: - 所有学生的姓名以及其所在班级名称,QuerySet stu_list = Student.objects.all() select * from table; [obj, obj, obj, obj,] for row in stu_list: row.username, row.cs_id, row.cs.title stu_list = Student.objects.all().values('id','username') select id, username from table; [{'id':1, 'username':'xx'}, {'id':2, 'username':'yy'},] stu_list = Student.objects.all().values_list('id','username') [(1,'root'), (2,'alex'),] stu_list = Student.objects.all().values('username','cs__title') for row in stu_list: print(row['username'], row['cs__title']) - 所有学生的姓名以及其所在班级名称和所在校区名称(跨多次表) stu_list = Student.objects.all().values('username', 'cs__title', 'cs__sch__name') - 找到3班的所有学生 Student.objects.filter(cs__title='3班') obj = Classes.objects.filter(title='3班')
*************************************** 多表操作(多对多) *********************************************
以书籍表Book关联作者表Author为例,分为利用manytomany字段自动创建第三张表和手动创建第三张表两种方式,models.py文件内容如下:
from django.db import models # Create your models here. #alter table app01_book change name name char(32) character set utf8; class Book(models.Model): name=models.CharField(max_length=20) price=models.IntegerField() pub_date=models.DateField() #自动创建第三张表,创建多对多的关系,推荐,表名为app01_book_authors(authors是取自manytomany字段) **手动创建时不写这个字段 authors=models.ManyToManyField('Author') def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField(default=20) # default为设置默认值 def __str__(self): return self.name #手动创建第三张表,相当于创建两个一对多字段
class Book_Author(models.Model): book=models.ForeignKey('Book',on_delete=models.CASCADE) author=models.ForeignKey('Author',on_delete=models.CASCADE)
- 添加、查询和删除记录——自动创建第三张表(推荐)
#通过manytomany自动创建多对多 authors=models.ManyToManyField('Author')—>通过对象的方式绑定关系 1、查询,通过manytomany字段 正向查询: # id=3的书籍对象的所有关联作者 book_obj = Book.objects.get(id=3) obj = book_obj.authors.all() 反向查询:通过小写类名_set # id=2的作者出的书籍 author_obj = Author.objects.get(id=2) obj = author_obj.book_set.all() 掌握:通过 filter values (双线划线)结合manytomany字段进行多对多的关联查询,例如:alex出过的书籍名称,价格和作者 ret2 = Book.objects.filter(authors__name='alex').values('name','price','authors__name') #authors是manytomany的字段 ret3 = Author.objects.filter(book__name='python').values('name') #Author表里没有manytomany字段,此时同一对多,book__name中book为类名,正向查询均用关联字段,反向查询没有关联字段,用小写关联表名 2、添加:通过 m.add ;反向添加:通过 小写类名_set.add 或者 related_name.add 添加一个作者:为id=4的书籍添加一个id=2的作者 book_obj = Book.objects.get(id=4) author_obj = Author.objects.get(id=2) book_obj.authors.add(author_obj) 添加多个作者:add里传入QuerySet集合列表,m.add(*QuerySet),为id=3的书籍添加所有作者 book_obj = Book.objects.get(id=3) author_objs = Author.objects.all() book_obj.authors.add(*author_objs) 3、删除,通过m.remove 删除一个绑定对象: obj.remove(author_obj) 删除多个多个绑定对象: book_obj.authors.remove(*author_objs) 传入数字代表id,下面代表的书籍关联的作者id book_obj.authors.remove(2) #里面的数字代表id
- 添加、查询和删除记录——手动创建第三张表
如果想向第三张表插入值的方式绑定关系:手动创建第三张表 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=2, author_id=3) 查询: 通过对象:id=2的书籍的第一个作者 obj = Book.objects.get(id=2) obj.book_author_set.all()[0].author 通过filter values 双下划线,例如:alex出过的书籍名称及价格 ret = Book.objects.filter(book_author__author__name='alex').values('name','price')
# 多对多----以老师表和班级表为例: class Classes(models.Model): """ 班级表 """ title = models.CharField(max_length=32) m = models.ManyToManyField('Teachers', related_name='sssss') #manytomany时不会生成该字段,但是会自动生成第三张表,这个是间接操作第三张表的桥梁 class Teachers(models.Model): """ 老师表 """ name = models.CharField(max_length=32) """ 班级: id title 1 3班 2 4班 3 5班 老师: id name 1 Alex 2 老妖 3 xialv 4 Eric 老师班级关系表(类): id 班级id 老师id 1 1 2 2 1 3 #3 1 4 4 2 2 5 2 3 6 2 4 7 1 5 """ # 增 1、正向添加 obj = Classes.objects.filter(id=1).first() # 1 3班 obj.m.add(2) #通过obj.m对第三张表进行操作 obj.m.add([3,4]) obj = Classes.objects.filter(id=2).first() # 2 4班 obj.m.add(2) #通过obj.m对第三张表进行操作 obj.m.add([3,4]) 2.1、反向添加,类名_set obj = Teachers.objects.filter(id=2).first() obj.classes_set.add(3) 2.2、反向添加,related_name='sssss' obj = models.Teachers.objects.filter(id=2).first() obj.sssss.add(2) # 删除和清空 obj = Classes.objects.filter(id=1).first() # 1 3班 1、删除 obj.m.remove([4,3]) #把班级id=1的,老师id=3和4的删除 2、清空 obj.m.clear() #把班级id=1的都删除 # 设置(修改前后都有的,保留原来的不动) 1、set中有,原先也有-->保留 2、set中没有,原先有-->删除 3、set中有,原先没有-->添加 #正向设置: obj = Classes.objects.filter(id=1).first() # 1 3班 obj.m.set([2,3,5]) #把班级id=1的老师id换成2,3,5 #反向设置: obj = Teachers.objects.filter(id=2).first() obj.sssss.set([1,2]) # 查看 #列举3班的所有老师列举 obj = Classes.objects.filter(id=1).first() obj.id obj.title ret = obj.m.all() #ret是一个列表[老师1(id,name), obj(id,name),] #正向查: v = Classes.objects.all().values('id', 'title', 'm', 'm__name') #反向查: v = models.Teachers.objects.all().values('name','sssss__title') 注意:操作m不能通过.values的方式,因为此时是字典,只能通过.all(),.filter()等对象的方式操作m v = models.Classes.objects.all().values('id', 'title', 'm', 'm__name') for item in v: print(item['m'],type(item['m'])) # type(item['m'])-->int
聚合查询和分组查询:
使用前需要导入相关函数:
from django.db.models import Avg,Min,Sum,Max,Count
<1> aggregate(*args,**kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值,即在查询集上生成聚合。
from django.db.models import Avg,Min,Sum,Max,Count 从整个查询集生成统计值,比如,你想要计算所有书的平均价钱,Django的查询语法提供了一种方式描述所有图书的集合:
ret = Book.objects.all().aggregate(Avg('price')) ret = Book.objects.all().aggregate(Sum('price')) aggregate()子句的参数描述了我们想要计算的聚合值 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典,键的名称是聚合值的 标识符,值是计算出来的聚合值,键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定 一个名称,可以向聚合子句提供它: ret = Book.objects.filter(authors__name='alex').aggregate(alex_money=Sum('price')) count里面可以传入任何一个字段名称: ret = Book.objects.filter(authors__name='alex').aggregate(Count('name')) 如果你也想知道所有图书价格的平均值、最大值和最小值,可以这样查询:
ret = Book.objects.all().aggregate(Avg('price'), Max('price'), Min('price'))
<2> annotate(*args,**kwargs):
可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合,即group by操作。
查询各个作者出的书的总价格: ret = Book.objects.values('authors__name').annotate(Sum('price')) 查询各个出版社最便宜的书价是多少:
ret = Publish.objects.values('name').annotate(abc=Min('book__price')) 或 ret = Book.objects.values('publish__name').annotate(Min('price'))
F查询和Q查询:
仅仅靠单一的关键字参数查询已经很难满足查询要求,此时Django为我们提供了F和Q查询:
from django.db.models import F,Q
F 使用查询条件的值,专门取对象中某列值的操作:
所有书的价格加10: Book.objects.all().update(price=F('price')+10)
关键字查询可以满足条件"与"的查询,但是如果要进行条件"或"和"非"的条件查询,就需要使用 Q 查询:
# Q查询中的或与非:| ~ ret = Book.objects.filter(Q(price=87)|~Q(name='GO')) # Q查询也可以使用双下划线方法: ret = Book.objects.filter(Q(name__contains='G')) # Q查询和关键字查询一起用时,要放在前面:
ret = Book.objects.filter(Q(name='GO'), price=87) # 条件与,用&或逗号隔开条件即可: ret = Book.objects.get(name='GO', price=77)
惰性机制:
所谓惰性机制: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可切片 objs[1] objs[1:4] 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(): #会执行sql,但是不会把数据放到缓存里 print("hello world!") <5> 当queryset非常巨大时,cache会成为问题,处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法来获取数据,处理完数据就将其丢弃。 ret = Book.objects.filter(price=100) ret = ret.iterator() #因为是迭代器,所以缓存里不会再保留查询数据 for i in ret: print(i.name) # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 # 但是,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 # 当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询,所以使用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 总结:queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能会造成额外的数据库查询。

浙公网安备 33010602011771号