Django基础——Model篇(三)

一 Django ORM中的概念

ORM —— 关系对象映射,是Object Relational Mapping的简写,是用来简化数据库操作的框架

Django ORM遵循Code Frist原则,即根据代码中定义的类来自动生成数据库表,对于ORM框架:
  (1)自定义的类表示待创建数据库的表
  (2)根据自定义类创建的对象obj表示数据库表中的一行数据
  (3)obj.字段1、obj.字段2、...、obj.字段n表示每一行数据中相应字段的值

ORM中的一对多、一对一以及多对多的概念及应用场景,具体参见前期博客。
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()

   对于上面两张表,UserInfo表是关联UserType表的,即一对多字段(外键)在UserInfo表中。那么根据UserInfo表去操作数据库,就表示正向;反之根据UserType表去操作数据库,就表示反向。

二 ORM中一对多的操作

  models.py中的表结构

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserInfo(models.Model):
    # 此处user_type是models.ForeignKey类封装了UserType类生成的的对象
    # 即user_type是包含了id和caption字段的对象
    user_type = models.ForeignKey(UserType)
    username = models.CharField(max_length=32)
    age = models.IntegerField()

   views.py中生成三种用户类型的代码

def user_type(request):
    # 添加user_type表的数据
    # dic = {'caption': 'CEO'}  对应id为1
    # dic = {'caption': 'CTO'}  对应id为2
    # dic = {'caption': 'COO'}  对应id为3
    # models.UserType.objects.create(**dic)
    print models.UserType.objects.all()
   return HttpResponse('ok')

  生成的表结构如下图所示:

  1 UserInfo表添加数据的两种方法

  (1)数据库级别

    上表是UserInfo数据库表结构,外键user_type字段默认会加上"_id"进行存储,那么从数据库级别添加数据的代码如下:

def user_info(request):
    # 一对多:外键
    # 一对多中添加user_info表数据:数据库级别

    # 第1种方法(数据库级别):按外键在数据库中的存储方式
    dic = {'username':'xx','age':18,'user_type_id':1}
    models.UserInfo.objects.create(**dic)
    result = models.UserInfo.objects.all()
    for item in result:
        print item.username,item.age,item.user_type.caption

  (2)对象级别

def user_info(request):
    # 一对多:外键
    # 一对多中添加user_info表数据:对象级别

    # 第2种方法(对象级别):UserInfo表中的user_type字段对应models中的UserType对象

    #先获取用户类型的对象
    type = models.UserType.objects.filter(id=2)
    #添加时直接添加对象
    models.UserInfo.objects.create(username='xxx',age=28,user_type=type)
    
    #或写成下面形式,省略中间步骤
    dic = {'username':'xxx','age':28,'user_type':models.UserType.objects.get(id=2)}
    models.UserInfo.objects.create(**dic)

 get方法是从数据库中取一个匹配的结果,返回一个对象,如果不存在,则会报错
 filter方法是从数据库中取匹配的结果,返回一个对象列表,如果不存在,则会返回[]

  2 查找数据

  (1)正向查

def user_info(request):
    # 查找表中的所有数据
    result = models.UserInfo.objects.all()

    # 查找指定数据
    # 正向查找:查询当前表中数据
    result = models.UserInfo.objects.filter(username='xx')

    # 跨表查询(使用双线划线"__"),user_type是UserType类的对象,所以这里是查询UserType表中的caption字段,即是跨表查询
    result = models.UserInfo.objects.filter(user_type__caption='CTO')
    for item in result:
      # 与跨表查询数据不同的是,当查询完数据再取数据时,使用的是".",这里要注意
      print item.username,item.age,item.user_type.caption
    
    return HttpResponse('ok')

  (2)反向查

def user_info(request):
    # 反向查找
    # 搜索条件:id/caption/userinfo(该字段是Django在UserType表中自动生成的,关联到UserInfo表,这个字段很重要,为反向查找提供了可能)
    line = models.UserType.objects.get(id=1)
    print line.id  #输出"1"
    print line.caption  #输出"CEO"
    #输出"[<UserInfo: UserInfo object>]",表示当前用户类型UserType(id=1)对应的用户对象
    '''
    注意下面的line.userinfo_set等价于models.UserInfo.objects.filter(user_type=line)
    好好理解这种等价关系
    '''
    print line.userinfo_set.all()
    print line.userinfo_set.filter(username='xx')
    for item in line.userinfo_set.all():
       # 输出"xx 18 UserType object"
        print item.username, item.age, item.user_type
       # 输出"xx 18 CEO",注意user_type是UserType的对象
        print item.username, item.age, item.user_type.caption

    # 1、查找某个人是哪种用户类型;
     user_type_obj = models.UserType.objects.get(userinfo__username='xx')
    print user_type_obj.caption

