Django基础
Django基础
web框架
框架, 即framework, 特指为解决一个开放性问题而设计的具有一定约束性的支撑结构. 使用框架可以帮助快速开发特定的系统. 简单地说, 就是用别人搭建好的舞台来做表演.
对于所有的Web应用, 本质上其实就是一个socket服务端, 用户的浏览器其实就是一个socket客户端.
import socket def handle_request(client): buf = client.recv(1024) print(buf.decode('utf8')) # 浏览器请求内容 client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) with open('index.html','rb') as f: data = f.read() client.send(data) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8003)) 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响应都都需要写很多代码还要根据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): # 通过environ封装成一个所有请求信息的对象 # start_response可以方便的设置响应头 print('environ_path:', environ['PATH_INFO']) #在浏览器输入http://localhost:8080/book, 会得到environ_path: /book start_response('200 OK', [('Content-Type', 'text/html')]) #响应头, contenttype告诉浏览器(客户端)下面的响应体是html类型, 浏览器就可以按这个类型解析 return [b'<h1>Hello, web!</h1>'] #响应体 # 封装了socket对象和准备过程(socket, bind,listen) httpd = make_server('', 8080, application) print('Serving HTTP on port 8080...') # 开始监听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 def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 设置响应头 path = environ['PATH_INFO'] if path == '/book': return [b'<h1>Hello, book!</h1>'] #响应体 elif path == '/web': return [b'<h1>Hello, web!</h1>'] #响应体 else: return ['<h1>404</h1>'.encode('utf8')] #响应体 # 封装了socket对象和准备过程(socket, bind,listen) httpd = make_server('', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever() # 触发application的执行
再进阶:
from wsgiref.simple_server import make_server def f1(): f1=open("index.html","rb") data1=f1.read() return [data1] def f2(): f2=open("index1.html","rb") data2=f2.read() return [data2] def application(environ, start_response): print(environ['PATH_INFO']) path=environ['PATH_INFO'] start_response('200 OK', [('Content-Type', 'text/html')]) if path=="/web": return f1() elif path=="/book": return f2() else: return ["<h1>404</h1>".encode("utf8")] httpd = make_server('', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()
最终:
from wsgiref.simple_server import make_server def f1(req): print(req) print(req["QUERY_STRING"]) f1=open("index.html","rb") data1=f1.read() return [data1] # return [b'<h1>Hello, book!</h1>'] # 响应体 def f2(req): f2=open("index1.html","rb") data2=f2.read() return [data2] # return [b'<h1>Hello, web!</h1>'] # 响应体 import time def current_time(request): #模版以及数据库 # cur_time = time.ctime(time.time()) cur_time = time.strftime("%Y-%m-%d %X", time.localtime()) with open('itime.html','rb') as f: data = f.read() data = str(data,'utf8').replace('!cur_time!',str(cur_time)) #!cur_time!为自定义的模板语言 return [data.encode('utf8')] def routers(): urlpatterns = ( ('/web',f1), ('/book',f2), ('/time', current_time) # .... ) return urlpatterns def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 设置响应头 path = environ['PATH_INFO'] 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")] # 封装了socket对象和准备过程(socket, bind,listen) httpd = make_server('', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()
MVC,MVP,MVVM和MTV模式
MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层;他们之间以一种插件似的, 松耦合的方式连接在一起.
模型(Model)负责业务对象与数据库的对象(ORM) - 数据保存 (数据库)
视图(View)负责与用户的交互(页面) - 用户界面 (前端网页)
控制器(Controller)接受用户的输入调用模型和视图完成用户的请求 - 业务逻辑 (映射,模式渲染等)
通信:
- View 传送指令到 Controller
- Controller 完成业务逻辑后, 要求 Model 改变状态
- Model 将新的数据发送到 Controller, 再由 Controller 发送到 View, 用户得到反馈.

接受用户指令时, MVC 可以分成两种方式:
一种是通过 View 接受指令, 传递给 Controller. 
另一种是直接通过controller接受指令. 
MVP模式: MVP 模式将 Controller 改名为 Presenter, 同时改变了通信方向.
1. 各部分之间的通信, 都是双向的.
2. View 与 Model 不发生联系, 都通过 Presenter 传递.
3. View 不部署任何业务逻辑, 称为"被动视图" (Passive View), 即没有任何主动性. 所有逻辑都部署在 Presenter.
MVVM模式: MVVM 模式将 Presenter 改名为 ViewModel, 基本上与 MVP 模式完全一致.

唯一的区别是, 它采用双向绑定 (data-binding): View的变动, 自动反映在 ViewModel, 反之亦然.
Django的MTV模式本质上与MVC模式没有什么差别, 也是各组件之间为了保持松耦合关系, 只是定义上有些许不同, Django的MTV分别代表:
Model(模型):负责业务对象与数据库的对象(ORM). 数据存取 - 处理与数据相关的所有事务: 如何存取、如何验证有效
Template(模版):负责如何把页面展示给用户. 表现 - 处理与表现相关的决定: 如何在页面或其他类型文档中进行显示
View(视图):负责业务逻辑, 并在适当的时候调用Model和Template. 业务逻辑 - 包含存取模型及调取恰当模板的相关逻辑, 模型与模板之间的桥梁.
此外, Django框架本身就类型MVC中的Controller控制器, 包含一个url分发器, 它的作用是根据用户输入委派视图的部分, 由 Django 框架根据 URLconf 设置, 对给定 URL 调用适当的 Python 函数. (即将一个个URL的页面请求分发给不同的view处理, view再调用相应的Model和Template).

Django的处理顺序:

1、wsgi - socket请求处理
2、控制器 (Django框架本身)- 控制用户输入, url匹配, 通过映射列表将一个请求发送到一个合适的视图.
3、视图views (Views) - python程序, 向模型和模板发送(或获取)数据.
4、模型绑定 (Model) - 数据库存取数据.
5、模板引擎 (Templates) - 用于将内容与展现分离, 描述了数据如何展现(如网页模板).
6、模式渲染 (Views) - 将模板和数据整合, 形成最终网页.
7、控制器(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配置 STATICFILES_DIRS=( os.path.join(BASE_DIR,"statics"), ) STATIC_URL = '/static/' # 不管STATIC_URL内容是什么, STATIC_URL会按着你的STATICFILES_DIRS去找, 即用STATIC_URL的路径也可以找到STATICFILES_DIRS的路径 #4 根据需求设计代码 url.py view.py #5 使用模版 render(req,"index.html") #6 启动项目 python manage.py runserver 127.0.0.1:8080 #7 连接数据库,操作数据 model.py
Django的命令行工具(可在pycharm的Terminal输入)
django-admin.py 是Django的一个用于管理任务的命令行工具.
manage.py是对django-admin.py的简单包装,每一个Django Project里都会有一个manage.py.
<1> 创建一个django工程 : django-admin.py startproject mysite
当前目录下会生成mysite的工程,目录结构如下:

- manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库等.
- settings.py ---- 包含了项目的默认设置, 包括数据库信息,调试标志以及其他一些工作的变量.
- urls.py ----- 负责把URL模式映射到应用程序.
- wsgi.py ----- wsgi服务器的配置文件.
<2>在mysite目录下创建blog应用: python manage.py startapp blog
Project与app区别:
Project:包含多个django app 以及多个相关配置
App:一套django功能的集合,通常包含模型和视图
<3>启动django项目:python manage.py runserver 8080
Django启动, 当访问:http://127.0.0.1:8080/时就可以看到:

<4>生成同步数据库的脚本:python manage.py makemigrations
同步数据库: python manage.py migrate
python manage.py migrate --fake 本地跟数据库同步
注意:在开发过程中, 数据库同步误操作之后, 难免会遇到后面不能同步成功的情况, 解决这个问题的一个简单粗暴方法是把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 可以看到详细的列表,在忘记子名称的时候特别有用.
例子: 提交数据并展示
-----------------------userInfor.html--------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>创建个人信息</h1> <form action="/userInfor/" method="post"> <p>姓名<input type="text" name="username"></p> <p>性别<input type="text" name="sex"></p> <p>邮箱<input type="text" name="email"></p> <p><input type="submit" value="submit"></p> </form> <hr> <h1>信息展示</h1> <table border="1"> <tr> <td>姓名</td> <td>性别</td> <td>邮箱</td> </tr> {% for i in info_list %} <tr> <td>{{ i.username }}</td> <td>{{ i.sex }}</td> <td>{{ i.email }}</td> </tr> {% endfor %} </table> </body> </html> -----------------------url.py--------------------------------------- urlpatterns = [ path('userinfo/', views.userinfo), ] -----------------------views.py-------------------------------------- info_list=[] def userInfor(req): if req.method=="POST": username=req.POST.get("username",None) sex=req.POST.get("sex",None) email=req.POST.get("email",None) info={"username":username,"sex":sex,"email":email} info_list.append(info) return render(req,"userInfor.html",{"info_list":info_list})
例子: 提交数据并展示(数据库)
-----------------------userInfor.html--------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>创建个人信息</h1> <form action="/userInfor/" method="post"> <p>姓名<input type="text" name="username"></p> <p>性别<input type="text" name="sex"></p> <p>邮箱<input type="text" name="email"></p> <p><input type="submit" value="submit"></p> </form> <hr> <h1>信息展示</h1> <table border="1"> <tr> <td>姓名</td> <td>性别</td> <td>邮箱</td> </tr> {% for i in info_list %} <tr> <td>{{ i.username }}</td> <td>{{ i.sex }}</td> <td>{{ i.email }}</td> </tr> {% endfor %} </table> </body> </html> ----------------------models.py--------------------------------- from django.db import models class UserInfor(models.Model): username=models.CharField(max_length=64) sex=models.CharField(max_length=64) email=models.CharField(max_length=64) -----------------------url.py--------------------------------------- urlpatterns = [ path('userinfo/', views.userinfo), ] -----------------------views.py-------------------------------------- from django.shortcuts import render from app01 import models def userInfor(req): if req.method=="POST": u=req.POST.get("username",None) s=req.POST.get("sex",None) e=req.POST.get("email",None) #---------表中插入数据方式一 # info={"username":u,"sex":e,"email":e} # models.UserInfor.objects.create(**info) #---------表中插入数据方式二 models.UserInfor.objects.create( username=u, sex=s, email=e ) info_list=models.UserInfor.objects.all() return render(req,"userInfor.html",{"info_list":info_list}) return render(req,"userInfor.html")
Django的配置文件(settings)
静态文件设置:
静态文件交由Web服务器处理, Django本身不处理静态文件.
MEDIA_ROOT和MEDIA_URL (没懂)
静态文件的处理又包括STATIC和MEDIA两类.
#而静态文件的处理又包括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; }
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 staticfiles %} # <script src={% static "jquery-1.8.2.min.js" %}></script>
其它重要参数设置:
APPEND_SLASH Default: True # When set to True, if the request URL does not match any of the patterns in the URLconf and it doesn’t end in a slash, an HTTP redirect is issued to the same URL with a slash appended. # Note that the redirect may cause any data submitted in a POST request to be lost.
Setting汇总 - 参考
Django settings详解 1.基础 DJANGO_SETTING_MODULE环境变量:让settings模块被包含到python可以找到的目录下,开发情况下不需要,我们通常会在当前文件夹运行,python可以搜索到。如果需要运行在其他服务器上,就必须指定DJANGO_SETTINGS_MODULE变量。 2.默认设定 一个django的settings文件不需要我们手动去设置所有项目,因为系统已经默认设置好了。我们只需要修改我们使用的设 定就好了。默认的设置在django/conf/global_settings.py文件中。django在编译时,先载入global_settings.py中的配置,然后加载指定的settings文件,重写改变的设定。 好了,步入正文。 前面的随笔中我们经常会改setting配置也经常将一些配置混淆今天主要是将一些常见的配置做一个汇总。 setting配置汇总 1、app路径 1 2 3 4 5 6 7 8 9 10 11 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app1.apps.App1Config', # 默认已有 如果没有只要添加app名称即可 例如: 'app1' # 新建的应用都要在这里添加 ] 2、数据库配置 如果使用django的默认sqlite3数据库则不需要改 1 2 3 4 5 6 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } 如果使用mysql数据库需要将上述数据库注掉修改如下 1 2 3 4 5 6 7 8 9 10 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'blog', #你的数据库名称 数据库需要自己提前建好 'USER': 'root', #你的数据库用户名 'PASSWORD': '', #你的数据库密码 'HOST': '', #你的数据库主机,留空默认为localhost 'PORT': '3306', #你的数据库端口 } } 并且需要在应用的__init__.py文件添加 1 2 import pymysql pymysql.install_as_MySQLdb() 详情可以查看:http://www.jb51.net/article/128674.htm 3、sql语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } 当你的操作与数据库相关时 会将我们的写的语句翻译成sql语句在服务端打印。 4、静态文件目录 1 2 3 4 STATIC_URL = '/static/' #调用时目录 STATICFILES_DIRS=[ os.path.join(BASE_DIR,"static"), #具体路径 ] 5、如果数据库中的UserInfo(用户表)继承django内置AbstractUser 1)model需导入 1 from django.contrib.auth.models import AbstractUser 2)配置文件 1 AUTH_USER_MODEL = "应用名.UserInfo" 6、中间件 自己写的中间件,例如在项目中的md文件夹下md.py文件中的M1与M2两个中间件 1 2 3 4 5 6 7 8 9 10 11 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'md.md.M1', 'md.md.M2', ] 注意自己写的中间件,配置要写在系统中的后面 7、session存储的相关配置 1)数据库配置(默认) 1 2 3 4 5 6 7 8 9 10 11 Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认) SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认) 2)缓存配置 1 2 3 4 5 6 7 8 9 10 11 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 3)默认配置 1 2 3 4 5 6 7 8 9 10 11 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 注意: 1)也可以自定义配置 但是自定义的配置都要写到配置文件最后 代码中使用时可以导入配置 1 2 from django.conf import settings settings.配置名 2)上面所有配置都是针对特定问题需要修改的 系统默认配置不做说明 3)上面配置只是前面django系列随笔所遇到的常用配置 后续所遇配置都会逐步在此随笔中持续添加跟新 下面看段django settings最佳配置实例代码,具体代码如下所示: # encoding=utf-8 import os import socket SITE_ID = 1 # 项目的根目录 # 简化后面的操作 PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) # 加载应用 # 把应用添加到INSTALLED_APPS中 from apps.kuser.mysetting import myapp as kuser_app from apps.blog.mysetting import myapp as blog_app MY_APPS = blog_app + kuser_app # 加载静态文件 from apps.blog.mysetting import my_staticfiles as blog_staticfiles from apps.kuser.mysetting import my_staticfiles as kuser_staticfiles MY_STATIC_DIRS = blog_staticfiles + kuser_staticfiles # 加载模板文件 from apps.blog.mysetting import my_templates as blog_templates from apps.kuser.mysetting import my_templates as kuser_templates MY_TEMPLATE_DIRS = blog_templates + kuser_templates # 密钥配置 # 适用于开发环境和部署环境 # 可以从系统环境中,配置文件中,和硬编码的配置中得到密钥 try: SECRET_KEY = os.environ['SECRET_KEY'] except: try: with open(os.path.join(PROJECT_ROOT, 'db/secret_key').replace('\\', '/')) as f: SECRET_KEY = f.read().strip() except: SECRET_KEY = '*lk^6@0l0(iulgar$j)faff&^(^u+qk3j73d18@&+ur^xuTxY' # 得到主机名 def hostname(): sys = os.name if sys == 'nt': hostname = os.getenv('computername') return hostname elif sys == 'posix': host = os.popen('echo $HOSTNAME') try: hostname = host.read() return hostname finally: host.close() else: raise RuntimeError('Unkwon hostname') #调试和模板调试配置 #主机名相同则为开发环境,不同则为部署环境 #ALLOWED_HOSTS只在调试环境中才能为空 if socket.gethostname().lower() == hostname().lower(): DEBUG = TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] else: ALLOWED_HOSTS = [ 'baidu.com', '0.0.0.0', ] DEBUG = TEMPLATE_DEBUG = False #数据库配置 MYDB = { 'mysql': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'books', #你的数据库名称 'USER': 'root', #你的数据库用户名 'PASSWORD': '', #你的数据库密码 'HOST': '', #你的数据库主机,留空默认为localhost 'PORT': '3306', #你的数据库端口 }, 'sqlite': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(PROJECT_ROOT, 'db/db.sqlite3').replace('\\', '/'), } } # 给静态文件url一个后缀,在templates里用到的。 # 映射到静态文件的url # STATIC_URL的含义与MEDIA_URL类似 STATIC_URL = '/static/' # 总的static目录 # 可以使用命令 manage.py collectstatic 自动收集static文件 # STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static').replace('\\', '/') #放各个app的static目录及公共的static目录 #STATICFILES_DIRS:和TEMPLATE_DIRS的含义差不多,就是除了各个app的static目录以外还需要管理的静态文件设置, #比如项目的公共文件差不多。然后给静态文件变量赋值,告诉Django,静态文件在哪里 #另外,Django提供了一个findstatic命令来查找指定的静态文件所在的目录,例如:D:\TestDjango>python manage.py findstatic Chrome.jpg # 默认情况下(如果没有修改STATICFILES_FINDERS的话),Django首先会在STATICFILES_DIRS配置的文件夹中寻找静态文件,然后再从每个app的static子目录下查找, # 并且返回找到的第一个文件。所以我们可以将全局的静态文件放在STATICFILES_DIRS配置的目录中,将app独有的静态文件放在app的static子目录中。 # 存放的时候按类别存放在static目录的子目录下,如图片都放在images文件夹中,所有的CSS都放在css文件夹中,所有的js文件都放在js文件夹中。 STATICFILES_DIRS = ( ("downloads", os.path.join(PROJECT_ROOT, 'static/downloads').replace('\\', '/')), ("uploads", os.path.join(PROJECT_ROOT, 'static/uploads').replace('\\', '/')), ) # 将app中的静态文件添加到静态文件配置列表中 STATICFILES_DIRS += MY_STATIC_DIRS # 最后关键的部分是STATICFILES_DIRS以下配置 # 简要说一下,static文件夹在项目里,有css js images 三个文件夹(看项目结构),他们的路径分别是: # os.path.join(STATIC_ROOT,'css'),os.path.join(STATIC_ROOT,'js'),os.path.join(STATIC_ROOT,'images'); # 我们分别给他们起三个别名css,js,images(你可以随意给,不过为了易记,我们原名称指定别名了) TEMPLATE_DIRS = ( os.path.join(PROJECT_ROOT, 'templates').replace('\\', '/'), ) # 配置应用的模板文件路径 TEMPLATE_DIRS += MY_TEMPLATE_DIRS # 配置缓存 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', 'KEY_PREFIX': 'lcfcn', 'TIMEOUT': None } } LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/auth/login/' LOGOUT_URL = '/auth/logout/' # 指用户上传的文件,比如在Model里面的FileFIeld,ImageField上传的文件。如果你定义 # MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/"),上传的文件就会被保存到c:\temp\media\abc。MEDIA_ROOT必须是本地路径的绝对路径。 MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'static/uploads') # MEDIA_URL是指从浏览器访问时的地址前缀。 MEDIA_URL = '/uploads/' # 应用注册列表 INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.sitemaps', ) #为了不和系统应用混合,自己开发的应用放在这里 # 将自己写的app添加到应用列表中去 INSTALLED_APPS += MY_APPS # django 中间件 # django处理一个Request的过程是首先通过django 中间件,然后再通过默认的URL方式进行的。 # 所以说我们要做的就是在django 中间件这个地方把所有Request拦截住, # 用我们自己的方式完成处理以后直接返回Response,那么我们可以简化原来的设计思路, # 把中间件不能处理的 Request统统不管,丢给Django去处理。 MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', ) ROOT_URLCONF = 'lcforum.urls' WSGI_APPLICATION = 'lcforum.wsgi.application' #数据库配置 DATABASES = { 'default': MYDB.get('sqlite'), } # 语言 LANGUAGE_CODE = 'zh-cn' # 时区 TIME_ZONE = 'Asia/Shanghai' USE_TZ = True # 在template中使用静态文件 # 采用这种方式需要有一些额外配置,打开settings.py,确认TEMPLATE_CONTEXT_PROCESSORS中包含有'django.core.context_processors.static' # TEMPLATE_CONTEXT_PROCESSORS = ( # 'django.core.context_processors.debug', # 'django.core.context_processors.i18n', # 'django.core.context_processors.media', # 'django.core.context_processors.static', # 'django.contrib.auth.context_processors.auth', # 'django.contrib.messages.context_processors.messages', # # 'django.core.context_processors.tz', # 'django.contrib.messages.context_processors.messages', # # 'blog.context_processors.custom_proc',自定义函数 # ) #from django.conf import settings #gettext = lambda s: s #getattr() # 假设有个工程djangodemo,有两个app为demo1跟demo2 # django处理static的方法是把各个app各自的static合并到一处 # 比如: # djangodemo/djangodemo/static 放置公共静态文件 # djangodemo/demo1/static 放置该app自己的静态文件 # djangodemo/demo2/static 放置该app自己的静态文件 # 可以这么设置: # STATIC_ROOT = '/www/djangodemo/djangodemo/static ' # STATIC_URL = '/static/' # STATICFILES_DIRS = ( # 'djangodemo/static', # 'demo1/static/', # 'demo2/static/', # ) # 使用命令 # manage.py collectstatic # 就会自动把所有静态文件全部复制到STATIC_ROOT中 # 如果开启了admin,这一步是很必要的,不然部署到生产环境的时候会找不到样式文件 # 不要把你项目的静态文件放到这个目录。这个目录只有在运行manage.py collectstatic时才会用到
Django URL (路由系统)
URL配置(URLconf)就像 Django 所支撑网站的目录. 它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表. (即配置告诉Django, 对于这个URL调用这段代码, 对于那个URL调用那段代码).
Django1.X的路由语法和例子:
# 语法 urlpatterns = [ url(正则表达式, views视图函数,参数,别名), ] #例子 from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), #no_named group url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), #name group url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ]
Django2.0中URL的路由机制
路由是关联url及其处理函数关系的过程. Django的url路由配置在settings.py文件中ROOT_URLCONF变量指定全局路由文件名称. Django的路由都写在urls.py文件中的urlpatterns列表中, 由path()或re_path()作为元素组成.
Django的URL路由流程:
- Django查找全局urlpatterns变量(urls.py)
- 按照先后顺序,对URL逐一匹配urlpatterns每个元素
- 找到第一个匹配时停止查找,根据匹配结果执行对应的处理函数。
- 如果没有找到匹配或出现异常,Django进行错误处理
注意:
Django的路由不考虑HTTP请求方式, 仅根据URL进行路由, 即只要URL相同,无论POST、GET等哪种请求方式都指向同一个操作函数.
Urlpatterns中的path()处理字符串路由, re_path()处理正则表达式路由.
Django2.0提供了更简单的路由语法 (Simplified URL routing syntax). 它新增了 django.urls.path 函数, 包括了更加简洁、可读的路由语法.
其格式:
from django.urls import path, re_path urlpatterns=[ path(route, views.函数名, 向视图函数提供的额外参数(以字典形式表示), 该URL模式的别名), re_path(正则表达式,view.对应的处理函数) ]
Django支持三种表达route:
1、 精确字符串格式: 一个精确URL匹配一个操作函数;最简单的形式, 适合对静态URL的响应;URL字符串不以"/"开头, 但要以"/"结尾.
例子1: path(articles/2018/, views.case_2018)
请求URL: /articles/2018/
视图函数调用形式: views.case_2018(request)
例子2: path(articles/2018/, views.case_2018)
请求URL: /articles/2018
视图函数调用形式: -
2、 Django的转换格式 - <类型:变量名>: 通过转化器转化, 匹配URL同时在其中获得一批变量作为参数;是一种常用形式, 目的是通过URL进行参数获取和传递. 单个参数可以*args接收, 多个参数可以**kwargs接收.
path转化器(path converter)

例子1: path(articles/2018/<int:year>/, views.year_archive)
请求URL: /articles/2018/9/
视图函数调用形式: views.year_archive(request, year=2018)
例子2: path(articles/<int:year>/<int:month>/, views.month_archive)
请求URL: /articles/2018/9/
视图函数调用形式: views.month_archive(request, year=2018, month=9)
例子3: path(articles/<int:year>/<int:month>/<slug>/, views.details)
请求URL: /articles/2018/9/haha/
视图函数调用形式: views.details(request, year=2018, month=9, slug="haha")
自定义转化器
自定义path转换器 其实就是写一个类,并包含下面的成员和属性: 1. 类属性regex:一个字符串形式的正则表达式属性; 2. to_python(self, value) 方法:一个用来将匹配到的字符串转换为你想要的那个数据类型,并传递给视图函数。如果转换失败,它必须弹出ValueError异常; 3. to_url(self, value)方法:将Python数据类型转换为一段url的方法,上面方法的反向操作. # 例如,新建一个converters.py文件,与urlconf同目录,写个下面的类: class FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value # 写完类后,在URLconf 中注册,并使用它,如下所示,注册了一个yyyy: from django.urls import register_converter, path from . import converters, views register_converter(converters.FourDigitYearConverter, 'yyyy') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<yyyy:year>/', views.year_archive), ... ]
3、 正则表达式格式: 使用 django.urls.re_path函数, 使用该方法时, 前面不能使用path()函数, 必须使用re_path()函数;表达的全部是str格式, 不能是其他类型.
两种形式:
- 不提取参数.
例子: re_path(articles/([0-9]{4}/, views.xxx), 表示四位数字, 每一个数字都是0到9的任意数字;
- 提取参数, 命名形式(?P<name>pattern).
例子: re_path(articles/(?p<year>[0-9]{4})/, views.xxx), 将正则表达式提取的四位数字, 每一个数字都是0到9的任意数字命名为year.
额外参数
例子: re_path(articles/(?P<year>[0-9]{4})/$', views.para_test, {'foo': 'bar'})
请求URL: /articles/2018/
视图函数调用形式: views.para_test(request, year='2018', foo='bar')
别名
#########url.py########## urlpatterns = [ path('index/',views.index,name='bieming'), ] ########view.py########### def index(req): if req.method=='POST': username=req.POST.get('username') password=req.POST.get('password') if username=='alan' and password=='123': return HttpResponse("登陆成功") return render(req,'index.html') #########index.html############ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> # <form action="/index/" method="post"> <form action="{% url 'bieming' %}" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> <input type="submit" value="submit"> </form> </body> </html> # 总结: 使用<form action="/index/" method="post">的话, 若是url.py中'index/'路径改变, 在页面中也得改. 而使用了别名, 以<form action="{% url 'bieming' %}" method="post">这种方式, 不管路径有没有改都不影响它找到页面.
Include()用法
当视图函数路径较多时, 可以使用Include()进行去重.
当网站功能较多时可以在该功能文件夹(app的文件夹)里建一个urls.py文件, 将该功能模块下的url全部写在该文件里. 但是要在全局的urls.py中使用include方法实现url映射分发.
例子:
网站有论坛模块, 则在论坛模块下建个urls.py文件, 将与论坛相关的页面的url全部写在这个文件里. # 在全局的urls.py文件里这样写: from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('ant_test/',include('ant_test.urls')) ] # 在ant_test的urls.py文件这样写: from django.urls import path urlpatterns = [ path('news/',views.news), ] # 在ant_test的views.py中写对应的news函数即可.
Reverse 和 Resolve
https://docs.djangoproject.com/en/2.0/ref/urlresolvers/
reverse()¶ If you need to use something similar to the url template tag in your code, Django provides the following function: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)[source]¶ viewname can be a URL pattern name or the callable view object. For example, given the following url: from news import views path('archive/', views.archive, name='news-archive') you can use any of the following to reverse the URL: # using the named URL reverse('news-archive') # passing a callable object # (This is discouraged because you can't reverse namespaced views this way.) from news import views reverse(views.archive) If the URL accepts arguments, you may pass them in args. For example: from django.urls import reverse def myview(request): return HttpResponseRedirect(reverse('arch-summary', args=[1945])) You can also pass kwargs instead of args. For example: >>> reverse('admin:app_list', kwargs={'app_label': 'auth'}) '/admin/auth/' args and kwargs cannot be passed to reverse() at the same time. If no match can be made, reverse() raises a NoReverseMatch exception. The reverse() function can reverse a large variety of regular expression patterns for URLs, but not every possible one. The main restriction at the moment is that the pattern cannot contain alternative choices using the vertical bar ("|") character. You can quite happily use such patterns for matching against incoming URLs and sending them off to views, but you cannot reverse such patterns. The current_app argument allows you to provide a hint to the resolver indicating the application to which the currently executing view belongs. This current_app argument is used as a hint to resolve application namespaces into URLs on specific application instances, according to the namespaced URL resolution strategy. The urlconf argument is the URLconf module containing the URL patterns to use for reversing. By default, the root URLconf for the current thread is used. Note The string returned by reverse() is already urlquoted. For example: >>> reverse('cities', args=['Orléans']) '.../Orl%C3%A9ans/' Applying further encoding (such as urllib.parse.quote()) to the output of reverse() may produce undesirable results. reverse_lazy()¶ A lazily evaluated version of reverse(). reverse_lazy(viewname, urlconf=None, args=None, kwargs=None, current_app=None)¶ It is useful for when you need to use a URL reversal before your project’s URLConf is loaded. Some common cases where this function is necessary are: providing a reversed URL as the url attribute of a generic class-based view. providing a reversed URL to a decorator (such as the login_url argument for the django.contrib.auth.decorators.permission_required() decorator). providing a reversed URL as a default value for a parameter in a function’s signature. resolve()¶ The resolve() function can be used for resolving URL paths to the corresponding view functions. It has the following signature: resolve(path, urlconf=None)[source]¶ path is the URL path you want to resolve. As with reverse(), you don’t need to worry about the urlconf parameter. The function returns a ResolverMatch object that allows you to access various metadata about the resolved URL. If the URL does not resolve, the function raises a Resolver404 exception (a subclass of Http404) . class ResolverMatch[source]¶ func¶ The view function that would be used to serve the URL args¶ The arguments that would be passed to the view function, as parsed from the URL. kwargs¶ The keyword arguments that would be passed to the view function, as parsed from the URL. url_name¶ The name of the URL pattern that matches the URL. app_name¶ The application namespace for the URL pattern that matches the URL. app_names¶ The list of individual namespace components in the full application namespace for the URL pattern that matches the URL. For example, if the app_name is 'foo:bar', then app_names will be ['foo', 'bar']. namespace¶ The instance namespace for the URL pattern that matches the URL. namespaces¶ The list of individual namespace components in the full instance namespace for the URL pattern that matches the URL. i.e., if the namespace is foo:bar, then namespaces will be ['foo', 'bar']. view_name¶ The name of the view that matches the URL, including the namespace if there is one. A ResolverMatch object can then be interrogated to provide information about the URL pattern that matches a URL: # Resolve a URL match = resolve('/some/path/') # Print the URL pattern that matches the URL print(match.url_name) A ResolverMatch object can also be assigned to a triple: func, args, kwargs = resolve('/some/path/') One possible use of resolve() would be to test whether a view would raise a Http404 error before redirecting to it: from urllib.parse import urlparse from django.urls import resolve from django.http import HttpResponseRedirect, Http404 def myview(request): next = request.META.get('HTTP_REFERER', None) or '/' response = HttpResponseRedirect(next) # modify the request and response as required, e.g. change locale # and set corresponding locale cookie view, args, kwargs = resolve(urlparse(next)[2]) kwargs['request'] = request try: view(*args, **kwargs) except Http404: return HttpResponseRedirect('/') return response get_script_prefix()¶ get_script_prefix()[source]¶ Normally, you should always use reverse() to define URLs within your application. However, if your application constructs part of the URL hierarchy itself, you may occasionally need to generate URLs. In that case, you need to be able to find the base URL of the Django project within its Web server (normally, reverse() takes care of this for you). In that case, you can call get_script_prefix(), which will return the script prefix portion of the URL for your Django project. If your Django project is at the root of its web server, this is always "/".
Django Views(视图函数)

http请求中产生两个核心对象:
http请求:HttpRequest对象
http响应:HttpResponse对象
所在位置:django.http
参数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". !!注意一个常用方法:request.POST.getlist('') 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() 比如:http://127.0.0.1:8000/index33/?name=123 req.get_full_path()得到的结果就是/index33/?name=123 req.path:/index33
2 HttpResponse对象:
HttpRequest对象是由Django自动创建的, 但是HttpResponse对象必须自己创建. 每个view请求处理方法必须返回一个HttpResponse对象.
HttpResponse类在django.http.HttpResponse
页面渲染: render()(推荐) 和 render_to_response(), 页面跳转: redirect("路径") locals(): 可以直接将函数中所有的变量传给模板
补充:
---------------url.py-------------------- urlpatterns = [ path('home/', views.home), path('login/', views.login), ] ----------------views.py------------------- def login(req): if req.method=="POST": if 1: # return redirect("/home/") name="charon" return render(req,"my backend.html",locals()) return render(req,"login.html",locals()) def home(req): name="Charon" return render(req,"my backend.html",locals()) -----------------login.html------------------ <form action="/login/" method="post"> <p>姓名<input type="text" name="username"></p> <p>性别<input type="text" name="sex"></p> <p>邮箱<input type="text" name="email"></p> <p><input type="submit" value="submit"></p> </form> ------------------my backend.html----------------- <h1>用户{{ name }}你好</h1> #总结: render和redirect的区别: # 1. 如果render的页面需要模板语言渲染,需要的将数据库的数据加载到html,那么所有的这一部分除了写在home的视图函数中,必须还要写在login中,代码重复,没有解耦. # 2. 最重要的是 url 没有跳转到/home/,而是还在/login/, 所以当刷新后又得重新登录. # 3. 如果使用redirect页面会成功跳转然后停留在/home/并成功渲染.
URL传递参数到后台, 后台返回 **
一. 采用?a=1&b=2访问
修改视图views.py:
from django.shortcuts import render from django.http import HttpResponse def add(request): a = request.GET['a'] b = request.GET['b'] c = int(a)+int(b) return HttpResponse(str(c)) # 注: request.GET 类似于一个字典, 更好的办法是用 request.GET.get('a', 0) 当没有传递 a 的时候默认 a 为 0.
修改视图函数urls.py:
path('add/', views.add, name='add'),
浏览器访问:
http://127.0.0.1:8000/add/?a=4&b=5
二. 采用 /add/3/4/ 方式访问
修改views.py
def add2(request, a, b): c = int(a) + int(b) return HttpResponse(str(c))
修改urls.py
path('add2/(\d+)/(\d+)/', views.add2, name='add2')
浏览器访问:
http://127.0.0.1:8000/add/4/5/
模板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)来实现这种模式.
Template和Context对象
>>> python manange.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()方法渲染会更为高效: 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 # 第一种 # 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>') #t=get_template('current_datetime.html') c=Context({'current_date':now}) html=t.render(c) return HttpResponse(html) #第三种(推荐) def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
模板语法-------------------------------------------------
模板组成:HTML代码+逻辑控制代码
逻辑控制代码的组成
1. 变量 (使用两个大括号来引用变量)
语法格式: {{var_name}}
- 深度变量的查找 (句号)
模板系统能够非常简洁地处理更加复杂的数据结构, 例如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. 句点可用于访问字典的值: >>> 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 >>> d.month >>> d.day >>> 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. 句点可用于访问自定义的类: >>> from django.template import Template, Context >>> class Person(object): def __init__(self, first_name, last_name): self.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}}
一、形式:小写 {{ name | lower }} 二、串联:先转义文本到HTML,再转换每行到 <p> 标签 {{ my_text|escape|linebreaks }} 三、过滤器的参数 显示前30个字 {{ bio | truncatewords:"30" }} 格式化 {{ pub_date | date:"F j, Y" }} 过滤器列表 {{ 123|add:"5" }} 给value加上一个数值 {{ "AB'CD"|addslashes }} 单引号加上转义号,一般用于输出到javascript中 {{ "abcd"|capfirst }} 第一个字母大写 {{ "abcd"|center:"50" }} 输出指定长度的字符串,并把值对中 {{ "123spam456spam789"|cut:"spam" }} 查找删除指定字符串 {{ value|date:"F j, Y" }} 格式化日期 {{ value|default:"(N/A)" }} 值不存在,使用指定值 {{ value|default_if_none:"(N/A)" }} 值是None,使用指定值 {{ 列表变量|dictsort:"数字" }} 排序从小到大 {{ 列表变量|dictsortreversed:"数字" }} 排序从大到小 {% if 92|divisibleby:"2" %} 判断是否整除指定数字 {{ string|escape }} 转换为html实体 {{ 21984124|filesizeformat }} 以1024为基数,计算最大值,保留1位小数,增加可读性 {{ list|first }} 返回列表第一个元素 {{ "ik23hr&jqwh"|fix_ampersands }} &转为& {{ 13.414121241|floatformat }} 保留1位小数,可为负数,几种形式 {{ 13.414121241|floatformat:"2" }} 保留2位小数 {{ 23456 |get_digit:"1" }} 从个位数开始截取指定位置的1个数字 {{ list|join:", " }} 用指定分隔符连接列表 {{ list|length }} 返回列表个数 {% if 列表|length_is:"3" %} 列表个数是否指定数值 {{ "ABCD"|linebreaks }} 用新行用<p> 、 <br /> 标记包裹 {{ "ABCD"|linebreaksbr }} 用新行用<br /> 标记包裹 {{ 变量|linenumbers }} 为变量中每一行加上行号 {{ "abcd"|ljust:"50" }} 把字符串在指定宽度中对左,其它用空格填充 {{ "ABCD"|lower }} 小写 {% for i in "1abc1"|make_list %}ABCDE,{% endfor %} 把字符串或数字的字符个数作为一个列表 {{ "abcdefghijklmnopqrstuvwxyz"|phone2numeric }} 把字符转为可以对应的数字?? {{ 列表或数字|pluralize }} 单词的复数形式,如列表字符串个数大于1,返回s,否则返回空串 {{ 列表或数字|pluralize:"es" }} 指定es {{ 列表或数字|pluralize:"y,ies" }} 指定ies替换为y {{ object|pprint }} 显示一个对象的值 {{ 列表|random }} 返回列表的随机一项 {{ string|removetags:"br p div" }} 删除字符串中指定html标记 {{ string|rjust:"50" }} 把字符串在指定宽度中对右,其它用空格填充 {{ 列表|slice:":2" }} 切片 {{ string|slugify }} 字符串中留下减号和下划线,其它符号删除,空格用减号替换 {{ 3|stringformat:"02i" }} 字符串格式,使用Python的字符串格式语法 {{ "E<A>A</A>B<C>C</C>D"|striptags }} 剥去[X]HTML语法标记 {{ 时间变量|time:"P" }} 日期的时间部分格式 {{ datetime|timesince }} 给定日期到现在过去了多少时间 {{ datetime|timesince:"other_datetime" }} 两日期间过去了多少时间 {{ datetime|timeuntil }} 给定日期到现在过去了多少时间,与上面的区别在于2日期的前后位置。 {{ datetime|timeuntil:"other_datetime" }} 两日期间过去了多少时间 {{ "abdsadf"|title }} 首字母大写 {{ "A B C D E F"|truncatewords:"3" }} 截取指定个数的单词 {{ "<a>1<a>1<a>1</a></a></a>22<a>1</a>"|truncatewords_html:"2" }} 截取指定个数的html标记,并补完整 <ul>{{ list|unordered_list }}</ul> 多重嵌套列表展现为html的无序列表 {{ string|upper }} 全部大写 <a href="{{ link|urlencode }}">linkage</a> url编码 {{ string|urlize }} 将URLs由纯文本变为可点击的链接。(没有实验成功) {{ string|urlizetrunc:"30" }} 同上,多个截取字符数。(同样没有实验成功) {{ "B C D E F"|wordcount }} 单词数 {{ "a b c d e f g h i j k"|wordwrap:"5" }} 每指定数量的字符就插入回车符 {{ boolean|yesno:"Yes,No,Perhaps" }} 对三种值的返回字符串,对应是 非空,空,None
重点 - 关闭Django模板的自动转义:
# value='<a href="#">跳转</a>' # 直接使用 {{ value }} 其值会按字符串显示 防止自动转义方法一: {% autoescape off %} {{ value }} {% endautoescape %} 防止自动转义方法二: {{ value|safe }}
2. 标签(tag)的使用 (使用大括号和百分比的组合来表示使用tag)
语法格式: {% tags %}
- {% 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 %}和{% 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 5. forloop.first当第一次循环时值为True, 在特别情况下很有用: {% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% 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 - Cross Site Request Forgery, 跨站域请求伪造. csrf_token标签用于生成csrf_token的标签, 是Django为了在用户提交表单时防止跨站攻击所做的保护. 注意如果在view的index里用的是render_to_response方法, 不会生效.
<form action="{% url "bieming"%}" method="post"> <input type="text"> <input type="submit"value="提交"> {%csrf_token%} #这里会生成一个input标签, 和其他表单标签一起提交给后台. </form>
# 在后端一定要使用render()的方法返回数据 return render(request, 'xxx.html')
- {% url %}: 引用路由配置的地址 (别名)
<form action="{% url "bieming"%}" method="post"> <input type="text"> <input type="submit"value="提交"> </form>
- {% with %}: 用更简单的变量名替代复杂的变量名
# fhjsaldfhjsdfhlasdfhljsdal为传递进来的名字 {% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
- {% verbatim %}: 禁止render
{% verbatim %}
{{ hello }}
{% endverbatim %}
# 会只显示{{ hello }}
- {% load %}: 加载标签库
3. 自定义filter和simple_tag
步骤:
1. 在app的根目录下创建templatetags模块(必须)
2. 创建任意 .py 文件,如: my_tag_filter.py

3. 在my_tag_filter.py中, 先导入头文件, 编写过滤器函数, 注册过滤器.
from django.template import Library from django.utils.safestring import mark_safe # 将注册类实例化为register对象 register = Library() # register的名字是固定的,不可改变 # 使用装饰器注册
# filter最多只能传两个参数!! @register.filter def filter_multi(v1,v2): return v1 * v2
# sample_tag可以传多个参数, 但是不能使用if for语句!! @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)
4. 将自定义simple_tag和filter加载到html, 并使用.
文件中导入之前创建的 my_tag_filter.py:{% load my_tag_filter %}
{# 加载过滤器所在的文件,由于templatetags的文件名是固定的,Django可以直接找到过滤器文件所在的位置 #}
{% load my_tag_filter %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>过滤器</title>
</head>
<body>
# num=12
{{ num|filter_multi:2 }} #24
{% if num|filter_multi:25 > 100 %}
<p>大于100</p>
{% elif num|filter_multi:25 < 100 %}
<p>小于100</p>
{% else %}
<p>等于100</p>
{% endif %}
{% simple_tag_multi 2 5 %} #10
{% simple_tag_multi num 5 %} #60
</body>
</html>
5. 在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
注意:
filter可以用在if等语句后,simple_tag不可以
4. extend模板继承
- include 模板标签
内建模板标签: {% include %} . 该标签允许在模板中包含其它的模板的内容. 标签的参数是所要包含的模板名称, 可以是一个变量, 也可以是用单/双引号硬编码的字符串. 每当在多个模板中出现相同的代码时, 就应该考虑是否要使用 {% include %} 来减少重复.
# 使用模板加载API机制之后,可用的包含其它模板标签 {% include 'nav.html' %} {% include "nav.html" %} # 可带路径,相对路径,以 TEMPLATE_DIRS 的模板目录 为基准 {% include 'includes/nav.html' %} # 可使用变量名 {% include template_name %} 和在 get_template() 中一样, 对模板的文件名进行判断时会在所调取的模板名称之前加上来自 TEMPLATE_DIRS 所包含的模板执行时的 context 和包含它们的模板是一样的. 考虑下面两个模板文件: # mypage.html <html> <body> {% include "includes/nav.html" %} <h1>{{ title }}</h1> </body> </html> # includes/nav.html <div id="nav"> You are in: {{ current_section }} </div> 如果你用一个包含 current_section的上下文去渲染 mypage.html这个模板文件,这个变量将存在于它所包含(include)的模板里. 如果{% include %}标签指定的模板没找到,Django将会在下面两个处理方法中选择一个: 如果 DEBUG 设置为 True ,你将会在 Django 错误信息页面看到 TemplateDoesNotExist 异常。 如果 DEBUG 设置为 False ,该标签不会引发错误信息,在标签位置不显示任何东西。
- extend(继承)模板标签
在实际应用中, 会用 Django 模板系统来创建整个 HTML 页面. 这就带来一个常见的 Web 开发问题: 在整个网站中, 如何减少共用页面区域所引起的重复和冗余代码?
解决该问题的传统做法是使用服务器端的 includes 即使用{% include %}标签, 可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中. 例:
<!DOCTYPE> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html> # 为 hours_ahead 视图创建另一个模板: <!DOCTYPE> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html> # 两个模板有大量重复的 HTML 代码. # 解决这个问题的服务器端 include 方案是找出两个模板中的共同部分, 将其保存为不同的模板片段, 然后在每个模板中进行 include. # 把模板头部的一些代码保存为 header.html 文件: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> #把底部保存到文件 footer.html : <hr> <p>Thanks for visiting my site.</p> </body> </html> 对基于 include 的策略, 头部和底部的包含很简单, 麻烦的是中间部分. 在此范例中, 每个页面都有一个<h1>My helpful timestamp site</h1> 标题, 但是这个标题不能放在 header.html 中, 因为每个页面的 <title> 是不同的. 若是<h1>包含在头部, 就不得不包含 <title> , 但这样又不允许在每个页面对它进行定制.
更好的解决方法是用 Django 的模板继承, 可以对那些 不同 的代码段进行定义, 而不是 共同 代码段. 本质上来说, 模板继承就是先构造一个基础框架模板, 而后在其子模板中对它所包含站点公用部分和定义块进行重载.
例1:
第一步是定义基础模板,该框架之后将由子模板所继承 # 基础模板: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %} {% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html> 这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档, 将在本站点的所有页面中使用. 子模板的作用就是重载、添加或保留那些块的内容 # 使用模板标签: {% block %}. 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖. # current_datetime.html模板, 并使用基础模板: {% extends "base.html" %} {% block title %} The current time {% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %} # hours_ahead.html模板: {% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %} 每个模板只包含对自己而言 独一无二 的代码, 无需多余的部分. 如果想进行站点级的设计修改, 仅需修改 base.html, 所有其它模板会立即反映出所作修改.
例1模板继承的工作方式
在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由{% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。你可以根据需要使用任意多的继承次数
例2:
# --------------- base.html --------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .page-header{ height: 50px; background-color: lemonchiffon; } .page-body .menu{ height: 400px; background-color: salmon; float: left; width: 20%; } .page-body .content{ height: 400px; background-color: skyblue; float: left; width: 80%; } .page-footer{ height: 30px; background-color: aquamarine; clear: both; } </style> </head> <body> <div> <div class="page-header"></div> <div class="page-body"> <div class="menu"> <a href="/order/">订单</a><br> <a href="/sc/">购物车</a> </div> <div class="content"> {% block content %} hahahaha {% endblock %} </div> </div> <div class="page-footer"></div> </div> </body> </html> # --------------- order.html --------------------- {% extends "base.html" %} {% block content %} {{ block.super }} <div> 订单 </div> {% endblock %} # --------------- shopping_cart.html --------------------- {% extends "base.html" %} {% block content %} <div > 购物车 </div> {% endblock %} # --------------- views.py --------------------- def ordered(req): return render(req,'ordered.html') def shopping_cart(req): return render(req,'shopping_cart.html') # --------------- urls.py --------------------- urlpatterns = [ path('order/', views.ordered), path('sc/', views.shopping_cart), ]
使用继承的一种常见方式 - 三层法:
<1> 创建 base.html 模板, 在其中定义站点的主要外观感受. 这些都是不常修改甚至从不修改的部分. <2> 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html ).这些模板对base.html 进行拓展, 并包含区域特定的风格与设计. <3> 为每种类型的页面创建独立的模板, 例如论坛页面或者图片库. 这些模板拓展相应的区域模板.
# 这个方法可最大限度地重用代码, 并使向公共区域(如区域级的导航)添加内容变得方便
模板继承补充
<1>如果在模板中使用 {% extends %}, 必须保证其为模板中的第一个模板标记. 否则, 模板继承将不起作用.
<2>一般来说, 基础模板中的 {% block %} 标签越多越好. 记住, 子模板不必定义父模板中所有的代码块, 因此可以用合理的缺省值对一些代码块进行填充, 然后只对子模板所需的代码块进行定义(重载).
<3>如果发觉自己在多个模板之间拷贝代码, 应该考虑将该代码段放置到父模板的某个 {% block %} 中. 如果你需要访问父模板中的块的内容, 就使用 {{ block.super }}这个标签, 这个变量将会表现出父模板中的内容. 如果只想在上级代码块基础上添加内容, 而不是全部重载, 该变量就显得非常有用了.
<4>不允许在同一个模板中定义多个同名的 {% block %}. 存在这样的限制是因为block 标签的工作方式是双向的. 如果模板中出现了两个相同名称的 {% block %} 标签, 父模板将无从得知要使用哪个块的内容.
Models
ORM - Object Relational Mapping(对象关系映射)
用于实现面向对象编程语言里不同类型系统的数据之间的转换, 换言之, 就是用面向对象的方式去操作数据库的创建表以及增删改查等操作.
ORM分为两类 Code First 和 DB First.
Code First: 创建类 -> 自动生成表
DB First: 创建表 -> 自动生成类
Django中遵循 Code Frist 的原则, 即:根据代码中定义的类来自动生成数据库表.
类名对应 - 数据库中的表名
类属性对应 - 数据库里的字段
类实例对应 - 数据库表里的一行数据
优点:
1. 隐藏了数据访问细节, "封闭"的通用数据库交互 - ORM的核心. 这使得通用数据库交互变得简单易行, 而且完全不用考虑SQL语句 - 提高开发效率.
2. ORM使构造固化数据结构变得简单, ORM框架都提供了通过对象模型构造关系数据库结构的功能.
3. 可以避免一些写出的不好的sql语句带来的性能问题.
比如查询User表中的所有字段:
若是用select * from auth_user, 这样会因为多了一个匹配动作而影响效率的.
通过QuerySet的query属性查询对应操作的sql语句
user=models.User.objects.all()
print(user.query)
缺点:
1. 自动化意味着映射和关联管理, 代价是牺牲性能, ORM是多层系统且是面向对象, 对性能产生影响. 不过现在的各种ORM框架都在尝试各种方法, 比如缓存, 延迟加载登来减轻这个问题, 效果很显著.
2. 对于个别复杂查询, ORM仍然力不从心, 为了解决这个问题, ORM一般也支持写原始sql.
常见ORM框架
PHP:activerecord
Java:Hibernate
C#:Entity Framework
Django ORM语法
在model.py里创建表
字段
<1> models.CharField(max_length) #字符串字段, 用于较短的字符串. #必须有一个参数 maxlength, 用于限制该字段所允许的最大字符数. <2> models.IntegerField(Field) #用于保存一个整数. <3> models.FloatField(Field) # 一个浮点数. 必须提供两个参数: # 参数 # 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> models.AutoField(Field) # 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; # 自定义一个主键:my_id=models.AutoField(primary_key=True) # 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> models.BooleanField(Field) # A true/false field. admin 用 checkbox 来表示此类字段. <6> models.TextField(Field) # 一个容量很大的文本字段. admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> models.EmailField(Field) # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> models.DateField(Field) # 一个日期字段. 共有下列额外的可选参数: # 参数 # auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. # auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间. 仅仅在admin中有意义 <9> models.DateTimeField(Field) # 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> models.ImageField(Field) # 类似 FileField, 不过要校验上传对象是否是一个合法图片. # 它有两个可选参数:height_field和width_field, 图片将按提供的高度和宽度规格保存. <11> models.FileField(Field) # 一个文件上传字段. # 必须有参数: upload_to, 用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime 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> models.URLField(Field) # 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应). admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> models.NullBooleanField(Field) # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> models.SlugField(Field) # Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) # 通常用于URLs # 可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. <15> models.FilePathField(Field) # Django Admin以及ModelForm中提供读取文件夹下文件的功能 # 参数: # path, 文件夹路径 # match=None, 正则匹配 # recursive=False, 递归下面的文件夹 # allow_files=True, 允许文件 # allow_folders=False, 允许文件夹 <16> models.IPAddressField(Field) # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <17> models.CommaSeparatedIntegerField(Field) # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
1.触发Model中的验证和错误提示有两种方式: a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息 b. 使用ModelForm c. 调用Model对象的 clean_fields 方法,如: # models.py class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) email = models.EmailField(error_messages={'invalid': '格式错了.'}) # views.py def index(request): obj = models.UserInfo(username='11234', email='uu') try: print(obj.clean_fields()) except Exception as e: print(e) return HttpResponse('ok') # Model的clean方法是一个钩子,可用于定制操作,如:上述的异常处理。 2.Admin中修改错误提示 # admin.py from django.contrib import admin from model_club import models from django import forms class UserInfoForm(forms.ModelForm): age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'}) class Meta: model = models.UserInfo # fields = ('username',) fields = "__all__" exclude = ['title'] labels = { 'name':'Writer', } help_texts = {'name':'some useful help text.',} error_messages={ 'name':{'max_length':"this writer name is too long"} } widgets={'name':Textarea(attrs={'cols':80,'rows':20})} class UserInfoAdmin(admin.ModelAdmin): form = UserInfoForm admin.site.register(models.UserInfo, UserInfoAdmin)
字段的参数
1. 针对数据库的参数
null # 是否可以为空 default # 默认值 primary_key # 主键 db_column # 列名 db_index # 索引(db_index=True) unique # 唯一索引(unique=True) unique_for_date # 只对日期索引 unique_for_month # 只对月份索引 unique_for_year # 只对年做索引 auto_now # 创建时,自动生成时间 auto_now_add # 更新时,自动更新为当前时间
2. 针对 admin 页面生效的参数
choices # 作用:1、django admin中显示下拉框;2、避免连表查询 user_type_choices = ( # 数据库只存1、2、3,后面的信息存在内存里 (1, '超级用户'), (2, '普通用户'), (3, '普普通用户'), ) user_type_id = models.IntegerField(choices=user_type_choices,default=1) blank # django admin是否可以为空 verbose_name # django admin显示字段中文 editable # django admin是否可以被编辑 error_messages # 错误信息 # error_messages={"required":"密码不能为空",} # 注意必须有逗号 help_text # django admin提示 validators # django form ,自定义错误信息
3. 多表关系及参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要进行关联的表名 to_field=None, # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 - models.CASCADE,删除关联数据,与之关联也删除 - models.DO_NOTHING,删除关联数据,引发错误IntegrityError - models.PROTECT,删除关联数据,引发错误ProtectedError - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) - models.SET,删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to="User", to_field="id" on_delete=models.SET(func),) related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') db_constraint=True # 是否在数据库中创建外键约束 parent_link=False # 在Admin中是否显示关联数据 OneToOneField(ForeignKey) to, # 要进行关联的表名 to_field=None # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 ###### 对于一对一 ###### # 1. 一对一其实就是 一对多 + 唯一索引 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 # 如下会在A表中额外增加一个c_ptr_id列且唯一: class C(models.Model): nid = models.AutoField(primary_key=True) part = models.CharField(max_length=12) class A(C): id = models.AutoField(primary_key=True) code = models.CharField(max_length=1) ManyToManyField(RelatedField) to, # 要进行关联的表名 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 # 做如下操作时,不同的symmetrical会有不同的可选字段 models.BB.objects.filter(...) # 可选字段有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) # 可选字段有: bb, code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, # 自定义第三张表时,使用字段用于指定关系表 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64) db_constraint=True, # 是否在数据库中创建外键约束 db_table=None, # 默认创建第三张表时,数据库中表的名称
元信息 - class meta:
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural
以一个基本的 书籍/作者/出版商 数据库结构 为例:
作者(表)模型 - Author: 一个作者有姓名.
作者详细(表)模型 - AuthorDetail: 把作者的详情放到详情表, 包含性别, email地址和出生日期. 作者详情模型和作者模型之间是一对一的关系(one-to-one). 注: 其实并不需要分作者跟作者详细, 只是为了引出一对一关系.
出版商(表)模型 - Publisher: 出版商有名称, 地址, 所在城市, 省, 国家和网站.
书籍(表)模型 - Book: 书籍有书名和出版日期, 一本书可能会有多个作者, 一个作者也可以写多本书, 所以作者和书籍的关系就是多对多的关联关系(many-to-many). 一本书只应该由一个出版商出版, 所以出版商和书籍是一对多关联关系(one-to-many), 也被称作外键.
from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30, verbose_name="名称") address = models.CharField("地址", max_length=50) city = models.CharField('城市', max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() class Meta: verbose_name = '出版商' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=30) def __str__(self): return self.name class AuthorDetail(models.Model): sex = models.BooleanField(max_length=1, choices=((0, '男'), (1, '女'),)) email = models.EmailField() address = models.CharField(max_length=50) birthday = models.DateField() author = models.OneToOneField(Author,on_delete=models.CASCADE) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE) publication_date = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2, default=10) def __str__(self): return self.title
注意1: 记得在settings里的INSTALLED_APPS中加入'application1',然后再同步数据库.
注意2: Django2.0 关联表必填on_delete参数
# 一对多(ForeignKey) class ForeignKey(ForeignObject): def __init__(self, to, on_delete, related_name=None, related_query_name=None, limit_choices_to=None, parent_link=False, to_field=None, db_constraint=True, **kwargs): super().__init__(to, on_delete, from_fields=['self'], to_fields=[to_field], **kwargs) # 一对一(OneToOneField) class OneToOneField(ForeignKey): def __init__(self, to, on_delete, to_field=None, **kwargs): kwargs['unique'] = True super().__init__(to, on_delete, to_field=to_field, **kwargs) # 外键(ForeignKey)和一对一(OneToOneField)的参数中都有on_delete参数,而 django 升级到2.0之后,表与表之间关联的时候,必须要写on_delete参数,否则会报异常: TypeError: __init__() missing 1 required positional argument: 'on_delete' # on_delete参数的各个值的含义: on_delete=None, # 删除关联表中的数据时,当前表与其关联的field的行为 on_delete=models.CASCADE, # 删除关联数据,与之关联也删除 on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做 on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError # models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True) on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理) # models.ForeignKey('关联表', on_delete=models.SET_DEFAULT, default='默认值') on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理) on_delete=models.SET, # 删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) # 多对多(ManyToManyField) class ManyToManyField(RelatedField): def __init__(self, to, related_name=None, related_query_name=None, limit_choices_to=None, symmetrical=None, through=None, through_fields=None, db_constraint=True, db_table=None, swappable=True, **kwargs): super().__init__(**kwargs) 因为多对多(ManyToManyField)没有 on_delete 参数.
- 一对多:models.ForeignKey(其他表)
- 多对多:models.ManyToManyField(其他表)
- 一对一:models.OneToOneField(其他表)
应用场景:
- 一对多: 当一张表中创建一行数据时, 有一个单选的下拉框(可以被重复选择)
例如:创建用户信息时候, 需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】等- 多对多: 在某表中创建一行数据是, 有一个可以多选的下拉框
例如:创建用户信息, 需要为用户指定多个爱好- 一对一: 在某表中创建一行数据时, 有一个单选的下拉框(下拉框中的内容被用过一次就消失了)
例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据
创建完表后
生成同步数据库的脚本: python manage.py makemigrations
同步数据库: python manage.py migrate
本地跟数据库同步: python manage.py migrate --fake
ORM单表操作
数据增加
# -------------- url.py ------------------- from application1 import views urlpatterns = [ path('orm/', views.orm), ] # -------------- views.py ------------------- from django.shortcuts import render, HttpResponse from application1 import models def orm(req): # 方式一 models.Author.objects.create(name='Charon') # 方式二 models.Author.objects.create(**{"name": "Alan"}) # 或 dic = {'name':'alan'} models.Author.objects.create(**dic) # 方式三 author = models.Author(name='Mama') author.save() # 方式四 author = models.Author() author.name = 'Baba' author.save()
数据查询
# <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象, 返回query_set对象集合 # <2>all(): 查询所有结果, 返回query_set对象集合 # <3>get(**kwargs): 返回与所给筛选条件相匹配的对象, 返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误. 例子: res = models.Author.objects.all() res1 = models.Author.objects.get(name='Charon') res2 = models.Author.objects.filter() #-----------下面的方法都是对查询的结果再进行处理:比如 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。
QuerySet与惰性机制
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象), 它并不会马上执行sql, 而是当调用QuerySet的时候才执行.
QuerySet特点:
<1> 可迭代的
<2> 可切片
<3>惰性计算和缓存机制
objs=models.Book.objects.all() #[obj1,obj2,ob3...] # QuerySet: 可迭代 for obj in books: #每一obj就是一个行对象 print(obj.title) # QuerySet: 可切片 books=models.Book.objects.all()[:10] #切片 应用分页 books = models.Book.objects.all()[::2] book= models.Book.objects.all()[6] #索引 # 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,可能会造成额外的数据库查询。
数据查询 - 条件查询(利用双下划线将字段和对应的操作连接起来)
#条件选取querySet的时候, filter表示=, exclude表示!=. querySet.distinct() 去重复 __exact 精确等于 like 'aaa' __iexact 精确等于 忽略大小写 ilike 'aaa' __contains 包含 like '%aaa%' __icontains 包含 忽略大小写 ilike '%aaa%',但是对于sqlite来说,contains的作用效果等同于icontains。 __gt 大于 __gte 大于等于 __lt 小于 __lte 小于等于 __in 存在于一个list范围内 __startswith 以...开头 __istartswith 以...开头 忽略大小写 __endswith 以...结尾 __iendswith 以...结尾,忽略大小写 __range 在...范围内 __year 日期字段的年份 __month 日期字段的月份 __day 日期字段的日 __isnull=True/False
例子:
获取个数 # models.Tb1.objects.filter(name='charon').count() 大于, 小于 # models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 # models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 # models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 in # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in isnull # Entry.objects.filter(pub_date__isnull=True) contains # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # models.Tb1.objects.exclude(name__icontains="ven") range # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and 其他类似 # startswith,istartswith, endswith, iendswith, order by # models.Tb1.objects.filter(name='seven').order_by('id') # asc # models.Tb1.objects.filter(name='seven').order_by('-id') # desc group by # from django.db.models import Count, Min, Max, Sum # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" limit 、offset # models.Tb1.objects.all()[10:20] regex正则匹配, iregex 不区分大小写 # Entry.objects.get(title__regex=r'^(An?|The) +') # Entry.objects.get(title__iregex=r'^(an?|the) +') date # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) year # Entry.objects.filter(pub_date__year=2005) # Entry.objects.filter(pub_date__year__gte=2005) month # Entry.objects.filter(pub_date__month=12) # Entry.objects.filter(pub_date__month__gte=6) day # Entry.objects.filter(pub_date__day=3) # Entry.objects.filter(pub_date__day__gte=3) week_day # Entry.objects.filter(pub_date__week_day=2) # Entry.objects.filter(pub_date__week_day__gte=2) hour # Event.objects.filter(timestamp__hour=23) # Event.objects.filter(time__hour=5) # Event.objects.filter(timestamp__hour__gte=12) minute # Event.objects.filter(timestamp__minute=29) # Event.objects.filter(time__minute=46) # Event.objects.filter(timestamp__minute__gte=29) second # Event.objects.filter(timestamp__second=31) # Event.objects.filter(time__second=2) # Event.objects.filter(timestamp__second__gte=31) extra # extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) # Entry.objects.extra(where=['headline=%s'], params=['Lennon']) # Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) # Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
数据查询 - 聚合查询, 分组查询
<1> aggregate(*args,**kwargs): 通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。 from django.db.models import Avg,Min,Sum,Max 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有 图书的集合。 >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的 标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定 一个名称,可以向聚合子句提供它: >>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')} <2> annotate(*args,**kwargs): 可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合. # 查询charon出的书的总价格 Book.objects.filter(authors__name='charon').values('title') Book.objects.filter(authors__name='charon').aggregate(Sum('price')) # 查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors__name Book.objects.filter('authors__name').annotate(Sum('price')) # 查询各个出版社最便宜的书价是多少 Book.objects.filter('publisher__name').annotate(Min('price')) #
数据查询 - 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') # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 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))) # 6. 另一种方式 # con = Q() # q1 = Q() # q1.connector = 'OR' # q1.children.append(('id', 1)) # q1.children.append(('id', 10)) # q1.children.append(('id', 9)) # q2 = Q() # q2.connector = 'OR' # q2.children.append(('c1', 1)) # q2.children.append(('c1', 10)) # q2.children.append(('c1', 9)) # con.add(q1, 'AND') # con.add(q2, 'AND') # # models.Tb1.objects.filter(con)
数据查询 - 补充
################################################################## # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # ################################################################## def all(self) # 获取所有的数据对象 def filter(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def exclude(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def select_related(self, *fields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 model.tb.objects.all().select_related() model.tb.objects.all().select_related('外键字段') model.tb.objects.all().select_related('外键字段__外键字段') def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 # 获取所有用户表 # 获取用户类型表where id in (用户表中的查到的所有用户ID) models.UserInfo.objects.prefetch_related('外键字段') from django.db.models import Count, Case, When, IntegerField Article.objects.annotate( numviews=Count(Case( When(readership__what_time__lt=treshold, then=1), output_field=CharField(), )) ) students = Student.objects.all().annotate(num_excused_absences=models.Sum( models.Case( models.When(absence__type='Excused', then=1), default=0, output_field=models.IntegerField() ))) def annotate(self, *args, **kwargs) # 用于实现聚合group by查询 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 def distinct(self, *field_names) # 用于distinct去重 models.UserInfo.objects.values('nid').distinct() # select distinct nid from userinfo 注:只有在PostgreSQL中才能使用distinct进行去重 def order_by(self, *field_names) # 用于排序 models.UserInfo.objects.all().order_by('-id','age') def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射,如:子查询 Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) def reverse(self): # 倒序 models.UserInfo.objects.all().order_by('-nid').reverse() # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序 def defer(self, *fields): models.UserInfo.objects.defer('username','id') 或 models.UserInfo.objects.filter(...).defer('username','id') #映射中排除某列数据 def only(self, *fields): #仅取某个表中的数据 models.UserInfo.objects.only('username','id') 或 models.UserInfo.objects.filter(...).only('username','id') def using(self, alias): 指定使用的数据库,参数为别名(setting中的设置) ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw('select id as nid from 其他表') # 为原生SQL设置参数 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 将获取的到列名转换为指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定数据库 models.UserInfo.objects.raw('select * from userinfo', using="default") ################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) # 执行原生SQL # # from django.db import connection, connections # cursor = connection.cursor() # cursor = connections['default'].cursor() # cursor.execute("""SELECT * from auth_user where id = %s""", [1]) # row = cursor.fetchone() def values(self, *fields): # 获取每行数据为字典格式 def values_list(self, *fields, **kwargs): # 获取每行数据为元祖 def dates(self, field_name, kind, order='ASC'): # 根据时间进行某一部分进行去重查找并截取指定内容 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) # order只能是:"ASC" "DESC" # 并获取转换后的时间 - year : 年-01-01 - month: 年-月-01 - day : 年-月-日 models.DatePlus.objects.dates('ctime','day','DESC') def datetimes(self, field_name, kind, order='ASC', tzinfo=None): # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间 # kind只能是 "year", "month", "day", "hour", "minute", "second" # order只能是:"ASC" "DESC" # tzinfo时区对象 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC) models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai')) """ pip3 install pytz import pytz pytz.all_timezones pytz.timezone(‘Asia/Shanghai’) """ def none(self): # 空QuerySet对象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### def aggregate(self, *args, **kwargs): # 聚合函数,获取字典类型聚合结果 from django.db.models import Count, Avg, Max, Min, Sum result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) ===> {'k': 3, 'n': 4} def count(self): # 获取个数 def get(self, *args, **kwargs): # 获取单个对象 def create(self, **kwargs): # 创建对象 def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的个数 objs = [ models.DDD(name='r11'), models.DDD(name='r22') ] models.DDD.objects.bulk_create(objs, 10) def get_or_create(self, defaults=None, **kwargs): # 如果存在,则获取,否则,创建 # defaults 指定创建时,其他字段的值 obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2}) def update_or_create(self, defaults=None, **kwargs): # 如果存在,则更新,否则,创建 # defaults 指定创建时或更新时的其他字段 obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1}) def first(self): # 获取第一个 def last(self): # 获取最后一个 def in_bulk(self, id_list=None): # 根据主键ID进行查找 id_list = [11,21,31] models.DDD.objects.in_bulk(id_list) def delete(self): # 删除 def update(self, **kwargs): # 更新 def exists(self): # 是否有结果 其他操作
数据删除
# 删除数据 models.Author.objects.all().delete() # 删除所有 models.Author.objects.filter(name='Charon').delete() # 删除所有name='Charon'
数据更改
# 更新数据 author = models.Author.objects.get(id=3) author.name = 'lala' author.save models.Author.objects.all().update(name='charon') models.Author.objects.filter(id=3).update(name='alan') # 不能用models.Author.objects.get(id=3).update(name='alan') # update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合) #---------------- update方法直接设定对应属性---------------- models.Book.objects.filter(id=3).update(title="PHP") #UPDATE "app01_book" SET "title" = 'PHP' WHERE "app01_book"."id" = 3; args=('PHP', 3) #--------------- save方法会将所有属性重新设定一遍,效率低----------- obj=models.Book.objects.filter(id=3)[0] obj.title="Python" obj.save() # SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."color", "app01_book"."page_num", "app01_book"."publisher_id" FROM "app01_book" WHERE "app01_book"."id" = 3 LIMIT 1; # UPDATE "app01_book" SET "title" = 'Python', "price" = 3333, "color" = 'red', "page_num" = 556, "publisher_id" = 1 WHERE "app01_book"."id" = 3; save()方法, 这个方法更新一行里的所有列 结果集(QuerySet)对象的update()方法, 这个方法更改某一指定的列
查看原始sql语句的两种方法
# 1. 通过QuerySet的query属性查询对应操作的sql语句 author_obj=models.Author.objects.filter(id=2) print(author_obj.query) # 2. 使用update返回的是一个整形, 所以没法用query属性;对于每次创建一个对象, 想显示对应的原始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', }, } }
select_related 和 prefetch_related 函数对 QuerySet 查询的优化:
select_related: 用于优化原始sql语句的查询,一次性将查询的表和ForiegnKey关联的表一次性加载到内存
ret = models.UserInfo.objects.all() print(ret.query) ''' SELECT "app01_userinfo"."id", "app01_userinfo"."username", "app01_userinfo"."age", "app01_userinfo"."user_type_id" FROM "app01_userinfo" ''' ret1 = models.UserInfo.objects.all().select_related() print(ret1.query) ''' SELECT "app01_userinfo"."id", "app01_userinfo"."username", "app01_userinfo"."age", "app01_userinfo"."user_type_id", "app01_usertype"."id", "app01_usertype"."caption" FROM "app01_userinfo" INNER JOIN "app01_usertype" ON ("app01_userinfo"."user_type_id" = "app01_usertype"."id") '''
其他
import pymysql from django.db import connection, connections connection.connect() conn = connection.connection cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("""SELECT * from app01_userinfo""") row = cursor.fetchone() connection.close()
# 数字自增 from django.db.models import F models.UserInfo.objects.update(num=F('num') + 1) # 字符串更新 from django.db.models.functions import Concat from django.db.models import Value models.UserInfo.objects.update(name=Concat('name', 'pwd')) models.UserInfo.objects.update(name=Concat('name', Value('666')))
# ########### 基础函数 ########### # 1. Concat,用于做类型转换 # v = models.UserInfo.objects.annotate(c=Cast('pwd', FloatField())) # 2. Coalesce,从前向后,查询第一个不为空的值 # v = models.UserInfo.objects.annotate(c=Coalesce('name', 'pwd')) # v = models.UserInfo.objects.annotate(c=Coalesce(Value('666'),'name', 'pwd')) # 3. Concat,拼接 # models.UserInfo.objects.update(name=Concat('name', 'pwd')) # models.UserInfo.objects.update(name=Concat('name', Value('666'))) # models.UserInfo.objects.update(name=Concat('name', Value('666'),Value('999'))) # 4.ConcatPair,拼接(仅两个参数) # v = models.UserInfo.objects.annotate(c=ConcatPair('name', 'pwd')) # v = models.UserInfo.objects.annotate(c=ConcatPair('name', Value('666'))) # 5.Greatest,获取比较大的值;least 获取比较小的值; # v = models.UserInfo.objects.annotate(c=Greatest('id', 'pwd',output_field=FloatField())) # 6.Length,获取长度 # v = models.UserInfo.objects.annotate(c=Length('name')) # 7. Lower,Upper,变大小写 # v = models.UserInfo.objects.annotate(c=Lower('name')) # v = models.UserInfo.objects.annotate(c=Upper('name')) # 8. Now,获取当前时间 # v = models.UserInfo.objects.annotate(c=Now()) # 9. substr,子序列 # v = models.UserInfo.objects.annotate(c=Substr('name',1,2)) # ########### 时间类函数 ########### # 1. 时间截取,不保留其他:Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,ExtractSecond, ExtractWeekDay, ExtractYear, # v = models.UserInfo.objects.annotate(c=functions.ExtractYear('ctime')) # v = models.UserInfo.objects.annotate(c=functions.ExtractMonth('ctime')) # v = models.UserInfo.objects.annotate(c=functions.ExtractDay('ctime')) # # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'year')) # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'month')) # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'year_month')) """ MICROSECOND SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR SECOND_MICROSECOND MINUTE_MICROSECOND MINUTE_SECOND HOUR_MICROSECOND HOUR_SECOND HOUR_MINUTE DAY_MICROSECOND DAY_SECOND DAY_MINUTE DAY_HOUR YEAR_MONTH """ # 2. 时间截图,保留其他:Trunc, TruncDate, TruncDay,TruncHour, TruncMinute, TruncMonth, TruncSecond, TruncYear # v = models.UserInfo.objects.annotate(c=functions.TruncHour('ctime')) # v = models.UserInfo.objects.annotate(c=functions.TruncDate('ctime')) # v = models.UserInfo.objects.annotate(c=functions.Trunc('ctime','year'))
from django.db.models.functions.base import Func class CustomeFunc(Func): function = 'DATE_FORMAT' template = '%(function)s(%(expressions)s,%(format)s)' def __init__(self, expression, **extra): expressions = [expression] super(CustomeFunc, self).__init__(*expressions, **extra) v = models.UserInfo.objects.annotate(c=CustomeFunc('ctime',format="'%%Y-%%m'"))
ORM连表操作
一、一对多
1、创建数据
通过对象创建
通过对象字段_id创建
2、查找
正向查找
在通过filter的时候跨表使用 双下划线 '__'
在获取值得时候通过.跨表
反向查找
Django自动生成 表名_set
其他操作和正向查找一样
二、多对多
1、自动生成关系表
间接的方式获取关系表,如果是正向的:一行数据的对象.ManyToMany字典就行 反向:一行数据的对象.表名_set
2、自定义关系表(推荐)不管是添加、修改只对关系表操作就行
一对多:
class UserType(models.Model): caption = models.CharField(max_length=32) class UserInfo(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() user_type = models.ForeignKey('UserType',on_delete=models.CASCADE)
添加数据
# 方式一 - 直接根据这个字段进行添加数据, 记得要给user_type 加 '_id' dic = {'username': 'mosson', 'age': 18, 'user_type_id': 1} models.UserInfo.objects.create(**dic) # 方式二 - 通过对象添加 usertype = models.UserType.objects.filter(id=2)[0] # 先获取组的对象 models.UserInfo.objects.create(username='seven', age=18, user_type=usertype) # 添加的时候直接添加对象就可以 django的get方法是从数据库的取得一个匹配的结果,返回一个对象,如果记录不存在的话,它会报错. django的filter方法是从数据库的取得匹配的结果,返回一个对象列表,如果记录不存在的话,它会返回[].
一对多查找
正向查:ForeignKey在UserInfo表里,如果根据UserInfo这张表去查询这两张关联的表的合起来的内容就是正向查
反向查:ForeignKey不在UserType里,如果根据UserType这张表去查询这两张关联的表的合起来的内容就是反向查
# 正向查 - 跨表查询使用“双下划线” + 属性: ret = models.UserInfo.objects.filter(user_type__caption='model') print(ret) #<QuerySet [<UserInfo: UserInfo object (4)>]> for item in ret: print(item, item.username, item.age, item.user_type.caption) #charon 3 model # 反向查 obj = models.UserType.objects.get(id=1) print(obj.caption) #得到在UserType表中id为1对应的caption print(obj.id) #得到在UserType表中id为1 print(obj.userinfo_set) # 理解为一种能力,可以获取所有当前这个用户类型的用户/或者这个用户类型的多个用户. 相当于models.UserInfo.objects.filter(user_type=obj) print(obj.userinfo_set.all()) # 获取所有用户类型为actor的用户(UserType表中id为1的是actor) print(obj.userinfo_set.filter(username='charon')) # 获取用户类型为actor的并且用户为charon的用户
例子
# 反向查询actor类型下的所有的用户名 # way1 ret = models.UserType.objects.filter(caption='actor').values('userinfo__username') for item in ret: print(item, type(item)) # way2 ret = models.UserType.objects.filter(caption='actor').first() for item in ret.userinfo_set.all(): print(item.username)
总结:
正向查找:
1. 通过value、value_list、fifter 方式正向跨表:外键名__关联表字段)
- ret = models.UserInfo.objects.filter(user_type__caption='model')
- ret = models.UserInfo.objects.values('username','age','user_type__caption')
- ret = models.UserInfo.objects.values_list('username','age','user_type__caption')
2. 通过对象的形式反向跨表:对象.外键名.关联表字段
- 先获取对象: ret = models.UserInfo.objects.all().first()
- ret.user_type.caption
反向查找:
1. 通过value、value_list、fifter 方式反向跨表:小写表名__关联表字段, 可以得到有关系的列
- ret = models.UserType.objects.filter(caption='actor').values('userinfo__username')
- ret = models.UserType.objects.values('caption', 'userinfo__age', 'userinfo__username')
2. 通过对象的形式反向跨表:小写表名_set().all()
- 先获取对象: ret = models.UserType.objects.filter(caption='actor').first()
- ret.userinfo_set.all()
多对多:
Django自动创建第三张表
class Host(models.Model): hostname = models.CharField(max_length=32) port = models.IntegerField() class HostAdmin(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32) host = models.ManyToManyField('Host') #数据库会自动创建第三张表hostadmin_host
添加数据
models.Host.objects.create(hostname='host1.test.com', port=80) models.Host.objects.create(hostname='host2.test.com', port=80) models.Host.objects.create(hostname='host3.test.com', port=80) models.Host.objects.create(hostname='host4.test.com', port=80) models.HostAdmin.objects.create(username='alan', email='alan@qq.com') models.HostAdmin.objects.create(username='mosson', email='mosson@qq.com') models.HostAdmin.objects.create(username='charon', email='charon@qq.com') models.HostAdmin.objects.create(username='njwqv', email='njwqv@qq.com')
对自动生成的第三张表进行操作
# 正向操作 # 正向向hostadmin_host添加数据 admin_obj = models.HostAdmin.objects.get(username='dali') # 找到用户dali这个对象 host_list = models.Host.objects.filter(id__lt=2) # 找到主机 admin_obj.host.add(*host_list) # 通过找到的dali的对象.add去添加数据 # admin_obj.host.add(*[2,3]) # admin_obj.host.add(2,3) # admin_obj.host.add(3) # 删除数据 admin_obj.host.remove(*host_list) # 更改数据 admin_obj.host.set(*host_list) # 反向操作 # 反向向hostadmin_host添加数据 host_obj = models.Host.objects.get(id=3) # 获取主机 admin_list = models.HostAdmin.objects.filter(id__gt=3) # 获取用户列表 host_obj.hostadmin_set.add(*admin_list) # host_obj.hostadmin_set.add(*[1,2]) # ......
查 - 基于表中的对象去找到第三张表(通过间接的方式找到这张表的句柄)
#正向查 admin_obj = models.HostAdmin.objects.get(id=1) admin_obj.host.all() #反相差 host_obj = models.Host.objects.get(id=1) host_obj.hostadmin_set.all()
总结:
正向操作
操作: .add(), .set(), .remove(), .clear(), all()
获取带有外键的对象, 对象.外键名.操作
反向操作
获取不带有外键的对象, 对象.小写关联表名_set.操作
自定义第三张表
class HostInfo(models.Model): hostname = models.CharField(max_length=32) port = models.IntegerField() class UserMap(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32) # through指定第三张表的表名 # through_fields指定第三张表的字段 host = models.ManyToManyField(HostInfo, through='HostRelation',through_fields=('host','user',)) class HostRelation(models.Model): host = models.ForeignKey('HostInfo', on_delete=models.CASCADE) user = models.ForeignKey('UserMap', on_delete=models.CASCADE) # 不用through class HostRelation(models.Model): host = models.ForeignKey('HostInfo', on_delete=models.CASCADE) user = models.ForeignKey('UserMap', on_delete=models.CASCADE) class Meta: unique_together = ( ('host','user'), )
添加数据
models.HostInfo.objects.create(hostname='vre.test.com', port=80) models.HostInfo.objects.create(hostname='bwn.test.com', port=80) models.HostInfo.objects.create(hostname='k86l.test.com', port=80) models.UserMap.objects.create(username='alan', email='alan@qq.com') models.UserMap.objects.create(username='charon', email='charon@qq.com') models.UserMap.objects.create(username='mosson', email='mosson@qq.com')
添加数据到自定义的第三张表HostRelation
models.HostRelation.objects.create( user=models.UserMap.objects.get(id=1), host=models.HostInfo.objects.get(id=1) )
查 - 直接到第三张表去查
relation_list = models.HostRelation.objects.all() for item in relation_list: # 每一个item就是一个关系 print(item.user.username) print(item.host.hostname) relation_list = models.HostRelation.objects.filter(user__username='charon') for item in relation_list: # 每一个item就是一个关系 print(item.user.username) print(item.host.hostname)
Django连接MySQL
Django中models的操作, 也是调用了ORM框架来实现pymysql 或者mysqldb(一般在python程序中ORM操作都是对mysqldb和pymysql这样的底层模块进行的封装处理), 所以可以使用原生的SQL语句来操作数据库.
数据库的配置
Django默认支持sqlite, mysql, oracle, postgresql数据库.
<1> sqlite
Django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
mysql驱动程序
- MySQLdb(mysql python)
- mysqlclient
- MySQL
- PyMySQL(纯python的mysql驱动程序)
在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分别是数据库的用户名和密码. 设置完后, 进行数据迁移.(python manage.py makemigration 和 python manage.py migrate) 启动项目, 若是报错:no module named MySQLdb 这是因为django默认你导入的驱动是MySQLdb, 可是MySQLdb对于py3有很大问题, 所以需要的驱动是PyMySQL 解决方法: 找到项目名文件下的__init__,在里面写入: import pymysql pymysql.install_as_MySQLdb()
Django debug toolbar使用
安装
1. 在命令行输入
pip3 install django-debug-toolbar
2. 在setting.py添加
INSTALLED_APPS = [ # ... 'django.contrib.staticfiles', # ... 'debug_toolbar', #添加调式工具app ] # 以及 MIDDLEWARE = [ # ... 'debug_toolbar.middleware.DebugToolbarMiddleware', #添加调式工具的中间件 # ... ] # 以及 INTERNAL_IPS = ("127.0.0.1",) #添加调试工具的IP
3. 在urls.py添加
from django.conf import settings from django.urls import path,include if settings.DEBUG: import debug_toolbar urlpatterns = [ path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
4. 在views.py一定要使用render, toolbar才会生效


浙公网安备 33010602011771号