Python之路【第二十一篇】Django ORM详解
ORM回顾
关系对象映射(Object Relational Mapping,简称ORM)。
django中遵循 Code Frist 的原则,即:根据代码中定义的类来自动生成数据库表。
对于ORM框架里:
我们写的类表示数据库的表
如果根据这个类创建的对象是数据库表里的一行数据
对象.id 对象.value 是每一行里的数据
http://www.cnblogs.com/luotianshuai/p/5301343.html
梳理
首先在理解ORM的时候,我们可以把一对多、多对多 分为正向和反向!
什么是正向呢?看下面的代码!
class UserType(models.Model): caption = models.CharField(max_length=32) class UserInfo(models.Model): user_type = models.ForeignKey('UserType') username = models.CharField(max_length=32) age = models.IntegerField()
ForeignKey在UserInfo表里,如果根据UserInfo去操作就是正向
因为ForeignKey不在UserType里,如果根据UserType去操作反向
环境:
创建一个Project并且创建一个APP,然后配置Model
class UserType(models.Model):
caption = models.CharField(max_length=32)
class UserInfo(models.Model):
user_type = models.ForeignKey('UserType')
username = models.CharField(max_length=32)
age = models.IntegerField()
配置URL然后并配置Views
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^user_type/', views.user_type),
url(r'^user_info/', views.user_info),
]
ORM一对多
什么时候使用一对多?
在设计表结构的时候什么时候使用一对多呢?比如我在创建用户的时候有个菜单让我们选择用户类型的时候,使用一对多!
1、一对多正向反向
首先怎们看下UserType这个表,他没有关联其他表我们在添加的时候没有什么特殊直接添加就行:
def user_type(request): dic = {'caption':'COO'} models.UserType.objects.create(**dic) return HttpResponse('Ok')
我们添加:CEO、CFO、COO 3个用户组
2、正向:增
在看下UserType这个表,默认他在创建的时候回有4列,ID、username、user_type_id、age
所以我们在创建UserType数据的时候有两种方法:第一种方法是直接根据这个字段进行添加数据!给user_type 加 '_id'
def user_info(request): dic = {'username':'luotianshuai','age':18,'user_type_id':3} models.UserInfo.objects.create(**dic) return HttpResponse('OK')
或者通过对象添加
#先获取组的对象 usertype = models.UserType.objects.fiter(id=2) #添加的时候直接添加对象就可以 models.UserInfo.objects.create(username='shuaige',age=18,user_type=usertype) #写成一行也行 models.UserInfo.objects.create(username='tim',age=18,user_type=models.UserType.objects.filter(id=1))
django的get方法是从数据库的取得一个匹配的结果,返回一个对象,如果记录不存在的话,它会报错。
django的filter方法是从数据库的取得匹配的结果,返回一个对象列表,如果记录不存在的话,它会返回[]。
3、正向:查
再次说明,
ForeignKey在UserInfo表里,如果根据UserInfo这张表去查询这两张关联的表的合起来的内容就是正向查
ForeignKey不在UserType里,如果根据UserType这张表去查询这两张关联的表的合起来的内容就是反向查
3.1、需求1、查询所有用户为CEO的用户
首先咱们看下下面的注释:
class UserType(models.Model): caption = models.CharField(max_length=32) class UserInfo(models.Model): user_type = models.ForeignKey('UserType') #这个user_type是一个对象,对象里面封装了ID和caption username = models.CharField(max_length=32) age = models.IntegerField()
我们要查询所有用户为CEO的用户,我们是不是的根据UserType这张表去查,如果是跨表查询使用“双下划线” + 属性
models.UserInfo.objects.filter(user_type__caption='CEO')
输出看下:
result = models.UserInfo.objects.filter(user_type__caption='CEO') for item in result: print (item.username,item.age,item.user_type.caption)
这里需要注意:当我们通过条件跨表访问的时候使用的是‘双下划线’当我们去取值的时候通过对象'.'去取值!
4、反向:查
我们可以根据下面的命令,取出一个用户组,那么对于这个用户组他有多少个用户呢?
models.UserType.objects.get(id=1)
可以用下面这种方法
models.UserInfo.objects.filter(user_type=models.UserType.objects.get(id=1))
说好的反向呢?首先咱们看下下面的代码:
obj=models.UserType.objects.get(id=1) obj.caption obj.id obj.userinfo_set #理解为一种能力,可以获取所有当前这个用户类型的用户/或者这个用户类型的多个用户 obj.userinfo_set.all() #获取所有用户类型为COO的用户 obj.userinfo_set.filter(username='tim') #获取用户类型为COO的并且用户为tim的用户 ''' 这.userinfo_set.相当于什么?它相当于 models.UserInfo.objects.filter(user_type=obj) '''
只要是filter出来的就有是QuerySet类型对象那么就有如下的方法
4.2、获取某个人是什么用户类型,并且当前用户类型下有多少人?
首先,咱们先看下,咱们UserType表里都有什么属性?不知道?那么咱们先来个错误的!(咱们自己写的和默认的ID就两个:caption, id,)
def user_info(request): print models.UserType.objects.filter(ddd=1) return HttpResponse('OK')
看下报错:
提醒咱们有一个userinfo他是什么?咱们没有创建这个字段,那是什么呢?咱们创建了一个UserInof的表,他是他的小写,对了他就是是Django为咱么自动创建的一个字段!
def user_info(request): #这样就可以查到用户名为:luotianshuai的用户类型是什么了!!! user_type_obj = models.UserType.objects.get(userinfo__username='luotianshuai') #从对象里取出类型名称 print user_type_obj.caption #通过对象的userinfo_set.all().count()取出这个类型下的用户有多少个 print user_type_obj.userinfo_set.all().count() return HttpResponse('OK')
这个用户类型.userinfo_set就代指这个用户类型下的所有的人!
一对多点赞实例
在之前写过一个BSS里有个点赞功能现在当作实例看下!
1、首先创建表
#点赞实例 #用户表 class MyUser(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) def __unicode__(self): return self.username #新闻表 class News(models.Model): #标题 title = models.CharField(max_length=32) #内容 content = models.CharField(max_length=256) def __unicode__(self): return self.title #点赞表 class Favor(models.Model): ''' 这里需要注意,首先这个赞是谁点的,并且谁给某个新闻点的赞! 如果你点过就不能再点了 ''' user_obj = models.ForeignKey('MyUser') news_obj = models.ForeignKey('News') def __unicode__(self): return '用户:%s ---->赞文章:%s' % (self.user_obj.username,self.news_obj.title)
2、配置admin和创建admin用户,并创建几篇文章
from app01 import models admin.site.register(models.MyUser) admin.site.register(models.News) admin.site.register(models.Favor)
文章:
文章主题:Alex和武Sir泰国游回来后双双成人妖
文章内容:走过路过千万不要错过好新闻尽在次篇文章
文章主题:Alex和陌生男人船上野战被XXX
文章主题:此处省略一万字。。。。。。。。。。。。
创建几个用户
用户名有:dali haojie shipeng
3、首先我们要列出文章有多少咱我们的是不是得循环文章
news_list = models.News.objects.all() for item in news_list: print '%s' % (30 * '#') print item.title print item.content
现在我们要查看总共有多少个赞,要知道在新闻表里面没有外键,这个点赞在另一种表中,这个点赞表关联了新闻表(new),所以我们现在要查看的话就是反向操作
Django会自动在被关联表中增加关联了他这个表的‘表明’
咱们去通过news对象去获取链表操作的时候就使用 对象_set去获取
def user_info(request): news_list = models.News.objects.all() for item in news_list: print '%s' % (30 * '#') print '文章标题:',item.title print '文章内容:',item.content ''' 首先要记住一点,新闻表(News)被关联的,Django会为我们在查询条件的时候创建一个favor字段如下: models.News.objects.filter(favor.user_obj=.......) ''' ''' 通过news对象获取链表的时候使用_set他就代指这条新闻所有的'赞数' ''' print '点赞总数为:',item.favor_set.all().count() return HttpResponse('OK')
输出结果:
''' ############################## 文章标题: Alex和武Sir泰国游回来后双双成人妖 文章内容: 走过路过千万不要错过好新闻尽在次篇文章 点赞总数为: 2 ############################## 文章标题: Alex和陌生男人船上野战被XXX 文章内容: 此处省略一万字。。。。。。。。。。。。 点赞总数为: 2 '''
3.1、我赞过的所有的文章
比如我要列出‘大力赞过的所有的文章’
通过News这张表是否可以拿到Favor这张表,当然可以
def user_info(request): news_list = models.News.objects.filter(favor__user_obj__username='dali') #跨表查字段使用 "俩下划线 __" for item in news_list: print '%s' % (30 * '#') print '文章标题:',item.title print '文章内容:',item.content return HttpResponse('OK')
为了测试可以在给大力添加一个赞过的文章在输出看下!
通过这个例子应该记住,在操作一对多的时候需要记住他的特征:
1、创建数据
1.1 obj_id
1.2 obj = model.
2、查找
正向找
filter(跨表的时候,应该是对象__跨表的字段)
获取这个值的时候,拿到了一行数据的时候 line.对象.跨表的字段
反向找
filter(关联这个表的表明) 自动创建和表明相同的对象,通过这个对象__跨表的字段
line.自动创建和表明相同的对象_set.方法
ORM多对多
多对多和一对多没有任何关系,放轻松去理解多对多!
多对多来说Django会为咱们自动创建第三张表
添加Model
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')
现在有个问题,我现在Host表里,和HostAdmin表里添加数据和第三张表有关系吗?
半毛钱关系没有!
给主机表里添加点数据:
def user_info(request): 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) return HttpResponse('OK')
给用户表里添加数据:
这里需要注意下,下面一条在添加数据的时候,虽然下面有对应关系,但是我们创建的时候是没有用的,我们只要把username和email写上就可以了!
host = models.ManyToManyField('Host')
创建用户:
def user_info(request): models.HostAdmin.objects.create(username='alex',email='alex@qq.com') models.HostAdmin.objects.create(username='dali',email='dali@qq.com') models.HostAdmin.objects.create(username='haojie',email='haojie@qq.com') models.HostAdmin.objects.create(username='wusir',email='wusir@qq.com') return HttpResponse('OK')
效果如下:
第三张表自动创建了
2、多对多正向&反向添加数据
我们在关系表里(第3张表)添加!
那么在添加的时候我们就有一个正向添加和反向添加!那么看下那个ManyToManyField字段是放在谁那里了呢?放在HostAdmin那么在HostAdmin操作就是正向操作!
正向添加数据:
def user_info(request): #正向添加数据 #找到用户dali这个 admin_obj = models.HostAdmin.objects.get(username='dali') #找到主机 host_list = models.Host.objects.filter(id__lt=3) #通过找到的dali的对象.add去添加数据 admin_obj.host.add(*host_list) ''' admin_obj 通过大力这个对象.add 去操作的主机, 大力的ID为 2 主机ID为:(1,2) 那就会生成这样的表: #2 1 #2 2 ''' return HttpResponse('OK')
效果:
注:因为第三张表是自动创建的,他有这个对象让咱们去操作第三张表吗?咱们是不是只能通过Host 和 HostAdmin这两张表去操作第三张表!当然咱们也可以自定义第三张表不让他自动创建咱们手动创建(推荐)
反向添加数据:
这里想下,我们反向添加的时候是不是为某一个主机创建一些管理员对应关系啊?
def user_info(request): #反向添加数据 #获取主机 host_obj = models.Host.objects.get(id=3) #获取用户列表 admin_list = models.HostAdmin.objects.filter(id__gt=1) #和一对多一样的道理 host_obj.hostadmin_set.add(*admin_list) #host_obj = 3 管理员ID = 2 3 4 #3 2 #3 3 #3 4 return HttpResponse('OK')
结果如下:
不管是正向添加还是反向添加,都是基于主机表或者用户表的一行数据对应另一张表中的一行或多行数据!
如果正向添加那么,用户ID为2时候添加多台主机的时候比如ID为,1,2,3,4的主机那么表的信息如下:
2 1
2 2
2 3
2 4
反向添加的时候主机ID为2的时候用户为多个用户的时候用户ID为:1 2 3
1 2
2 2
3 2
3、自定义第三张表
首先看,如果咱们自定义第三张表,那么第三张表和另外两张表 {Host表、HostAdmin表} 是什么关系?
为什么呢?当我们学外键的时候,使用外键进行关联,如果需要进行下拉选择的时候需使用,举例来说,用户表中有3个用户,我们能在第三张表中写个用户ID为4吗?
当然不行,所以我们让他下拉选择用户ID!
那么手动创建第三张表,我们来告诉Django来告诉Django不要来自动创建表了!
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告诉Django用那张表做关联 host = models.ManyToManyField(HostInfo , through='HostRelation') class HostRelation(models.Model): host = models.ForeignKey('HostInfo') user = models.ForeignKey('UserMap') ''' 并且这里我们可以添加多个关系,比如在加一个字段 usertype = models.ForeignKey('UserType') 或者增加一个普通字段 status = models.CharField(max_length=32) '''
现在咱们自己创建了第三张表了。现在我们已经会了两种方式创建第三张表了,当我们使用自定义创建的第三张表的时候,在去添加数据的时候!
就不能使用第一种方式对象添加了!
现在我们有第三张表了这个对象了,我们就不需要管另外两张表了,直接添加就行了! 0 0 !
首先先添加主机和用户名
def user_info(request): models.HostInfo.objects.create(hostname='shuai1.test.com', port=80) models.HostInfo.objects.create(hostname='shuai6.test.com', port=80) models.UserMap.objects.create(username='luotianshuai', email='luotianshuai@qq.com') models.UserMap.objects.create(username='shuaige', email='shuaige@qq.com') return HttpResponse('OK')
然后用笨方法去添加一条数据到第三张表
def user_info(request): models.HostRelation.objects.create( user = models.UserMap.objects.get(id=1), host = models.HostInfo.objects.get(id=1) ) return HttpResponse('OK')
效果:
另一种方法
def user_info(request): models.HostRelation.objects.create( user_id = 2, host_id = 2 ) return HttpResponse('OK')
效果:
比较两种方法:首先第一种方法是做了几次数据库查询?两次数据库查询1次数据库插入,那么第二种方法呢0此数据库查询1次数据库插入!第二种方法用在什么地方呢!
想想一下,我们在前端如果两个下拉框的话,用户提交!提交的是不是ID?我们拿到这个ID就直接就添加到数据库就行了使用第二种方法!
4、多对多两种方式对比和查询
假设我们要装系统,我们应该有
管理员表,主机表
我们是不是该有一个张表记录着用户装机的机器和状态 如果是第一种方法可以实现吗?
看看第二张表的实现方式
4.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()
4.3、第二种方法就不需要管,就更简单了
用第二种方法就没有正向和反向这么一说了,直接查即可!
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='luotianshuai') for item in relation_list: #每一个item就是一个关系 print item.user.username print item.host.hostname
通过第二种方式可以把所有的关系都找到,第一种方式可以把所有的关系表都找到吗?
第一种方式只能找到某一个人管理的机器,不能把有的对应关系找到!
select_related作用
场景:
class UserType(models.Model): caption = models.CharField(max_length=32) class UserInfo(models.Model): user_type = models.ForeignKey('UserType') #这个user_type是一个对象,对象里面封装了ID和caption username = models.CharField(max_length=32) age = models.IntegerField()
select_related的作用,他就是用来优化查询的,如果没有他可不可以,可以他优化的并不是很强。他主要优化什么呢:======》优化ForeignKey
def user_info(request): ret = models.UserInfo.objects.all() #咱们看下他执行的什么SQL语句 print ret.query ''' SELECT "app01_userinfo"."id", "app01_userinfo"."user_type_id", "app01_userinfo"."username", "app01_userinfo"."age" FROM "app01_userinfo" '''
咱们看下他加了select_related之后的区别
def user_info(request): ret = models.UserInfo.objects.all().select_related() #咱们看下他执行的什么SQL语句 print ret.query 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")
这样看下,如果使用了select_related之后,在SQL语句中可以看出他会把我们ForiegnKey关联的表自动做一个关联查询!他既获取UserInfo表又获取UserType表!
所以说select_related就是优化查询的!
ORM连表操作梳理
一、一对多创建
1、创建数据
通过对象创建
或者通过对象字段_id创建
2、查找
正向查找
在通过filter的时候跨表使用 双下划线 '__'
在获取值得时候通过.跨表
反向查找
Django自动生成 表名_set
其他操作和正向查找一样
二、多对对
1、自动生成关系表
间接的方式获取关系表,如果是正向的:一行数据的对象.ManyToMany字典就行 反向:一行数据的对象.表名_set
2、自定义关系表(推荐)不管是添加、修改只对关系表操作就行
三、select_related
用于优化查询,一次性将查询的表和ForiegnKey关联的表一次性加载到内存。
ORM的F&Q
1、F
F的作用:用来批量修改数据用的
比如我有一个age这个列,我想让所有的age自+1或者某些人自+1
model.tb.object.all().update(age=F('age')+1)
这个F就代表当前行的age
导入F模块
from django.db.models import F
2、Q
Q的作用:Q是用来做条件查询的。
默认情况下Django的查询只是且操作如下:
models.UserInfo.objects.filter(username='luotianshuai',age='18')
找到用户为:luotianshuai并且age=18的数据
有没有这么一种情况:username=luotianshuai 或 username=wusir 或 username=alex 并且 age=18的需求?原生的查询是不支持的!所以就用到了Q~
这个Django中的Q是非常常用的东西,并且非常有趣!
首先看下他的格式其实不是很难!
第一步: #生成一个搜索对象 search_q = Q() #在生成两个搜索对象 search1 = Q() search2 = Q() 第二步: #标记search1中的搜索条件为 ‘ 或’ 查询 search1.connector = 'OR' #把搜索条件加入到search1中 search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) #标记search2中的搜索条件为 ‘ 或’ 查询 search2.connector = 'OR' #把搜索条件加入到search2中 search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) 第三步: #把多个搜索条件进行合并 search_q.add(search1,'AND') search_q.add(search2,'AND') 第四步: #执行搜索 models.HostInfo.objects.filter(search_q)
实例:
在前端获取搜索条件的时候我把相同类型的搜索写成字典的形式{字段名:[条件结合列表]},这样我在查询的时候直接通过循环字典就可以把搜索条件分为不同的子条件!
function SearchSubmit() { //清空当前列表 $('#table-body').children().remove(); //设置一个空字典 SEARCH_DIC = {}; //找到所有的搜索框体 var search_data = $('.inputs').find("input[is-condition='true']"); //循环找到的内容 $.each(search_data,function (index,data) { //获取搜索的类型 /* 这里需要注意:重点::::: 这里和Django的Q可以进行耦合,在我们定义搜索的类型的时候可以直接写成我们要搜索的的'库中的字段或者条件都可以!!!' 如下: <input is-condition="true" type="text" placeholder="逗号分割多条件" class="form-control no-radius" name="hostname" /> */ var search_name = $(this).attr('name'); //获取搜索的值 var search_value = $(this).val(); if(SEARCH_DIC.hasOwnProperty(search_name)){//判断是否有这个KEY SEARCH_DIC[search_name].push(search_value) }else{ SEARCH_DIC[search_name] = [search_value] } } );//ajax请求结束 $.get("{% url 'search_info' %}",{'search_list':JSON.stringify(SEARCH_DIC)},function(callback){ $('#table-body').append(callback) });//搜索按钮结束
这里需要注意:
重点:
在前端我们定义input的name属性的时候,我们可以直接定义为数据库中的“字段名”,并且在Django的Q中支持跨表操作“双下划线”,所以我们在定义name的时候可以直接定义双下划线操作
search1.children.append(('字段名'__'跨表字段名','跨表字段内容'))
@login_auth def search_info(request): #获取用户请求的数据 user_post = json.loads(request.GET['search_list']) print user_post #生成搜索对象 Serach_Q = Q() #循环字典并生成搜索条件集合 for k,v in user_post.items(): #生成一个搜索结合 q = Q() #生命集合中的搜索条件为'或'条件 q.connector = 'OR' #循环字典中的value,value是前端传过来的条件集合 for i in v: #在搜索条件集合中增加条件,条件为元组形式,k为字典中的key! key是字段名或者跨表字段名或者支持_gt等 #i为字典中的vlaue中的元素,为条件 # q.children.append((k,i)) #没循环一次后后,吧他加入到总的搜索条件中 Serach_Q.add(q,'AND') #使用总的搜索条件进行查询 data = models.HostInfo.objects.filter(Serach_Q) #拼接字符串并返回 html = [] for i in data: html.append( "<tr>"+ "<td>" + "<input type='checkbox' >"+ "</td>" + "<td name='host_id'>" + '%s' %i.id + "</td>" + "<td name='host_name' edit='true'>" + i.hostname + "</td>"+ "<td name='host_ip' edit='true'>" + i.hostip + "</td>"+ "<td name='host_port' edit='true'>" + '%s' %i.hostport + "</td>"+ "<td name='host_business' edit='true' edit-type='select' global-key='BUSINESS' select-val='" + '%s' %i.hostbusiness_id + "'>" + i.hostbusiness.hostbusiness + "</td>"+ "<td name='host_status' edit='true' edit-type='select' global-key='STATUS' select-val='" + '%s' %i.hoststatus_id + "'>" + i.hoststatus.hoststatus + "</td>"+ "</tr>" ) html = mark_safe("".join(html)) return HttpResponse(html)
测试:
![](https://images.cnblogs.com/cnblogs_com/luotianshuai/1009142/o_o_Warning.png)