# 2、查找指定用户类型下有哪些用户
# 下面两种方法是等价的,注意在Django中,UserType表默认会增加一列表示与UserInfo表的关系 print user_type_obj.userinfo_set.count() # 下面方法是从UserInfo表出发查找的 print models.UserInfo.objects.filter(user_type=user_type_obj).count() return HttpResponse('ok')

  3 新闻点赞实例

  models.py中的表结构

#用户表
class
MyUser(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) def __unicode__(self): return self.username
#新闻表
class New(models.Model): title = models.CharField(max_length=32) content = models.CharField(max_length=32) def __unicode__(self): return self.title
#点赞表,这里需要注意:1 哪个用户点的赞;2 给哪个新闻点的赞;3 不能重复点赞
class Favor(models.Model): # 外键关联MyUser user_obj = models.ForeignKey(MyUser) # 外键关联New new_obj = models.ForeignKey(New) def __unicode__(self): return "%s -> %s" %(self.user_obj.username, self.new_obj.title)

   配置admin以及创建admin用户,并创建几篇文章

from django.contrib import admin
from app01 import models

# Register your models here.
admin.site.register(models.MyUser)
admin.site.register(models.New)
admin.site.register(models.Favor)
admin.site.register(models.HostAdmin)
admin.site.register(models.Host)

                                             

 views.py中的相关代码,主要完成两个功能:1 查询某人点赞过的文章; 2 查询某篇文章被点赞过的次数

# 新闻点赞实例(相关表数据已在Django的admin中添加完成)
def favor_new(request):
    # 获取所有的新闻列表
    # new_list = models.New.objects.all()

    # 获取alex点赞过的新闻列表,注意这里的反向查询和跨表查询
    new_list = models.New.objects.filter(favor__user_obj__username='alex')
    for item in new_list:
       print '====================='
        print item.title
       print item.content
       # 打印每条新闻的点赞次数
        print item.favor_set.all().count()
    return HttpResponse('ok')

三 ORM中多对多的操作

    Django中的多对多与一对多没有联系,它的实际操作比一对多简单,Django默认会为多对多生成第3张表。下面是对应数据库表的关系:

   models.py中的数据库结构

#============多对多================
#下面建立多对多关系的字段,写了其中任何一个即可,它们的区别只在于哪个是"正向查找",哪个是"反向查找",即查找时的参照类有区别。
class Host(models.Model):
    hostname = models.CharField(max_length=32)
    port = models.IntegerField()
    # admin = models.ManyToManyField(HostAdmin)

class HostAdmin(models.Model):
    username = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
    host = models.ManyToManyField(Host)

   views.py中添加数据

def many_to_many(request):
   #创建主机表
    models.Host.objects.create(hostname='c1',port=80)
    models.Host.objects.create(hostname='c2',port=80)
    models.Host.objects.create(hostname='c3',port=80)
    
   #创建用户表,注意这里只需要添加username和email字段,host字段不需要指定
    models.HostAdmin.objects.create(username='alex',email='alex@qq.com')
    models.HostAdmin.objects.create(username='eric',email='eric@qq.com')
    models.HostAdmin.objects.create(username='pony',email='pony@qq.com')
    models.HostAdmin.objects.create(username='rain',email='rain@qq.com')
  return HttpResponse('ok')

    生成的三张表如下:

   
  1 正向添加数据(从包含多对多字段的表操作数据库)

def many_to_many(request):
   # 正向添加
    # 目的:给alex分配两个主机的管理权限
    # 1、获取指定的HostAdmin对象
    admin_obj = models.HostAdmin.objects.get(username='alex')
    # 2、获取指定的Host,条件为id小于3
    host_list = models.Host.objects.filter(id__lt=3)
    # 3、将用户alex和指定的两个主机添加到对应的第3张表中,注意host_list是列表,所有加*号
    admin_obj.host.add(*host_list)
  return HttpResponse('ok')

  Django自动生成的第3张表如下所示:

  2 反向添加数据(从不包含多对多字段的表操作数据库)

def many_to_many(request):
    # 反向添加
    # 1、获取主机表
    # host_obj = models.Host.objects.get(id=3)
    # 2、获取用户表
    # admin_list = models.HostAdmin.objects.filter(id__gt=1)
    # 3、添加数据,注意hostadmin_set,这是在Host表中,Django自动生成的关联HostAdmin表的字段
    # host_obj.hostadmin_set.add(*admin_list)
  return HttpResponse('ok')
无论是正向添加还是反向添加,其本质都是基于主机表或用户表的一行数据(对象)对应另一张表中的一行或多行数据(对象)。

正向添加,对于ID=2的用户,添加多台主机(比如主机ID为1,2,3,4),那么Django自动生成的第3张表信息如下:
# 用户ID  主机ID
    2       1
    2       2
    2       3
    2       4

反向添加,对于ID=2的主机,添加多个用户(比如用户ID为1,2,3),那么Django自动生成的第3张表信息如下:
# 用户ID  主机ID
    1       2
    2       2
    3       2

    3 自定义第3张表

    对于Django自动生成的第3张表,在使用的过程中不是很灵活,也不能增加字段。对于这种情况,Django允许自定义生成第3张表,不需要使用默认的表结构。分析下三张表之间的关系:

     models.py中自定义第3张表

