22-Django基础(一)
框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演。
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) client.send("<h1 style=\'color:red\'>Hello, yuan</h1>".encode("utf8")) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((\'localhost\',8001)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == \'__main__\': main()
scoket 服务端
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
—————————–Do a web framework ourselves—————————
step 1:
from wsgiref.simple_server import make_server def application(environ, start_response): start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')]) return [b\'<h1>Hello, web!</h1>\'] httpd = make_server(\'\', 8080, application) print(\'Serving HTTP on port 8000...\') # 开始监听HTTP请求: httpd.serve_forever()
step 1
注意:
整个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。
step 2
print(environ[\'PATH_INFO\']) path=environ[\'PATH_INFO\'] start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')]) f1=open("index1.html","rb") data1=f1.read() f2=open("index2.html","rb") data2=f2.read() if path=="/yuan": return [data1] elif path=="/alex": return [data2] else: return ["<h1>404</h1>".encode(\'utf8\')]
step 2
step3
from wsgiref.simple_server import make_server def f1(): f1=open("index1.html","rb") data1=f1.read() return [data1] def f2(): f2=open("index2.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=="/yuan": return f1() elif path=="/alex": return f2() else: return ["<h1>404</h1>".encode("utf8")] httpd = make_server(\'\', 8502, application) print(\'Serving HTTP on port 8084...\') # 开始监听HTTP请求: httpd.serve_forever()
step 3
step4
from wsgiref.simple_server import make_server def f1(req): print(req) print(req["QUERY_STRING"]) f1=open("index1.html","rb") data1=f1.read() return [data1] def f2(req): f2=open("index2.html","rb") data2=f2.read() return [data2] import time def f3(req): #模版以及数据库 f3=open("index3.html","rb") data3=f3.read() times=time.strftime("%Y-%m-%d %X", time.localtime()) data3=str(data3,"utf8").replace("!time!",str(times)) return [data3.encode("utf8")] def routers(): urlpatterns = ( (\'/yuan\',f1), (\'/alex\',f2), ("/cur_time",f3) ) return urlpatterns def application(environ, start_response): print(environ[\'PATH_INFO\']) path=environ[\'PATH_INFO\'] start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')]) urlpatterns = routers() func = None for item in urlpatterns: if item[0] == path: func = item[1] break if func: return func(environ) else: return ["<h1>404</h1>".encode("utf8")] httpd = make_server(\'\', 8518, application) print(\'Serving HTTP on port 8084...\') # 开始监听HTTP请求: httpd.serve_forever()
step 4
伙计们,不知不觉我们自己已经写出一个web框架啦!
MVC和MTV模式
Django的MTV模式本质是各组件之间为了保持松耦合关系,Django的MTV分别代表:
Model(模型):负责业务对象与数据库的对象(ORM)
Template(模版):负责如何把页面展示给用户
View(视图):负责业务逻辑,并在适当的时候调用Model和Template
此外,Django还有一个url分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
学习web框架的方法:先学会用,再学怎么写
二、Django基本命令
0、安装django:
pip3 install django
1、创建一个django project
django-admin.py startproject mysite
当前目录下会生成mysite的工程,目录结构如下:
- manage.py —– Django项目里面的工具,通过它可以调用django shell和数据库等。
- settings.py —- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
- urls.py —– 负责把URL模式映射到应用程序。
2、在mysite目录下创建应用,比如blog:
python manage.py startapp blog
项目和应用的关系是:
一个项目可以包含多个应用
一个应用可以被多个项目包含
3、启动django项目
python manage.py runserver 8080
这样我们的django就启动起来了!当我们访问:http://127.0.0.1:8080/时就可以看到:
4、同步更改数据库表或字段
python manage.py syncdb
注意:Django 1.7.1 及以上的版本需要用以下命令
python manage.py makemigrations
python manage.py migrate
备注:数据库同步的时候要确保installed_app中有这个应用的名字
这种方法可以创建表,当你在models.py中新增了类时,运行它就可以自动在数据库中创建表了,不用手动创建。
记住一个原则,修改了数据库的字段 表结构等对数据存取方式有影响的才需要执行。其他不需要执行(比如添加__str__)
5、清空数据库
python manage.py flush
此命令会询问是 yes 还是 no, 选择 yes 会把数据全部清空掉,只留下空表。
如果要删除数据库,请物理删除文件和同步目录migrations下面的临时脚本
6、创建超级管理员
python manage.py createsuperuser # 按照提示输入用户名和对应的密码就好了邮箱可以留空,用户名和密码必填 # 修改 用户密码可以用: python manage.py changepassword username
7、Django 项目环境终端
python manage.py shell
这个命令和 直接运行 python 进入 shell 的区别是:你可以在这个 shell 里面调用当前项目的 models.py 中的 API,对于操作数据的测试非常方便。
8、Django 项目环境终端
ython manage.py dbshell
Django 会自动进入在settings.py中设置的数据库,如果是 MySQL 或 postgreSQL,会要求输入数据库用户密码。
在这个终端可以执行数据库的SQL语句。如果您对SQL比较熟悉,可能喜欢这种方式。
9、更多命令
python manage.py
查看所有的命令,忘记子名称的时候特别有用。
10 static配置
#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>
http://www.cnblogs.com/robinunix/p/7084537.html
static说明步骤版本:
引用静态文件的方法(多种实现方式,下面只是一种) 1,在引用book中创建static 2,增加绝对路径STATIC_ROOT的配置(STATIC_URL别名的配置在settings.py中默认存在) STATIC_URL = \'/static/\' 别名,在第四步引用,django默认已存在 STATIC_ROOT = os.path.join(BASE_DIR, \'book/static\') ##new 绝对路径 3,在html 文件的开头load staticfiles { % load staticfiles%} 4,在html文件的head头中添加link本地的css和js文件 比如: <link rel="stylesheet" href="{% static "dist/css/bookManageSystem_login.css" %}"> 其中的static是第二步中的别名代表的是STATIC_ROOT指向的绝对路径,因为实际的css和js文字在绝对路径中没有,所以后面的"dist/css/bookManageSystem_login.css"是文件实际路径的拼接字符串
线上 必须是STATIC_ROOT 被nginx指向,测试环境是用STATIC_DIR也可以,效果相同
=====
以后不要用STATIC_ROOT,而要使用STATICFILES_DIRS
STATIC_URL = \'/static/\'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, \'static\'),
)
=====
11,CSRF
csrf机制
解决方法:
1,注释掉settings.py中MIDDLEWARE下面的
#\'django.middleware.csrf.CsrfViewMiddleware\',
2,在form表单中添加{% csrf_token %}
csrf场景:
允许的情况:之前访问过一次,然后再提交post(第2次请求),
不允许的情况:如果是第一次就发送post则禁止
csrf原理:
第一次访问的时候给一个字符串(身份证) ,key=value的形式,第二次post来的时候,带着这个身份证
通过页面元素浏览可以看到在form表单中有 {% csrf_token %} 渲染出来的字符串 样式:type=hidden name=****是个key value是随机字符串
带着value去post的时候csrf中间件会对这个value来做验证是不是之前发送的
12,bootstrap说明
md 宽度
offset 偏移量
13,INSTALLED_APPS说明
settings配置文件中INSTALLED_APPS必须包含应用app的场景:
1,数据库migrate的时候,否则找不到app对应的modles
2,自定义filter
三 路由配置系统(URLconf)
URL配置(URLconf)就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。
urlpatterns = [
url(正则表达式, views视图函数,参数,别名),
]
参数说明:
一个正则表达式字符串
一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
可选的要传递给视图函数的默认参数(字典形式)
一个可选的name参数
3.1 URLconf的正则字符串参数
3.1.1 简单配置
from django.conf.urls import url from . import views #视图一般需要提前导入 urlpatterns = [ url(r\'^articles/2003/$\', views.special_case_2003), #视图函数 url(r\'^articles/([0-9]{4})/$\', views.year_archive),#括号括起来的年和月会自动传给对应的视图函数 url(r\'^articles/([0-9]{4})/([0-9]{2})/$\', views.month_archive), url(r\'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$\', views.article_detail), ]
NOTE: 1 一旦匹配成功则不再继续 2 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(对url正则表达式进行分组后,分组的结果会自动传给视图函数)。 3 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。 4 每个正则表达式前面的\'r\' 是可选的但是建议加上。 一些请求的例子: /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。 /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配,一旦匹配成功则不再继续。 /articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数 views.month_archive(request, \'2005\', \'03\')。括号括起来的年和月会自动传给对应的视图函数
SLASH(斜线)
#设置是否开启URL访问地址后面不为/,跳转至带有/的路径 APPEND_SLASH=True
无名分组正则表达式补充,在URLconf中无名正则表达式分组会按位置参数自动传值给视图函数
>>> import re >>> re.findall("\d+","173sdsakj123agsd1236") [\'173\', \'123\', \'1236\'] >>> re.findall("a\d+b","173sdsakj123agsd1236") [] >>> re.findall("a\d+b","173sdsakja123bagsd1236") [\'a123b\'] >>> re.findall("a(\d+)b","173sdsakja123bagsd1236") [\'123\'] >>>
3.1.2 有名分组(named group)
上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获URL 中的值并以位置参数传递给视图。在更高级的用法中,可以使用命名的正则表达式组来捕获URL 中的值并以关键字参数传递给视图。
在Python 正则表达式中,命名正则表达式组的语法是(?P<name>pattern)
,其中name
是组的名称,pattern
是要匹配的模式。
下面是以上URLconf 使用命名组的重写:
from django.conf.urls import url from . import views urlpatterns = [ url(r\'^articles/2003/$\', views.special_case_2003), url(r\'^articles/(?P<year>[0-9]{4})/$\', views.year_archive), url(r\'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$\', views.month_archive), url(r\'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$\', views.article_detail), #只有红色的部分是正则表达式的部分 ]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。例如:
/articles/2005/03/
请求将调用views.month_archive(request, year=\'2005\', month=\'03\')函数
/articles/2003/03/03/
请求将调用函数views.article_detail(request, year=\'2003\', month=\'03\', day=\'03\')。
在实际应用中,这意味你的URLconf 会更加明晰且不容易产生参数顺序问题的错误 —— 你可以在你的视图函数定义中重新安排参数的顺序。当然,这些好处是以简洁为代价;有些开发人员认为命名组语法丑陋而繁琐。而且必须保证,正则表达式的分组名称和函数的形式参数名称必须一致!!!!!(因为按关键字传参数的时候,命名的参数和函数的形参要一致。)
命名分组正则表达式补充,在URLconf中有名正则表达式分组会按关键字参数自动传值给视图函数
>>> import re >>> ret=re.search("d(?P<id>\d+)","32asd34szDxa34") >>> ret <_sre.SRE_Match object at 0x1062cbbe8> >>> ret.group() #search 返回的是对象,得通过group来取其中的值 \'d34\' >>> ret.group("id") \'34\' >>>
3.1.3 URLconf 在什么上查找
URLconf 在请求的URL上查找,将它当做一个普通的Python 字符串。不包括GET和POST参数以及域名。
例如,http://www.example.com/myapp/ 请求中,URLconf 将查找myapp/
。
在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/
。
URLconf 不检查请求的方法。换句话讲,所有的请求方法 —— 同一个URL的POST
、GET
、HEAD
等等 —— 都将路由到相同的函数(对方法的判断放到视图函数中去处理)。
3.1.4 捕获的参数永远是字符串
每个捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:
url(r\'^articles/(?P<year>[0-9]{4})/$\', views.year_archive), #括号括起来的参数会自动传给视图函数
views.year_archive()
的year
参数将是一个字符串
3.1.5 指定视图参数的默认值
有一个方便的小技巧是指定视图参数的默认值。 下面是一个URLconf 和视图的示例:
# URLconf from django.conf.urls import url from . import views urlpatterns = [ url(r\'^blog/$\', views.page), url(r\'^blog/page(?P<num>[0-9]+)/$\', views.page), ] # View (in blog/views.py) def page(request, num="1"): ...
在上面的例子中,两个URL模式指向同一个视图views.page
。但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,page()
函数将使用num
参数的默认值”1″。如果第二个模式匹配,page()
将使用正则表达式捕获的num
值。
3.1.6 Including other URLconfs
#At any point, your urlpatterns can “include” other URLconf modules. This #essentially “roots” a set of URLs below other ones. #For example, here’s an excerpt of the URLconf for the Django website itself. #It includes a number of other URLconfs: from django.conf.urls import include, url urlpatterns = [ url(r\'^admin/\', admin.site.urls), url(r\'^blog/\', include(\'blog.urls\')), ]
3.2 传递额外的选项给视图函数(了解)
URLconfs 具有一个钩子,让你传递一个Python字典作为额外的参数传递给视图函数。
django.conf.urls.url()
函数可以接收一个可选的第三个参数,它是一个字典,表示想要传递给视图函数的额外关键字参数。
例如:
from django.conf.urls import url from . import views urlpatterns = [ url(r\'^blog/(?P<year>[0-9]{4})/$\', views.year_archive, {\'foo\': \'bar\'}), ]
在这个例子中,对于/blog/2005/
请求,Django 将调用views.year_archive(request, year=\'2005\', foo=\'bar\')
。
这个技术在Syndication 框架中使用,来传递元数据和选项给视图。
3.3 name参数
urlpatterns = [ url(r\'^index\',views.index,name=\'INDEX\'), ] ################### def index(req): if req.method==\'POST\': #req.POST和req.GET中,POST和GET都要大写
#此处如果要打印,应该打印request.POST,打印request是看不到post的数据的 username=req.POST.get(\'username\') password=req.POST.get(\'password\') if username==\'alex\' and password==\'123\': return HttpResponse("登陆成功") return render(req,\'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 \'INDEX\' %}" method="post"> #这里可以使用URLconf中定义的name参数来引用对应的url,优点:url改变的时候不需要改其他地方的代码
# 视图中的render方法对模板语言了url标签,进行了一次替换,\'INDEX\' 加引号 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> <input type="submit" value="submit"> </form> </body> </html> #######################
四 编写视图
一个视图函数,或者简短来说叫做视图,是一个简单的Python函数,它接受web请求,并且返回web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你的Python目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了能够把代码放在某个地方,惯例是把视图放在叫做views.py的文件中,然后把它放到你的项目或者应用目录里。
4.1 一个简单的视图
下面是一个返回当前日期和时间作为HTML文档的视图:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
让我们逐行阅读上面的代码:
- 首先,我们从 django.http模块导入了HttpResponse类,以及Python的datetime库。
- 接着,我们定义了current_datetime函数。它是一个视图函数。每个视图函数都应接收HttpRequest对象作为第一个参数,一般叫做request。
- 注意视图函数的名称并不重要;不需要用一个统一的命名方式来命名,以便让Django识别它。我们将其命名为current_datetime,是因为这个名称能够精确地反映出它的功能。
- 这个视图会返回一个HttpResponse对象,其中包含生成的响应。每个视图函数都要返回HttpResponse对象(render中其实也是疯子了HttpResponse对象)
\'\'\'
http请求-响应过程中有两个核心对象:
http请求对象:HttpRequest (request 在视图函数的定义和render的时候必不可少,且必须是第一个参数)
http响应响应:HttpResponse
所在位置:django.http
\'\'\'
4.1.1 前端和后端进行数据交互的3种方式
1,a标签
2,form表单
3,ajax
get有两种方式向server端发送数据
方式一:在a标签的中使用,示例如下:
<a href="/delete/?name={{ user.username }}&password={{ user.pwd}}"><button type="button" class="btn btn-danger">删除</button></a>
效果,点击button的时候给后端发送了一个get请求,从request.GET中可以拿到name和password
print(request.GET.get(\'id\'))
方式二:将url中的信息通过命名分组传递给视图函数
GET方式访问,http://www.example.com/blog/2017/07/13, 在URLconf中通过命名分组的方式获得2017和07给视图函数使用
4.1.2 在视图函数中使用类
def register(req): class Person(object): def __init__(self,name): self.name = name p1 = Person("alex") p2 = Person("egon") p3 = Person("傻子") person_list = [p1, p2, p3] # 类似于queryset集合对象 return render(req, "register.html", locals()) ##传给前端渲染的对象也可以是类的实例化对象
register.html的内容如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% for person in person_list %} {{ person.name }} #在前端访问对象的属性 {% endfor %} </body> </html>
4.2 快捷函数
4.2.1 render函数
render(request, template_name[, context]) 结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象(render封装了HttpRespone对象)。 参数: request: 用于生成响应的请求对象(不可少且必须是第一个参数)。 template_name:要使用的模板的完整名称,可选的参数 context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它,可以使用locals()-->表示的是当前的所有局部变量。 content_type:生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。 status:响应的状态码。默认为200。
带tag的html template是不可以直接通过浏览器访问的,许通过url访问,经过变量渲染,调试的时候要注意这点
4.2.2 redirect函数
----------url.py url(r"login", views.login), url(r"yuan_back", views.yuan_back), ----------views.py def login(req): if req.method=="POST": //request.POST 和request.GET POST和GET要大写
if 1: # return redirect("/yuan_back/") ##建议使用这个,1,减少代码重复,2刷新后内容不变 name="yuanhao" return render(req,"my backend.html",locals()) return render(req,"login.html",locals()) def yuan_back(req): name="苑昊" return render(req,"my backend.html",locals())
--------login.html <form action="/login/" method="post"> //django 中的 url 前后都加反斜杠!!!!!! <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,那么所有的这一部分 # 除了写在yuan_back的视图函数中,必须还要写在login中,代码重复,没有解耦. # 2 最重要的是:url没有跳转到/yuan_back/,而是还在/login/,所以当刷新后又得重新登录.
redirect 要直接定向到url,更不能没有经过render直接定向到html文件,如果定向到html文件有两个重大问题
问题一、 无法加载数据库中的内容,并传递给html模板
问题二、 虽然页面内容变化,但url不变,一刷新后恢复到原url指定的内容,而指向url的时候:页面变,url变
使用Django时根据需要导入的额外类
1,HttpResponse
2,include
五 Template
5.1 模板系统的介绍
你可能已经注意到我们在例子视图中返回文本的方式有点特别。 也就是说,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模板:HTML代码+逻辑控制代码
逻辑控制语法:
{{ }} 渲染变量
{% %} 渲染标签
5.2 模板支持的语法
模板语言: 将具体数据嵌入到前端模板的语法
5.2.1 变量(使用双大括号来引用变量)
语法格式: {{var_name}}
-----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()方法渲染会 # 更为高效: # Low for name in (\'John\', \'Julie\', \'Pat\'): t = Template(\'Hello, {{ name }}\') #渲染变量 print t.render(Context({\'name\': name})) # Good t = Template(\'Hello, {{ name }}\')#一次创建,多次渲染 for name in (\'John\', \'Julie\', \'Pat\'): print t.render(Context({\'name\': name}))
Django 模板解析非常快捷。大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。这和基于XML的模板引擎形成鲜明对比,那些引擎承担了XML解析器的开销,且往往比 Django 模板渲染引擎要慢上几个数量级。
from django.shortcuts import render,HttpResponse from django.template.loader import get_template #记得导入 # Create your views here. import datetime from django.template import Template,Context # def current_time(req): #原始的视图函数 # now=datetime.datetime.now() # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) # def current_time(req): # django模板修改的视图函数 # now=datetime.datetime.now() # t=Template(\'<html><body>现在时刻是:<h1 style="color:red">{{current_date}}</h1></body></html>\') # 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})
5.2.2 深度变量的查找(万能的句点号)
在到目前为止的例子中,我们通过context传递的简单参数值主要是字符串,然而,模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。在Django模板中遍历复杂数据结构的关键是句点字符 (.)。
深度搜索都是通过. 完成的,比如:
.索引
.key
.对象的属性(ORM返回的是对象)
没有中括号的语法
#最好是用几个例子来说明一下。
#首先,句点可用于访问列表索引,例如: >>> from django.template import Template, Context >>> t = Template(\'Item 2 is {{ items.2 }}.\') #渲染列表中的元素,也可以使用for循环 >>> c = Context({\'items\': [\'apples\', \'bananas\', \'carrots\']}) >>> t.render(c) \'Item 2 is carrots.\' #假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点: >>> from django.template import Template, Context >>> person = {\'name\': \'Sally\', \'age\': \'43\'} >>> t = Template(\'{{ person.name }} is {{ person.age }} years old.\') #渲染字典中的元素,也可以使用for循环 >>> c = Context({\'person\': person}) >>> t.render(c) \'Sally is 43 years old.\' #同样,也可以通过句点来访问对象的属性。 比方说,Python的datetime.date 对象有 #year、month和day 几个属性,你同样可以在模板中使用句点来访问这些属性: >>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template(\'The month is {{ date.month }} and the year is {{ date.year }}.\') >>> c = Context({\'date\': d}) >>> t.render(c) \'The month is 5 and the year is 1993.\'
#.对象的属性(比如ORM返回的是对象集合,也适用) # 这个例子使用了一个自定义的类,演示了通过实例变量加一点(dots)来访问它的属性,这个方法适用于任意的对象。 >>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template(\'Hello, {{ person.first_name }} {{ person.last_name }}.\') >>> c = Context({\'person\': Person(\'John\', \'Smith\')}) >>> t.render(c) \'Hello, John Smith.\' # 点语法也可以用来引用对象的方法。 例如,每个 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\' # 注意这里调用方法时并*没有*使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的方法。
5.2.3 变量的过滤器(filter)的使用
语法格式: {{obj|filter:param}} ##因为有管道符,自身会默认传递给filter,filter后只能加一个参数,加参数的话就需要加冒号(:),不加参数的话,不用使用冒号,类似于python的内部函数,过滤器是模板语言的内部函数
# 1 add : 给变量加上相应的值 # # 2 addslashes : 给变量中的引号前加上斜线 # # 3 capfirst : 首字母大写 # # 4 cut : 从字符串中移除指定的字符 # # 5 date : 格式化日期字符串 # # 6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值 # # 7 default_if_none: 如果值是None,就替换成设置的默认值,否则就使用本来的值 #实例: #value1="aBcDe" {{ value1|upper }}<br> #value2=5 {{ value2|add:3 }}<br> #value3=\'he llo wo r ld\' {{ value3|cut:\' \' }}<br> #import datetime #value4=datetime.datetime.now() {{ value4|date:\'Y-m-d\' }}<br> #value5=[] {{ value5|default:\'空的\' }}<br> #value6=\'<a href="#">跳转</a>\' {{ value6 }} {% autoescape off %} {{ value6 }} {% endautoescape %} {{ value6|safe }}<br>
##如果value6是<a href=\'\'>点击</a>,返回的是原生的,加上 | safe后返回的就是渲染后带标签效果的。
防止xss攻击,比如评论的时候内容是html、css、alter等,所以不会返回原生的。
{{ value6|striptags }} #value7=\'1234\' {{ value7|filesizeformat }}<br> {{ value7|first }}<br> {{ value7|length }}<br> {{ value7|slice:":-1" }}<br> #value8=\'http://www.baidu.com/?a=1&b=3\' {{ value8|urlencode }}<br> value9=\'hello I am yuan\'
过滤器还有其他很多很多的方法,参考书籍或者其他文章,比如:
truncatewords、等方法 比如博客首页只显示前20个单词、字符等
<p>{{ a|truncatewords:3 }}</p>
<p>{{ a|truncatechars:20 }}</p>
后端主要处理逻辑(拿到对象传给前端就可以了),所以这部分可以格式化可以放到前端,但是强制放到后端也是可以的。
前端渲染的时候,如果后端没有给前端传送这个变量,前端是不会报错的,而是显示为空,当然也可以设置默认值default(使用default过滤器)。
5.2.4 标签(tag)的使用(使用大括号和百分比的组合 {% %} 来表示使用tag)
语法格式: {% tags %}
{% if %} 的使用
{% if %}标签计算一个变量值,如果是“true”,即它存在、不为空并且不是false的boolean值,系统则会显示{% if %}和{% endif %}间的所有内容
{% if num >= 100 and 8 %} {% if num > 200 %} <p>num大于200</p> {% else %} <p>num大于100小于200</p> {% endif %} {% elif num < 100%} <p>num小于100</p> {% else %} <p>num等于100</p> {% endif %} {% if %} 标签接受and,or或者not来测试多个变量值或者否定一个给定的变量 {% if %} 标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的: {% if obj1 and obj2 or obj3 %}
深度变量的查找也可以用再if判断中,如下
{% if li.2 < 23 %}
<p>大于</p>
{% else %}
<p>小于</p>
{% endif %}
{% for %}的使用
{% for %}标签允许你按顺序遍历一个序列中的各个元素,每次循环模板系统都会渲染{% for %}和{% endfor %}之间的所有内容
<ul> {% for obj in list %} <li>{{ obj.name }}</li> {% endfor %} </ul> #在标签里添加reversed来反序循环列表: {% for obj in list reversed %} ... {% endfor %} #{% for %}标签可以嵌套: {% for country in countries %} <h1>{{ country.name }}</h1> <ul> {% for city in country.city_list %} <li>{{ city }}</li> {% endfor %} </ul> {% endfor %} #系统不支持中断循环,系统也不支持continue语句,{% for %}标签内置了一个forloop模板变量, #这个变量含有一些属性可以提供给你一些关于循环的信息 1,forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1: {% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %} 2,forloop.counter0 类似于forloop.counter,但它是从0开始计数,第一次循环设为0 3,forloop.revcounter 4,forloop.revcounter0 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_token的标签,用于防治跨站攻击验证。 其实,这里是会生成一个input标签,和其他表单标签一起提交给后台的。
{% url %}
引用路由配置的地址
<form action="{% url "bieming"%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>
{% with %}
用更简单的变量名替代复杂的变量名
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
{% verbatim %}
禁止render
{% verbatim %} {{ hello }} #页面就要这个显示,即不被渲染的内容 {% endverbatim %}
{% load %}
略
加载标签库:自定义filter(一定会使用到)和simple_tag
a、在app中创建templatetags模块(必须的)
b、创建任意 .py 文件,如:my_tags.py
from django import template from django.utils.safestring import mark_safe register = template.Library() #register的名字是固定的,不可改变 @register.filter #自定义标签装饰器 def filter_multi(v1,v2): return v1 * v2 @register.simple_tag #自定义simpl_tag装饰器 def simple_tag_multi(v1,v2): return v1 * v2 @register.simple_tag def my_input(id,arg): ##传入参数 result = "<input type=\'text\' id=\'%s\' class=\'%s\' />" %(id,arg,) return mark_safe(result)
c、在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py :{% load my_tags %},(类似于python中先调用某个模块,然后才能使用这个模块中的函数)
d、使用simple_tag和filter(如何调用)
-------------------------------.html {% load xxx %} #首行
#filter {{}} # view中定义num=12
# html中定义如下
{{ num|filter_multi:2 }} #24 filter只能接受一个参数 {{ num|filter_multi:"[22,333,4444]" }} # 传入一个集合,循环集合内部的元素,但实现比较复杂,结果为12*22*333*4444 #simple_tag {% %} {% simple_tag_multi 2 5 %} 参数不限,当然传入的参数和定义的参数个数要一直,但不能放在if for语句中 {% simple_tag_multi num 5 %}
e、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
备注:如上配置后需要重启django服务~~~~~~~~
注意:
filter可以用在if等语句后,simple_tag不可以(因为simple_tag也用的是{% %}(调用的时候必须加) 会和外层if的{% %} 冲突)
#filter可以用在if等语句后
{% if num|filter_multi:30 > 100 %} ##大于号左右必须有空格 {{ num|filter_multi:30 }} {% endif %}
#simple_tag不可以
{% if {% simple_tag_multi 2 5 %} > 100 %} ##大于号左右必须有空格,去掉内层的{% %}也不可以,依旧会语法报错
<p>666</p>
{% endif %}
extend模板继承
到目前为止,我们的模板范例都只是些零星的 HTML 片段,但在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?Django 解决此类问题的首选方法是使用一种优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
让我们通过修改 current_datetime.html 文件,为 current_datetime 创建一个更加完整的模板来体会一下这种做法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <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 PUBLIC "-//W3C//DTD HTML 4.01//EN"> <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>
Django的模板继承系统解决了这些问题。 你可以将其视为服务器端include的逆向思维版本。 你可以对那些不同的代码段进行定义,而不是共同代码段。
第一步是定义基础模板,该框架之后将由子模板所继承。 以下是我们目前所讲述范例的基础模板:
<!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 框架文档,我们将在本站点的所有页面中使用。子模板的作用就是重载、添加或保留那些块的内容。 (如果你一直按顺序学习到这里,保存这个文件到你的template目录下,命名为 base.html .)
我们使用模板标签: {% block %} 。 所有的 {% 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 视图创建一个模板,看起来是这样的:
{% 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 ,所有其它模板会立即反映出所作修改。
以下是其工作方式:
在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由{% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:
<1> 创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常修改甚至从不修改的部分。 <2> 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对base.html 进行拓展, 并包含区域特定的风格与设计。 <3> 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展相应的区域模板。
这个方法可最大限度地重用代码,并使得向公共区域(如区域级的导航)添加内容成为一件轻松的工作。
以下是使用模板继承的一些诀窍:
<1>如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。 <2>一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此 你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话说,钩子越 多越好。 <3>如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。 如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模 板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。 <4>不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个 相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
<5> {% block %} 可以相互嵌套,定义的越多越好,嵌套关系在页面设计的时候要设计好
例如博客园的后台管理布局就可以使用extends来实现,继承header、左侧left、footer
实现一个功能
需求如下:
1,输入用户名、密码
2,如果登陆成功,返回hello页面;登陆失败则重新登录
实现:
目录结构如下
Django_demo的urls.py内容如下:
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r\'^admin/\', admin.site.urls), url(r\'^login_in/\', views.login_in), url(r"^back", views.back), ]
app01的view.py内容如下:
from django.shortcuts import render, redirect # Create your views here. def login_in(request): # 登陆视图函数 if request.method == "POST": print("=============", request.POST) username = request.POST.get("user") password = request.POST.get("pwd") if username == "yuan" and password == "123": return redirect("/back/") # POST成功后跳转到/back/这个URL,通过URLconf重新定义到back这个视图函数 return render(request, "login_in.html") # POST不成功或者GET访问的时候,渲染login.html def back(request): # POST成功后的视图函数 name = "yuan" return render(request, "back.html", locals()) # POST成功后渲染back.html
template的login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login_in/" method="post"> <p>姓名 <input type="text" name="user"></p> <p>密码 <input type="password" name="pwd"></p> <p><input type="submit"></p> </form> <a href="/app01/sendByget/?year=2013&month=12">发送</a> <!--这行代码和这个功能无关,但是说明了通过GET向后端提交数据的一种方式--> </body> </html>
template的back.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>hello {{ name }}</h1> </body> </html>
实现效果
登陆成功后:
POST失败:
六 数据库与ORM
6.0 Mysql数据表关系基础知识
Q:员工表和部门表的关系:
A:员工表和部门表的关系是多对一
***Mysql的表设计*****
表与表之间的关系分为3种:
使用book表(A),publisher表(B),author表(C)来举例说明
第一种:多对多
A->C A表中的一条记录可以对应C表中的多条记录
C->A C表中的一条记录可以对应A表中的多条记录
以上两种关系同时成立,则A表和C表之间的关系是多对多
第二种:一对多
B->A B表中的一条记录可以对应A表中的多条记录
仅以上一种关系成立,则B表和A表之间对一对多的关系
第三种:一对一
A->B A表中的一条记录只能对应B表中的一条记录
仅以上一种关系成立,则A表和B表之间对一对多的关系
备注:
Q:可以放在一个表中,为什么放在两个表中?
A:设计成一对一关系的数据表属于”数据表垂直切分”, 主要优点是更新/插入是可以减小锁表时间, 查询时可以减小读取的数据量.
如果把两个表文件保存在不同的硬盘上, 那么查询的性能可以更高.更多请参考http://www.cnblogs.com/robinunix/p/7403164.html
—————————————–
一对多
主表(publisher表):不依赖于其他任何表的表 (一)
子表(book表):依赖(外键在这个表上) (多)
主表的一条记录可以对应子表的多条记录,外键建立在子表上(外键建立在一对多的多的上!外键建立在一对多的多的上!外键建立在一对多的多的上!)
主表的一条记录可以对应子表的多条记录,外键建立在子表上(外键建立在一对多的多的上!外键建立在一对多的多的上!外键建立在一对多的多的上!)
主表的一条记录可以对应子表的多条记录,外键建立在子表上(外键建立在一对多的多的上!外键建立在一对多的多的上!外键建立在一对多的多的上!)
多对多
借助第三张表(实际就是这个新的第三张表分别和原先的两个表建立两个foreignkey来实现)
django中有3种方式实现多对多
1,设置m2m字段
2,手工创建第三张表
3,设置m2m,但是添加through参数
一对一
在外键字段的基础上增加唯一约束
–关于主键约束、外键约束、唯一约束,如果在表创建好了以后再加约束,则格式分别为:
主键约束:
alter table 表名
add constraint PK_字段名 –“PK”为主键的缩写,字段名为要在其上创建主键的字段名,\’PK_字段名\’就为约束名
primary key (字段名) –字段名同上
外键约束:
alter table 表名
add constraint FK_字段名 –“FK”为外键的缩写
foreign key (字段名) references 关联的表名(关联的字段名) –注意\’关联的表名\’和\’关联的字段名\’
唯一约束:
alter table 表名
add constraint UQ_字段名
unique (字段名)
简单介绍下外键约束:
外键取值规则:空值或参照关联表的主键值。比如A表中的外键列可以是空值,也可以是B表中的主键列
(1)插入非空值时,如果关联表的主键值中没有这个值,则不能插入。
(2)更新时,不能改为关联表的主键值中没有的值。
(3)删除子表记录时,你可以在建外键时选定外键记录一起级联删除还是拒绝删除。
(4)更新字表记录时,同样有级联更新和拒绝执行的选择。
关于主键约束、外键约束、唯一约束,更多请参考:http://www.cnblogs.com/robinunix/p/7403367.html
6.1 数据库的配置
1 django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
2 mysql驱动程序
- MySQLdb(mysql python)
- mysqlclient
- MySQL
- PyMySQL(纯python的mysql驱动程序)
3 在django的项目中会默认使用sqlite数据库,在settings里有如下设置:
如果我们想要更改数据库,需要修改如下:
DATABASES = { \'default\': { \'ENGINE\': \'django.db.backends.mysql\', \'NAME\': \'books\', #你的数据库名称 \'USER\': \'root\', #你的数据库用户名 \'PASSWORD\': \'\', #你的数据库密码 \'HOST\': \'\', #你的数据库主机,留空默认为localhost \'PORT\': \'3306\', #你的数据库端口 } }
注意:
NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。 设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。 然后,启动项目,会报错:no module named MySQLdb 这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的__init__,在里面写入: import pymysql pymysql.install_as_MySQLdb() 问题解决!
6.2 ORM表模型
6.2.1 ORM 表字段类型详解
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return \'integer UNSIGNED\' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: \'AutoField\': \'integer AUTO_INCREMENT\', \'BigAutoField\': \'bigint AUTO_INCREMENT\', \'BinaryField\': \'longblob\', \'BooleanField\': \'bool\', \'CharField\': \'varchar(%(max_length)s)\', \'CommaSeparatedIntegerField\': \'varchar(%(max_length)s)\', \'DateField\': \'date\', \'DateTimeField\': \'datetime\', \'DecimalField\': \'numeric(%(max_digits)s, %(decimal_places)s)\', \'DurationField\': \'bigint\', \'FileField\': \'varchar(%(max_length)s)\', \'FilePathField\': \'varchar(%(max_length)s)\', \'FloatField\': \'double precision\', \'IntegerField\': \'integer\', \'BigIntegerField\': \'bigint\', \'IPAddressField\': \'char(15)\', \'GenericIPAddressField\': \'char(39)\', \'NullBooleanField\': \'bool\', \'OneToOneField\': \'integer\', \'PositiveIntegerField\': \'integer UNSIGNED\', \'PositiveSmallIntegerField\': \'smallint UNSIGNED\', \'SlugField\': \'varchar(%(max_length)s)\', \'SmallIntegerField\': \'smallint\', \'TextField\': \'longtext\', \'TimeField\': \'time\', \'UUIDField\': \'char(32)\', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, \'both\',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
6.2.2 ORM表字段参数详解
null 数据库中字段是否可以为空 db_column 数据库中字段的列名 db_tablespace default 数据库中字段的默认值 primary_key 数据库中字段是否为主键 db_index 数据库中字段是否可以建立索引 unique 数据库中字段是否可以建立唯一索引 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 verbose_name Admin中显示的字段名称 blank Admin中是否允许用户输入为空 editable Admin中是否可以编辑 help_text Admin中该字段的提示信息 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 如:gf = models.IntegerField(choices=[(0, \'何穗\'),(1, \'大表姐\'),],default=1) error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{\'null\': "不能为空.", \'invalid\': \'格式错误\'} validators 自定义错误验证(列表类型),从而定制想要的验证规则 from django.core.validators import RegexValidator from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length=32, error_messages={ \'c1\': \'优先错信息1\', \'c2\': \'优先错信息2\', \'c3\': \'优先错信息3\', }, validators=[ RegexValidator(regex=\'root_\d+\', message=\'错误了\', code=\'c1\'), RegexValidator(regex=\'root_112233\d+\', message=\'又错误了\', code=\'c2\'), EmailValidator(message=\'又错误了\', code=\'c3\'), ] )
6.2.3 ORM表字段元信息
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 更多:https://docs.djangoproject.com/en/1.10/ref/models/options/
6.2.4 ORM表字段错误提示
1.触发Model中的验证和错误提示有两种方式: a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息 b. 调用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): username = forms.CharField(error_messages={\'required\': \'用户名不能为空.\'}) email = forms.EmailField(error_messages={\'invalid\': \'邮箱格式错误.\'}) age = forms.IntegerField(initial=1, error_messages={\'required\': \'请输入数值.\', \'invalid\': \'年龄必须为数值.\'}) class Meta: model = models.UserInfo # fields = (\'username\',) fields = "__all__" class UserInfoAdmin(admin.ModelAdmin): form = UserInfoForm admin.site.register(models.UserInfo, UserInfoAdmin)
6.2.5 ORM表模型关系详解
备注:如果你是sql大牛,推荐用sql语句更直接,会比ORM的效率更高
- 一对多:models.ForeignKey(其他表)
- 多对多:models.ManyToManyField(其他表)
- 一对一:models.OneToOneField(其他表)
应用场景:
- 一对多:当一张表中创建一行数据时,有一个单选的下拉框
例如:创建用户信息时候,需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】等。 - 多对多:在某表中创建一行数据是,有一个可以多选的下拉框(可以被重复选择)
例如:创建用户信息,需要为用户指定多个爱好 - 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了
例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据
更多请参考:http://www.cnblogs.com/wupeiqi/articles/5246483.html
表(模型)的创建:
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名。
作者详细模型:把作者的详情放到详情表,包含性别,email地址和出生日期,作者详情模型和作者模型之间是一对一的关系(one-to-one)(类似于每个人和他的身份证之间的关系),在大多数情况下我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。
出版商模型:出版商有名称,地址,所在城市,省,国家和网站。
书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(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): #打印类实例化的对象的时候显示的信息,如果不加__str__方法,显示的是object return self.name class Author(models.Model): name = models.CharField(max_length=30) #django默认创建了主键,如果自己创建了主键models.AutoFiled(primary_key=True),则Django不会再创建 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) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) #注意这里是没有加引号的,所以Author类必须在代码上面定义 publisher = models.ForeignKey(Publisher) #ForeignKey是在子表,即一对多的多的上创建,Django默认是关联Publisher表的主键 publication_date = models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2,default=10)
#数据库中的三种浮点类型,decimalField比float和double好用,最大位数是5,小数点后保存2位 ((整数会自动补齐小数点))
#(max_digits=5,decimal_places=2) 最大值99999
def __str__(self):
return self.title
#备注:
主表的一条记录可以对应子表的多条记录,外键建立在子表上(外键建立在一对多的多的上!外键建立在一对多的多的上!外键建立在一对多的多的上!)
举例:一个出版社可以有多本书,所以foreignkey创建在书的表上,django默认是关联另外一个表的主键
publisher = models.ForeifnKey(Publisher)
没有引号,这种方式Publisher需放在引用的上面先定义,
publisher = models.ForeifnKey(\'Publisher\')
有引号,这种方式Publisher可以写在下面,Django会自动找,ManyToMany类似
Django自动将这个字段和字符串\'_id\'拼接,即字段最后为publisher_id
分析代码:
<1> 每个数据模型都是django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法。并提供了一个简介漂亮的定义数据库字段的语法。
<2> 每个模型相当于单个数据库表(多对多关系例外,会多生成一张关系表),每个属性也是这个表中的字段。属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)。大家可以留意下其它的类型都和数据库里的什么字段对应。
<3> 模型之间的三种关系:一对一,一对多,多对多。
一对一:实质就是在主外键(author_id就是foreign key)的关系基础上,给外键加了一个UNIQUE=True的属性;
一对多:就是主外键关系;(foreign key)
多对多:(ManyToManyField) 自动创建第三张表(当然我们也可以自己创建第三张表:两个foreign key)
ORM之增(create,save)
单表增
from app01.models import * #create方式一: Author.objects.create(name=\'Alvin\') #可以使用逗号分隔,指定多个key=value #create方式二: Author.objects.create(**{"name":"alex"}) # 将字典转换成关键字参数传入
#save方式一: author=Author(name="alvin") #传入参数实例化,之后调用save方法 author.save() #save方式二: author=Author() #先实例化,然后设置对象属性,之后调用save方法 author.name="alvin" author.save()
重点来了------->那么如何创建存在一对多或多对多关系的一本书的信息呢?(如何处理外键关系的字段如一对多的publisher和
多对多的authors)
#一对多(ForeignKey): #方式一: 插入关联表的id
由于绑定一对多的字段,比如publish,存到数据库中的字段名叫publish_id,所以我们可以直接给这个字段设定对应值: Book.objects.create(title=\'php\', publisher_id=2, #这里的2是指为该book对象绑定了Publisher表中id=2的行对象(当然这个表提前存在id=2的记录,实现的时候,如果报错或不存在引导用户创建), #虽然Book表创建的时候用的是publisher,但是实际生成的外键字段是publisher_id(通过对象id赋值) publication_date=\'2017-7-7\', price=99) #方式二:插入关联表的对象 #<1> 先获取要绑定的Publisher对象:
重新创建: pub_obj=Publisher(name=\'河大出版社\',address=\'保定\',city=\'保定\',state_province=\'河北\',country=\'China\',website=\'http://www.hbu.com\') 或者过滤已有的: pub_obj=Publisher.objects.get(id=1) #<2>将方式一中的 publisher_id=2 改为 Book.objects.create(title=\'php\', publisher=pub_obj, #这里的pub_obj是指为该book对象绑定了Publisher表中pub_obj这个对象(一个对象其实就是一条记录), #虽然Book表创建的时候用的是publisher,但是实际生成的外键字段是publisher_id(通过对象id赋值) publication_date=\'2017-7-7\', price=99) 备注: 表.objects.filter(过滤条件) 取到的是集合对象,类似于之前的[obj1,obj2,obj3] 表.objects.get(过滤条件) 只有找到一个对象(其实就是一条记录)的时候才是正确的,得到多个就是错误的 一本书只能有一个出版社,即 print("+++ publisher:",book_obj.publisher) 取到的是和这本书相关的publisher,是一个object对象,所以可以给book的publisher这个字段直接赋值
其实质也是:找到pub_obj的id号再赋值给publisher_id 两种方式看需求,能拿到哪个用哪个 #多对多(ManyToManyField()): 方式一:manytomany的方式 ManyToMany不会创建字段,ORM是会创建第三张表,但是这个表名不可以使用,因为views的名称空间中没有这个表名的变量, 所以只能使用上面第二种对象的方法,但是又不能直接赋值(上面能够直接赋值是因为一本书只能有一个出版社) 因为是多对多,所以通过add方法(类似于列表的append方法) #--------给book添加author对象----------- author1=Author.objects.get(id=1) #这个数据必须得先有了,即数据初始化了) author2=Author.objects.filter(name=\'alvin\')[0] #[0]也可以使用.first() book=Book.objects.get(id=1) #每条记录都是一个对象,所以可以取对象的authors属性, #print("+++ authors:",book_obj.authors) 取到的是与这本书相关的所有作者,是一个集合对象 book.authors.add(author1,author2) #可以使用这个集合对象的add方法来添加上面取到的作者对象author1,author2 #等同于: book.authors.add(*[author1,author2]) # *会把集合对象(列表类型)当做位置参数传递给函数 book.authors.remove(*[author1,author2]) add方法实质:author1和author2的id值取出来和book的id值自动放在第三张表中,来实现创建关系 #--------给author添加book对象(实质和上面的等效)----------- book=models.Book.objects.filter(id__gt=1) #直接取到的是集合对象(列表类型),也可以直接all或者其他filter(条件)的结果 authors=models.Author.objects.filter(id=1)[0] #同理,每条记录都是一个对象,authors的book属性取到的是这个作者写过的所有书籍 authors.book_set.add(*book) authors.book_set.remove(*book)
_set知识补充:参考下面的备注2
#------------------- book.authors.add(1) ###定义了m2m的表的字段比较方便,直接调用add和remove方法 book.authors.remove(1) authors.book_set.add(1) ? ###没有定义m2m的表通过 表.属性_set.add() 这种方法调用add和remove方法 authors.book_set.remove(1) ? 备注1:只有add和remove方法,修改的话,只能先删除再重新添加,还有set方法。。。。。待确认~~
备注2:book.authors得到book关联的authors【在book中定义了m2m字段】
authors.book_set得到authors关联的book
author的对象可以通过book_set得到author对应的book
publisher的对象也可以通过book_set得到publisher对应的book
author的对象可以通过book_set得到author对应的book
publisher的对象也可以通过book_set得到publisher对应的book
author的对象可以通过book_set得到author对应的book
publisher的对象也可以通过book_set得到publisher对应的book
#注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式【通过add、remove、clear】 # 如果第三张表是自己创建的: class Book2Author(models.Model): author=models.ForeignKey("Author") Book= models.ForeignKey("Book") 方式二:自己修改自己创建的第三张表 author_obj=models.Author.objects.filter(id=2)[0] #models.表名 是因为并不是from models import * 所以需要models.表名 book_obj =models.Book.objects.filter(id=3)[0] 方法一:create s=models.Book2Author.objects.create(author_id=1,Book_id=2) #django会自动创建主键列 方法二:实例化对象 + save方法 s=models.Book2Author(author=author_obj,Book_id=1) s.save()
重点是要理解这两个字段
book_obj.publisher 取到的是和这本书相关的publisher,是一个object对象,所以可以直接赋值
book_obj.authors 取到的是与这本书相关的所有作者,是一个集合对象,调用add、remove方法
备注:
一对多
方式一(可通过:create | 实例化 + save): 使用publisher_id的方式,类似mysql中的insert方式插入 方式二(可通过:create | 实例化 + save): 直接赋值对象 方式二的实质还是翻译成方式一,取到两个id然后赋值 备注:__str__ 方法,也可以直接打印book_obj.publisher.name,但是__str__更简单
多对多
方式一: 写manytomany的语句 不会创建字段,而是创建第三张表,Book表没变化,但是Book类中多了一个authors的字段【在book中定义的m2m,则Book类中多了一个字段】 无法向第三张表直接插入,因为生成的第三张表不可直接使用 book_obj.authors.all() ##刚开始的时候是None,因为还没有任何绑定关系,然后再添加 author_list=Author.objects.all() Book_obj.authors.add(*author_list) 解除多对多关系 Book_obj.authors.remove(*author_list) 适用于只删除特定的关联关系 Book_obj.authors.clear() 适用于删除所有关联关系 方式二: 注释掉manytomany的语句,在models中手动创建第三张表(Book2Author)方式,执行migrate后,django自动创建的第三张表会被删除 Book2Author.objects.create(book_id=1,author_id=1) ##这个相当于是两个一对多赋值,两个一对多的foreignkey(第三张表是子表,一对多的多) 方式一【指的是m2m的方式】的不可以这么直接创建,是因为生成的第三张表不可直接使用
QuerySet API详解-get、create、get_or_create和bulk_create方法:
create :创建一条数据,并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象,然后再调用这个对象的 save 方法。示例代码如下:
article = Article(title=\'abc\') article.save() # 下面这行代码相当于以上两行代码 article = Article.objects.create(title=\'abc\')
get_or_create :根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个。示例代码如下:
如果有标题等于 默认分类 的分类,那么就会查找出来,如果没有,则会创建并且存储到数据库中。
这个方法的返回值是一个元组,元组的第一个参数 obj 是这个对象,第二个参数 created 代表是否创建的。
数据库模型中也可以怎么搞:
def publisher_default(): return Publisher.objects.get_or_create(name="默认出版社") class Book(models.Model): name = models.CharField(max_length=300) pages = models.IntegerField() price = models.FloatField() rating = models.FloatField() author = models.ForeignKey(Author,on_delete=models.CASCADE) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) # publisher = models.ForeignKey(Publisher, on_delete=models.SET_DEFAULT, default=publisher_default)
bulk_create :一次性创建多个数据。示例代码如下:
Tag.objects.bulk_create([Tag(name=\'111\'), Tag(name=\'222\')])
ORM之删(delete)
>>> Book.objects.filter(id=1).delete() (3, {\'app01.Book_authors\': 2, \'app01.Book\': 1})
我们表面上删除了一条信息,实际却删除了三条,因为我们同时也删除的这本书在Book_authors表中有两条相关信息,这种删除方式就是django默认的级联删除(数据删除后,关联表中和这个有关的数据也默认删除)。
ORM之改(update和save)
实例:
注意:
<1> 第二种方式修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如name=\’alvin\’,可能有两个name=\’alvin\’的行数据)。
<2>在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会更新一行里的所有列(效率低,不过可以通过指定update_fields来指定要更新的列而不是所有列,参考下面的部分说明)。 而某些情况下,我们只需要更新行里的某几列。
#---------------- update方法直接设定对应属性(效率高)---------------- models.Book.objects.filter(id=3).update(title="PHP") ##sql: ##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;
此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录,update()方法会返回一个整型数值,表示受影响的记录条数。
注意,这里因为update返回的是一个整形,所以没法用query属性???;
对于每次创建一个对象,想显示对应的raw sql(原生sql),需要在settings加上日志记录部分(可以在终端显示执行的sql语句):
LOGGING = { \'version\': 1, \'disable_existing_loggers\': False, \'handlers\': { \'console\':{ \'level\':\'DEBUG\', \'class\':\'logging.StreamHandler\', }, }, \'loggers\': { \'django.db.backends\': { \'handlers\': [\'console\'], \'propagate\': True, \'level\':\'DEBUG\', }, } }
关于model.save()的补充
Model.
save
(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
id和pk 如果一个model里没有显示声明哪一个字段(field)是主键(即在某个字段里声明primary_key=True),则django会自动生成一个名为id的AutoFields作为主键。model里的主键都有一个别名叫做pk(primary_key的缩写),无论这个主键是自动生成的还是我们指定的。使用pk和使用主键的名字是一样的效果。 注意1:对于一个要新加入数据库的model实例,在save()执行之前是无法知道它的id的,因为要算数据库中已有多少个本model才能算出它的id值。 >>> b2 = Blog(name=\'Cheddar Talk\', tagline=\'Thoughts on cheese.\') >>> b2.id # Returns None, because b doesn\'t have an ID yet. >>> b2.save() >>> b2.id # Returns the ID of your new object. force_update和force_insert 这两个参数一般较少用到,因为save()之后django执行的是UPDATE或者INSERT这两条SQL语句的哪一条,遵循如下算法: 1.如果这个对象已经有主键而且主键的值是True的(即不是None或者空字符串等),就执行UPDATE。 2.如果没有主键或者这条save()不会update任何字段,那么它就INSERT。 只有在某些特定情况下,需要强制save()执行INSERT或UPDATE时才会使force_update=True或force_insert=True(比如我要求能UPDATE就UPDATE,不能我也不取INSERT,那么我就把这个force_update参数设置为True)。 update_fields 用来指定哪些字段需要更新,别的不更新。默认是None,这样所有字段都会更新一遍。有时候字段很多而我们只需要更新很少的字段,可以用这个参数来提高一下效率。注意要给它的是一个可迭代对象(比如list等)。如果给它一个空的可迭代对象,那么就什么都不更新(注意和None不同,如果等于None是更新全部字段)。 一旦update_fields参数不使用默认值None,那么这条save()语句就是强制执行UPDATE的。 product.name = \'Name changed again\' product.save(update_fields=[\'name\'])
update_or_create(如果搜索到了多条记录,不会执行update操作,会报错)
SystemConfig.objects.update_or_create(host_ip=host_ip, type=TYPE, defaults={\'config\': config}) # 相当于先去 get(host_ip=host_ip, type=TYPE) 如果存在且是一条记录的时候,执行更新 config # 不存在 create(host_ip=host_ip, type=TYPE, config=config)
同样的方法还有get_or_create(),可以在本页搜索到,更多query_set_api 可查看
https://code.ziqiangxuetang.com/django/django-queryset-api.html
https://docs.djangoproject.com/en/2.2/ref/models/querysets/
ORM之查(filter,value)
查询API语法:
Book.objects.all() 表名.objects.查询api()
QuerySet是一种数据类型,这个类型有上面提到的add、remove、update等方法
查询相关API (查询的API是通用的,在单表查询和关联查询中都可以使用)
# <1>filter(**kwargs): 按条件过滤,它包含了与所给筛选条件相匹配的对象【条件为空就是显示所有】 # <2>all(): 查询所有结果, -得到的是QuerySet的集合对象[obj1,obj2,obj3] obj代表的是当前操作表的一条记录(一个对象) # <3>get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 #-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()-------- # <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列(对象的一个或多个属性)
values可以放在filter()之后,也可以直接使用 表名.objects.values(\'name\') # <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象,按条件排除 # <6>order_by(*field): 对查询结果按某个字段排序,sql中放在最后,按哪个字段排序 # <7>reverse(): 对查询结果反向排序 # <8>distinct(): 从返回结果中剔除重复纪录,select distinct id from ... 没有意义,因为id不会重复 # <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 # <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。使用方式:QuerySet的集合对象.count() 统计集合中有多少对象 # <11>first(): 使用方式:QuerySet的集合对象.first() 取到的是集合中的第一个model对象,单个obj返回第一条记录 # <12>last(): 使用方式:QuerySet的集合对象.last() 取到的是集合中的最后一个model对象,单个obj返回第一条记录
# <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False
exists,返回true和false,非常重要!!!!!
用处:
查看指定条件下是否有记录
方法一:
select * from
方法二:
使用exists,只会走一条,存在则不会继续执行,效率更高
#ret1=Book.objects.values("title","price") # QuerySet里存放字典
#ret1=Book.objects.values_list("title","price") # QuerySet里存放元祖
#ret1 = Book.objects.values("title",)
#print("result: ",ret1) # <QuerySet [{\'title\': \'python\'}, {\'title\': \'西游记\'}, {\'title\': \'python3\'}]>
QuerySet的集合对象.values() 取对象的某些或某个固定的字段,返回的是字典,(可以放在filter之后)
ret=Book.objects.values(“title”)
ret=Book.objects.filter(nid__gt=2).values(“title”)
print(ret)
QuerySet的集合对象.values_list() 取对象的某些或某个固定的字段,返回的是元祖,(可以放在filter之后)
---------------了不起的双下划线(__)之单表条件查询----------------
语法:表.objects.filter(字段1__keyword,字段2__keyword) 逗号是且的关系,逗号是且的关系,逗号是且的关系
# models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # #
startswith,
istartswith,
endswith,
iendswith,
那么双下划线支持的方法有哪些呢? 查看 https://docs.djangoproject.com/zh-hans/2.2/ref/models/querysets/#field-lookups 搜索Field
lookups ,支持下面的方法,有对应的例子
exact
iexact
contains
icontains
in
gt
gte
lt
lte
startswith
istartswith
endswith
iendswith
range
date
year
iso_year
month
day
week
week_day
quarter
time
hour
minute
second
isnull
regex
iregex
参考 https://blog.csdn.net/weixin_45019231/article/details/95789838
Django中defer和only区别
defer(\’id\’,\’name\’):取出对象,字段除了id和name都有
only(\’id\’,\’name\’):取的对象,只有id和name
如果点,依然能点出其它列,但是不要点了,因为取没有的列,会再次查询数据库
ret=models.Author.objects.only(\'nid\') for i in ret: # 查询不在的字段,会再次查询数据库,造成数据库压力大 print(i.name)
但是这个defer和only是不能排除主键id的,参考:https://code.djangoproject.com/ticket/13442
select_related介绍
select_related:用来优化数据库查询的操作,可以没有,但优化的不够彻底。
用于在foreignkey查询的时候使用。可以通过query来查看一下django执行的sql语句。
ret1 = models.UserInfo.objects.all() ret2 = models.UserInfo.objects.all().select_related() print ret1.query print ret2.query # 查看django执行的sql语句
语句如下
ret1: SELECT "App01_userinfo"."id", "App01_userinfo"."user_type_id", "App01_userinfo"."username", "App01_userinfo"."age" FROM "App01_userinfo" ret2: SELECT "App01_userinfo"."id", "App01_userinfo"."user_type_id", "App01_userinfo"."username", "App01_userinfo"."age", "App01_usertype"."id", "App01_usertype"."caption" FROM "App01_userinfo" INNER JOIN "App01_usertype" ON ("App01_userinfo"."user_type_id" = "App01_usertype"."id")
related_name介绍
我先定义两个模型,一个是作者,一个是作者出版的书籍,算是一对多的类型。 class Person(models.Model); name = models.CharField(verbose_name=\'作者姓名\', max_length=10) age = models.IntegerField(verbose_name=\'作者年龄\') class Book(models.Model): person = models.ForeignKey(Person, related_name=\'person_book\') title = models.CharField(verbose_name=\'书籍名称\', max_length=10) pubtime = models.DateField(verbose_name=\'出版时间\') 如果我们要查询一个作者出版了哪些书籍的话,那我们要怎么做呢? 即通过一查询出多的那方面 先查询到作者的信息 person = Person.objects.fiter(你的条件) 返回一个person对象 接下来就查询person关联的所有书籍对象,我们在前面讲过的使用基于对象的查询方式,反向查询按照表名 book = person.book_set.all() django 默认每个主表的对象都有一个是外键的属性,可以通过它来查询到所有属于主表的子表的信息。 这个属性的名称默认是以子表的名称小写加上_set()来表示,默认返回的是一个queryset对象,你可以继续的根据情况来查询等操作。 在实际项目中,我们使用最多的还是related_name 如果你觉得上面的定义比较麻烦的话,你也可以在定义主表的外键的时候,给这个外键定义好一个名称。要用related_name比如在Book表中: person = models.ForeignKey(Person, related_name=\'person_books\') 那么实现上面的需求,可以使用person.book_set.all() 也可以使用person.person_books.all() # 如果是ForeignKey话,得到是queryset对象,如果是OneToOneField的话,则得到的直接是对象,通过 .属性的方式获取对应的属性。
可参考:https://www.cnblogs.com/jiesir/p/6688524.html
Django ORM的几种时间筛选方式
时间筛选方式
直接使用__year、__month、__day、__week_day
年:User.object.filter(start_time__year=2019)
月:User.object.filter(start_time__month=10)
日:User.object.filter(start_time__day=10)
星期几:User.object.filter(start_time__week_day=1)
使用__lt、__gt范围
大于:
import datetime now = datetime.datetime.now() User.object.filter(start_time__gt=now)
大于等于
User.object.filter(start_time__gte=now)
小于
User.object.filter(start_time__lt=now)
小于等于
User.object.filter(start_time__lte=now)
使用__range范围
import datetime import calendar now = datetime.datetime.now() # 获取一个月的总天数 days = calendar.monthrange(now.year, now.month)[1] start_ = datetime.date(now.year, now.month, 1) end_ = datetime.date(now.year, now.month, days) User.object.filter(start_time__range=(start_, end_)) # 要注意起始和终止的顺序,否则查到为空
其他参考:
[django]用日期来查询datetime类型字段 (date_time_field__contains=datetime.date(1986, 7, 28))
====day18
两条sql语句
QuerySet与惰性机制
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:
<1> 可迭代的
<2> 可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一个行对象 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) # print(objs[1:4]) # print(objs[::-1])
QuerySet的高效使用:
<1>Django的queryset是惰性的 Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得 到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave") 上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数, 这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql. 为了验证这些,需要在settings里加入 LOGGING(验证方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行 (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset, 你不需要重复运行通用的查询。 obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) ## models.Book.objects.filter(id=3).update(title="GO") ## obj_new=models.Book.objects.filter(id=3) # for i in obj: # print(i) #LOGGING只会打印一次 <4> 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4) # exists()的检查可以避免数据放入queryset的cache。 if obj.exists(): print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。 objs = Book.objects.all().iterator() # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 for obj in objs: print(obj.name) #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 for obj in objs: print(obj.name) #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
对象查询,单表条件查询,多表条件关联查询
#--------------------对象形式的查找-------------------------- # 正向查找 ret1=models.Book.objects.first() print(ret1.title) print(ret1.price) print(ret1.publisher) print(ret1.publisher.name) #因为一对多的关系所以ret1.publisher是一个对象,而不是一个queryset集合 【能打印ret1.publisher是因为在Book的Model中定义了Foreignkey】 # 反向查找 ret2=models.Publish.objects.last() print(ret2.name) print(ret2.city) #如何拿到与它绑定的Book对象呢? print(ret2.book_set.all()) #ret2.book_set是一个queryset集合
#如何拿到与它绑定的Book对象呢?
print(ret2.book_set.all()) #ret2.book_set是一个queryset集合
#如何拿到与它绑定的Book对象呢?
print(ret2.book_set.all()) #ret2.book_set是一个queryset集合 #---------------了不起的双下划线(__)之单表条件查询---------------- # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # # startswith,istartswith, endswith, iendswith, #----------------了不起的双下划线(__)之多表条件关联查询--------------- # 正向查找(条件) # ret3=models.Book.objects.filter(title=\'Python\').values(\'id\') # print(ret3)#[{\'id\': 1}] #正向查找(条件)之一对多 ret4=models.Book.objects.filter(title=\'Python\').values(\'publisher__city\') print(ret4) #[{\'publisher__city\': \'北京\'}] #正向查找(条件)之多对多 ret5=models.Book.objects.filter(title=\'Python\').values(\'author__name\') #values中使用双下划线跨表 print(ret5) ret6=models.Book.objects.filter(author__name="alex").values(\'title\') #filter中使用双下划线跨表 print(ret6) #注意 #正向查找的publisher__city或者author__name中的publisher,author是book表中绑定的字段 #一对多和多对多在这里用法没区别 # 反向查找(条件) #反向查找之一对多: ret8=models.Publisher.objects.filter(book__title=\'Python\').values(\'name\') print(ret8)#[{\'name\': \'人大出版社\'}] 注意,book__title中的book就是Publisher的关联表名 ret9=models.Publisher.objects.filter(book__title=\'Python\').values(\'book__authors\') print(ret9)#[{\'book__authors\': 1}, {\'book__authors\': 2}] #反向查找之多对多: ret10=models.Author.objects.filter(book__title=\'Python\').values(\'name\') print(ret10)#[{\'name\': \'alex\'}, {\'name\': \'alvin\'}] #注意 #反向查找的book__title中的book是表名Book #一对多和多对多在这里用法没区别
版权归作者所有,任何形式转载请联系作者。
作者:petanne(来自豆瓣)
来源:https://www.douban.com/note/301166150/
class A(models.Model):
name = models.CharField(u\’名称\’)
class B(models.Model):
aa = models.ForeignKey(A)
B.objects.filter(aa__name__contains=\’searchtitle\’)
注意:条件查询即与对象查询对应,是指在filter,values等方法中的通过__来明确查询条件。
多对多查询课堂笔记:
多表查询 sql中: 现在有员工表emp、部门表dep,需求:张三所在部门的名字是什么 两种方式实现: 1,子查询 select name from dep where id = (select dep_id from emp where name=\'张三\') 2,连表查询(两张表连起来,所有字段都可用) select dep.name from emp inner join dep on emp_dep_id=dep.id where epm.name=\'张三\' sql中的连表查询一般都通过inner join或者left join来实现 Django的ORM最后也是转换成连表查询 Django的ORM连表查询的方式 1,对象 2,双下划线 __ (推荐) 对象:
一对多 book_obj=Book.objects.get(title="python") print(book_obj.publisher) print(book_obj.publisher.email) 多对多 author_list=book_obj.authors.all() print(author_list) 具体实现不要用对象的方式,要使用 __ (双下划线) 多表:values 和filter 中都可以跨表 ****************** Book.objects.get(title="python").values("publisher__email") #在values中跨表,publisher是book表中 publisher 外键字段 ****************** Book.objects.get(title="python").values("authors__email") #在values中跨表,authors是book表中 authors 多对多字段 备注:第三张表自建的话不能通过这种方式 正向查询(依赖的是子表当中的字段,也可以理解为从定义了ForeignKey和ManyToManyField的表开始查询): 通过book表的publisher 和 authors 字段来查(主语是book) #找alex出过的书的名称 Book.objects.filter(authors_name="alex").values("title") 反向查询(在主表中查)(主语是author) Author.objects.filter(name="alex").values("book_title") #在values、filter中直接用表名(小写),因为不像正向查找有字段名字 结论:
正向查找是字段关联,反向查找是小写表名关联
正向查找是字段关联,反向查找是小写表名关联
正向查找是字段关联,反向查找是小写表名关联
####### 示例: 查询出版过python这本书的出版社的名字 反向(在主表中通过小写表名关联 查询): ret8=models.Publisher.objects.filter(book__title=\'Python\').values(\'name\') 正向(在子表中通过字段关联 查询): Book.objects.filter("title=python").values("publisher__name") Book.objects.filter("title=python") 多对多查询的两种方式 1,对象查询 2,过滤查询 双下划线__ 结合通过filter()或者values()进行跨表
聚合查询和分组查询
参考:https://blog.csdn.net/weixin_42134789/article/details/84567365
在前面的文章中,我们从数据库里查询数据一般只使用了一些初级的查询方法比如filter()和exclude()方法。但如果查询本身比较复杂,比如需要对查询集(queryset)的某些字段进行计算或进行分组计算或排序, 这时我们就需要使用更高级的aggregate和annotate方法了。
aggregate和annotate方法的使用场景
Django的aggregate和annotate方法属于高级查询方法,主要用于组合查询,是Django高手们必需要熟练掌握的。当我们需要对查询集(queryset)的某些字段进行计算或进行先分组再计算或排序, 我们就需要使用aggregate和annotate方法了。
假如我们有如下一个模型,其中Student与Hobby(爱好)是多对多的关系
class Student(models.Model): name = models.CharField(max_length=20) age = models.IntegerField() hobbies = models.ManyToManyField(Hobby) class Hobby(models.Model): name = models.CharField(max_length=20)
aggregate()方法详解
aggregate的中文意思是聚合, 源于SQL的聚合函数。Django的aggregate()方法作用是对一组值(比如queryset的某个字段)进行统计计算,并以字典(Dict)格式返回统计计算结果。django的aggregate方法支持的聚合操作有AVG / COUNT / MAX / MIN /SUM 等。
我们现在来看下几组实际使用案例。使用前别忘了import Avg, Max, Min或者Sum方法哦
from django.db.models import Avg, Max, Min
# 计算学生平均年龄, 返回字典。age和avg间是双下划线哦
Student.objects.all().aggregate(Avg(\’age\’))
{ \’age__avg\’: 12 }
# 学生平均年龄,返回字典。all()不是必须的。
Student.objects.aggregate(Avg(\’age\’))
{ \’age__avg\’: 12 }
# 计算学生总年龄, 返回字典。
Student.objects.aggregate(Sum(\’age\’))
{ \’age__sum\’: 144 }
# 学生平均年龄, 设置字典的key
Student.objects.aggregate(average_age = Avg(\’age\’))
{ \’average_age\’: 12 }
# 学生最大年龄,返回字典
Student.objects.aggregate(Max(\’age\’))
{ \’age__max\’: 12 }
# 同时获取学生年龄均值, 最大值和最小值, 返回字典
Student.objects.aggregate(Avg(\’age‘), Max(\’age‘), Min(\’age‘))
{ \’age__avg\’: 12, \’age__max\’: 18, \’age__min\’: 6, }
# 根据Hobby反查学生最大年龄。查询字段student和age间有双下划线哦。
Hobby.objects.aggregate(Max(\’student__age\’))
{ \’student__age__max\’: 12 }
annotate()方法详解
annotate的中文意思是注释,小编我觉得是非常地词不达意,一个更好的理解是分组(Group By)。如果你想要对数据集先进行分组然后再进行某些聚合操作或排序时,需要使用annotate方法来实现。与aggregate方法不同的是,annotate方法返回结果的不仅仅是含有统计结果的一个字典,而是包含有新增统计字段的查询集(queryset).
我们接下来也看下几个实际使用案例。
# 按学生分组,统计每个学生的爱好数量
Student.objects.annotate(Count(\’hobbies\’))
返回的结果依然是Student查询集,只不过多了hobbies__count这个字段。如果你不喜欢这个默认名字,你当然可以对这个字段进行自定义从而使它变得更直观。
# 按学生分组,统计每个学生爱好数量,并自定义字段名
Student.objects.annotate(hobby_count_by_student=Count(\’hobbies\’))
# 按爱好分组,再统计每组学生数量。
Hobby.objects.annotate(Count(\’student\’))
# 按爱好分组,再统计每组学生最大年龄。
Hobby.objects.annotate(Max(\’student__age\’))
1.Annotate方法与Filter方法联用
有时我们需要先对数据集先筛选再分组,有时我们还需要先分组再对查询集进行筛选。根据需求不同,我们可以合理地联用annotate方法和filter方法。注意: annotate和filter方法联用时使用顺序很重要。
# 先按爱好分组,再统计每组学生数量, 然后筛选出学生数量大于1的爱好。
Hobby.objects.annotate(student_num=Count(\’student\’)).filter(student_num__gt=1)
# 先按爱好分组,筛选出以\’d\’开头的爱好,再统计每组学生数量。
Hobby.objects.filter(name__startswith=”d”).annotate(student_num=Count(\’student‘))
2.Annotate与order_by()联用
我们同样可以使用order_by方法对annotate方法返回的数据集进行排序。
# 先按爱好分组,再统计每组学生数量, 然后按每组学生数量大小对爱好排序。
Hobby.objects.annotate(student_num=Count(\’student‘)).order_by(\’student_num\’)
# 统计最受学生欢迎的5个爱好。
Hobby.objects.annotate(student_num=Count(\’student‘)).order_by(\’-student_num\’)[:5]
3.Annotate与values()联用
我们在前例中按学生对象进行分组,我们同样可以按学生姓名name来进行分组。唯一区别是本例中,如果两个学生具有相同名字,那么他们的爱好数量将叠加。
# 按学生名字分组,统计每个学生的爱好数量。
Student.objects.values(\’name\’).annotate(Count(\’hobbies\’))
你还可以使用values方法从annotate返回的数据集里提取你所需要的字段,如下所示:
# 按学生名字分组,统计每个学生的爱好数量。
Student.objects.annotate(hobby_count=Count(\’hobbies\’)).values(\’name\’, \’hobby_count\’)
小结
Django的aggregate和annotate方法属于高级查询方法,主要用于组合查询,可以大大提升数据库查询效率。当你需要对查询集(queryset)的某些字段进行聚合操作时(比如Sum, Avg, Max),请使用aggregate方法。如果你想要对数据集先进行分组(Group By)然后再进行某些聚合操作或排序时,请使用annotate方法。最后希望本文提供的一些示例对你有所帮助哦。
<1> aggregate(*args,**kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
sql中:
分组: select * from emp group by 某个字段 遇到每xxxx----> 就要使用到分组,例如: 每个部门 每门课程的学生 聚合函数一般配合分组使用,分组之后使用聚合函数 having 做过滤,放在group by后面,对分组做过滤 聚合函数使用场景: 所有结果应用聚合函数 分组的结果应用聚合函数 查询book表所有书籍的平均值 select avg(price) from book
ORM中:
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 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的 标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的,比如price_avg。如果你想要为聚合值指定 一个名称,可以向聚合子句提供它: >>> Book.objects.aggregate(average_price=Avg(\'price\')) {\'average_price\': 34.35}
#average_price是自定义键值,默认是price_avg 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: >>> 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):
可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
annotate 就理解为:group by后操作,即对分组后的结果做操作,实际是使用values来分组
查询alex出的书总价格
查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors__name
查询各个出版社最便宜的书价是多少
book.objects.values(“authors__name”).annotate(Sum(“price”))
1,group by authors_name
2,统计分组后的总价格
其他参考:
Django学习笔记之Django ORM Aggregation聚合详解
group by 效果图
—-
F查询和Q查询
仅仅靠单一的关键字参数查询已经很难满足查询要求。此时Django为我们提供了F和Q查询:
Q查询实现 与 或 非的更多的组合查询 & | ~ F查询 每本书涨价100元,主要用在数值型查询的封装操作上 Book.object.update(prince=price+100) 第2个price不可用,当然可以先查出来再用,但是用F查询更简单
# 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)))
总结
Django流程:
1 创建Django项目 : django-admin startproject projectname
2 创建应用: : python manage.py startapp appname
3 在控制器(urls.py)创建 url 与 视图函数的映射关系(一一对应)
4 创建视图函数,完成逻辑代码
5 从数据库取出集合对象
6 把数据库变量嵌入到模板进行渲染(render方法)
7 将渲染后的html页面返回给客户端
URL:协议+域名+端口+路径 eg:http://www.cnblogs.com/yuanchenqi/articles/6811632.html?a=1
协议:http
域名:www.cnblogs.com
端口:80
路径:yuanchenqi/articles/6811632.html
数据:a=1
URL配置中的正则表达式匹配的是一个url的路径部分
TEMPALTE(模板):HTML代码+逻辑控制代码
逻辑控制语法: {{}} 渲染变量 filter : {{var|方法:参数}}
{% %} 渲染标签
{% if %}
{% for %}
{% url %}
{% url %}
自定义filter和simpletag:
(1)在app中创建templatetags模块(必须的)
(2)创建任意 .py 文件,如:my_tags.py
from django import template
register = template.Library()
@register.filter
def filter_multi(v1,v2):
return v1 * v2
(3)创建任意 .py 文件,如:my_tags.py
在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py :{% load my_tags %}
(4)使用simple_tag和filter:
{% load xxx %} #首行
# num=12
{{ num|filter_multi:2 }} #24
总结:
filter: 只能接受一个参数,但是可以用if等语句
simpletag: 能接受多个参数,但是不可以用if等语句
ORM:
表之表之间的关系:
一对多 外键字段一定是在字表(一对多的多的表)中 Foreign KEY
多对多 在第三张表实现,通过两个Foreign KEY
一对一 在外键字段的基础上增加唯一约束。
表.object.filter():得到的是一个集合对象 比如 [obj1,obj2]
表.object.get(): 得到的是一个model对象
一对多的添加记录:
# 方法1:
# Book.objects.create(id=1,title=”python”,publication_date=”2017-03-04″,price=88.8,publisher_id=1)
#方法2
p1=Publisher.objects.get(name=”人大出版社”)
Book.objects.create(id=2,title=”python”,publication_date=”2017-05-04″,price=98.8,publisher=p1)
多对多的添加
ManyToMany只有一种添加方式:
book.authors.add(*[author1,author2])
book.authors.remove(*[author1,author2])
注意:理解 book_obj.publisher
book_obj.authors
自建第三张表
class Book2Author(models.Model):
author=models.ForeignKey(“Author”)
Book= models.ForeignKey(“Book”)
# 那么就还有一种方式:
author_obj=models.Author.objects.filter(id=2)[0]
book_obj =models.Book.objects.filter(id=3)[0]
s=models.Book2Author.objects.create(author_id=1,Book_id=2)
s.save()
s=models.Book2Author(author=author_obj,Book_id=1)
s.save()
修改操作update和save的区别:
update只是set指定的字段 save set所有字段,所以update 效率更高
查询:
ORM操作知识汇总:
<1> 添加记录方法 单表 # create方式 #Book.objects.create(title="python",price=233) # save 方式 book_obj=Book(title="Linux",price=122) book_obj.save() 一对多添加方式: 方式一: Book.objects.create(title="python",price=223,publisher_id=2) 方式二: book_obj=Book.objects.get(nid=1) print(book_obj.publisher) # 是nid=1的这本书的关联的出版社对象(一个对象) book_obj=Book(title="Linux",price=122,publisher=publish_obj) 多对多的添加方式: ManyToManyField: # 绑定关系 book_obj=Book.objects.get(nid=2) author_list=Author.objects.all() book_obj.authors.add(*author_list) # book_obj.authors: nid=2 的书籍关联的作者的对象集合 # 解除关系 book_obj.authors.clear() #解除所有绑定关系 book_obj.authors.remove(author) #解除单个绑定关系 手动创建第三张表: Book2Author.objects.create(book_id=1,author_id=1) <2> 查询 单表查询 表.objects.all() ----- QuerySet的集合对象 [obj1,obj2] obj:就是当前操作表的一条记录 表.objects.filter(查询条件) ----- QuerySet的集合对象 [obj1,obj2] 表.objects.get() ----- model对象 : obj 当前操作表的一条记录 QuerySet.first() ----- model对象 eg: 表.objects.all().first() QuerySet.last() ----- model对象 表.objects.values("title","price") 返回字典的形式 表.objects.values_list("title","price") 返回元组的形式 万能的__: 表.objects.filter(字段__keyword) models.Tb1.objects.filter(id__lt=10,id__gt=1) # 获取id大于1 且 小于10的值 models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in models.Tb1.objects.filter(name__contains="ven") models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and #startswith,istartswith, endswith, iendswith 关联查询(多表查询) sql: 子查询 select name from dep whrere id= (select dep_id from emp whrere name="张三") 联表查询(两表字段可以一起使用) select dep.name from emp inner join dep on emp_dep_id=dep.id where emp.name="张三" 两个手段: 1 对象 2 双下划线__ 1 对象 # 查询python这本书的出版社的联系方式 (一对多) book_obj = Book.objects.get(title="python") ret=book_obj.publisher.email print(ret) # 查询Linux的所有作者的名字 (多对多) book_obj = Book.objects.get(title="Linux") author_list=book_obj.authors.all() for author in author_list: print(author.name) 2 双下划线__ (******) 重点:filter方法与value方法都可以进行跨表查询 重点:filter方法与value方法都可以进行跨表查询 重点:filter方法与value方法都可以进行跨表查询 # 需求一:查询alex出版过的所有书籍名称(多对多关系) # 方式1 正向查询(从Book表开始查询,多对多中定义了ManyToMany字段的表) ret=Book.objects.filter(authors__name="alex").values("title") # 方式2 反向查询(从Author表开始查询,多对多关系,但未定义ManyToMany) ret=Author.objects.filter(name="alex").values("book__title") print(ret) # <QuerySet [{\'book__title\': \'水浒传\'}]> # 需求二:查询出版了python这本书的出版社的名字(一对多关系) # 正向(从子表Book表开始查询,一对多的多),正向查找是(字段__****)关联: ret1=Book.objects.filter(title="python").values("publisher__name") # 反向(从主表Publish表开始查询,一对多的一),反向查找是(小写表名__****)关联: ret2=Publish.objects.filter(book__title="python").values("name") # 需求三:查询python这本书的所有的作者名字(多对多关系) # ret = Book.objects.filter(title="python").values("authors__name") # print(ret) # <QuerySet [{\'authors__name\': \'egon\'}]> 备注:正向、反向其实关系不大,只要是跨表就可以,注意跨表用的是表的英文小写 聚合与分组查询 sql: 聚合函数 max min count avg sum 分组查询 select * from emp group by 字段 ORM: 聚合: # 需求一:查询book表中所有书籍价格的平均值 # sql # select AVG(price) from book # aggregate聚合函数 # ret=Book.objects.all().aggregate(PriceAvg=Avg("price"),maxPrice=Max("price")) # print(ret) # {\'maxPrice\': Decimal(\'223.00\'), \'PriceAvg\': 122.25} # 需求二:查询oldboy出过书的总价格 # ret=Book.objects.filter(authors__name="oldboy").aggregate(Sum("price")) # print(ret) # {\'price__sum\': Decimal(\'266.00\')} 分组(理解为:group by后的操作,即对分组后的结果做操作,实际是使用values来分组): # 每一个作者出过书的总价格 # annotate分组 # ret=Book.objects.values("authors__name").annotate(Sum("price")) # print(ret) F查询与Q查询 from django.db.models import F,Q # 需求:给每本书的价格都调高100 # F查询实现 # Book.objects.update(price=F("price")+100) # Q 查询 支持逻辑与& 逻辑或 | 逻辑非 ~,构成更复杂的条件语句 ret=Book.objects.filter( Q(title__startswith="p")|Q(title__startswith="L") ) <3> 修改 QuerySet.update() eg: Book.objects.filter(id__gt=10).update() eg: Book.objects.get(id=5).update() # models对象不能调用update方法 <4> QuerySet.delete()
七 admin的配置
admin是django强大功能之一,它能共从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用,但是有时候,一些特殊的功能还需要定制,比如搜索功能,下面这一系列文章就逐步深入介绍如何定制适合自己的admin应用。
如果你觉得英文界面不好用,可以在setting.py 文件中修改以下选项
LANGUAGE_CODE = \'en-us\' #LANGUAGE_CODE = \'zh-hans\'
7.1 认识ModelAdmin
管理界面的定制类,如需扩展特定的model界面需从该类继承。
7.2 注册medel类到admin的两种方式:
<1> 使用register的方法
admin.site.register(Book,MyAdmin)
<2> 使用register的装饰器
@admin.register(Book)
7.3 掌握一些常用的设置技巧
- list_display: 指定要显示的字段
- search_fields: 指定搜索的字段
- list_filter: 指定列表过滤器
- ordering: 指定排序字段
from django.contrib import admin from app01.models import * # Register your models here. # @admin.register(Book)#----->单给某个表加一个定制 class MyAdmin(admin.ModelAdmin): list_display = ("title","price","publisher") search_fields = ("title","publisher") list_filter = ("publisher",) ordering = ("price",) fieldsets =[ (None, {\'fields\': [\'title\']}), (\'price information\', {\'fields\': [\'price\',"publisher"], \'classes\': [\'collapse\']}), ] admin.site.register(Book,MyAdmin) admin.site.register(Publish) admin.site.register(Author)
参考博客
http://www.cnblogs.com/yuanchenqi/articles/6811632.html