# 主机表
class Host(models.Model):
    hostname = models.CharField(max_length=32)
    port = models.IntegerField()
    # admin = models.ManyToManyField(HostAdmin)

# 用户表 class HostAdmin(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32) host = models.ManyToManyField(Host1,through='HostRelation') # 自定义的第3张表 class HostRelation(models.Model): # 外键关系 c1 = models.ForeignKey(Host) c2 = models.ForeignKey(HostAdmin) # 还可以自定义字段

   自定义第3张表中添加数据(在自定义的第3张表中,我们在添加数据时其实跟其它两张表没有关系了,因为自定义时我们定义了数据库类)

def many_to_many(request):
    # 在自定义多对多的第3张表中添加数据
# 第1种方法:对象级别
models.HostRelation.objects.create( c1=models.Host.objects.get(id=1), c2=models.HostAdmin.objects.get(id=2) )
# 第2种方法:数据库级别
# models.HostRelation.objects.create( # c1_id = 2, # c2_id = 1 # ) return HttpResponse('ok')

  上述两种方法性能比较:第1种方法中两次数据库查询,1次数据库插入;第2种方法中0次数据库查询,1次数据库插入。

  4 查询数据库

    Django默认生成第3张表的查询/自定义第3张表的查询

def many_to_many(request):
    # 第1种 Django默认生成第3张表的查询
    # 正向查
    admin_obj = models.HostAdmin.objects.all(id=1)
    for item in admin_obj:
        item.host.all()

    # 反向查
    host_obj = models.Host.objects.get(id=1)
    host_obj.hostadmin_set.all()

    # 第2种 自定义第3张表的查询
    #relation_list = models.HostRelation.objects.all()
    relation_list = models.HostRelation.objects.filter(c2__username='alex')
    for item in relation_list:
        print item.c1.hostname
        print item.c2.username

    return HttpResponse('ok')

  5 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优化查询 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") ''' return HttpResponse('ok')

    如果是普通查询,从执行的SQL语句可以看出,它只会获取UserInfo表中的数据;如果使用select_related,从执行的SQL语句可以看出它会把ForiegnKey关联的表自动做一次关联查询。它既获取UserInfo表的数据,同时也获取UserType表的数据。

四 ORM中的F&Q

 1 F — 批量修改或更新数据

   假设数据库中有1个age字段,我想让所有age字段自加1或某些人自加1,那么可以利用ORM中F:

#导入F模块
from django.db.models import F
#F指代当前行的age字段
model.tb.object.all().update(age=F('age')+1)

 2 Q — 条件查询,非常有用

   默认情况下Django的查询

#查找username='alex'且age=18的对象,条件不是很灵活
models.UserInfo.objects.filter(username='alex',age=18)

    假如要查找username='alex'或username='eric'或username='rain',并且age=18的对象,那么默认的查询实现起来比较麻烦,为了解决这种情况,Django为我们提供了Q操作,下面是操作步骤:

#导入Q模块
from django.db.models import Q

#
第1步: #生成一个搜索对象 search_q = Q() #再生成两个搜索对象 search1 = Q() search2 = Q() #第2步: #标记search1中的搜索条件的连接符为'OR'(或条件) search1.connector = 'OR' #把搜索条件加入到search1中,注意都是元组的形式 search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) search1.children.append(('字段名','字段内容')) #标记search2中的搜索条件的连接符为'OR'(或条件) search2.connector = 'OR' #把搜索条件加入到search2中 search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) search2.children.append(('字段名','字段内容')) #第3步: #把多个搜索条件进行合并,以'AND'的形式(与条件) search_q.add(search1,'AND') search_q.add(search2,'AND') #第4步: #执行搜索,还是利用filter models.HostInfo.objects.filter(search_q)

五 Django ORM总结

 1、一对多
 (1)添加数据
    通过对象级别
    通过数据库级别(数据在数据库中的存储方式,对象字段_id)
 (2)查找
    正向查找
     通过filter跨表,对象__跨表的字段
     获取值,obj.对象.跨表的字段
    反向查找
          通过filter跨表,Django自动生成与表名相同的对象__跨表的字段
     获取值,obj.Django自动生成与表名相同的对象_set.filter()/all()[0:]
 2、多对多
 (1)Django自动生成关系表
    正向:一行数据的对象.ManyToMany字段
        反向:一行数据的对象.表名_set
 (2)自定义关系表(推荐)
       不管是添加、查询,只需要操作自定义关系表
 3、select_related
  优化查询,一次性将查询的表和ForiegnKey关联的表加载到内存。

 4、Django中的F&Q操作

参考资料:

   http://www.cnblogs.com/luotianshuai/p/5403513.html

原文地址:https://www.cnblogs.com/maociping/p/5350278.html