多对多的关系简单来说就是两张表里的数据,每一行都可以对应另外一张表里的多行数据。采用我们在一对多时使用的例子。加入我们放开限制,主机表Host表的数据都有可以有多个管理员。同时反过来HostAdmin表里的每个管理员也同时管理多个主机。代码如下:

# coding:utf-8
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Host(models.Model):
    ip = models.CharField(max_length=32)
    port = models.IntegerField()
# 在管理员表里配置多对多字段,建立管理员表与主机表的关联
class HostAdmin(models.Model):
    username = models.CharField(max_length=32)
    # 创建多对多字段
    host = models.ManyToManyField(Host)

创建之后填充测试数据


Host表记录主机和端口信息

Django进阶(二) 数据库的ORM操作之多对多_第1张图片

HostAdmin表中被定义为多对多关系的host字段不会在表里被显示出来

Django进阶(二) 数据库的ORM操作之多对多_第2张图片



由于多对多的关系只通过2张表不能完全的展示出来,所以django自动创建一张多对多关系表。主机和管理员表所有的关联信息都被记录在这张表里。这张表的名称就是被关联的两个表拼接而成。

wKioL1cXkhbyrnnpAAAj3jZRFWQ159.png



下面我们来给关系表里添加数据。


正向添加

因为我们的多对多关系字段是在HostAdmin表里定义的,所以我们通过HostAdmin表来添加关系就是正向添加操作。简单来说本例就是我们给管理员添加被管理主机的操作就是正向的。多对多关联的时候使用了add()方法将host字段与要关联表里一组对象进行关联。

views.py实现的代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 查出host表里对应的全部的数据行的对象
    host_obj = models.Host.objects.all()
    # 获取HostAdmin表里唯一的一行tom用户的数据对象
    admin_obj = models.HostAdmin.objects.get(username='tom')
    # 通过跨表操作将HostAdmin里的host字段与Host表里查出的host_obj字段进行关联
    # 因为host_obj里包含了多行数据对象,所以用(*host_obj)表示多个对象
    admin_obj.host.add(*host_obj)
    return HttpResponse('ok')

从截图中我们知道tom用户对应的id号为2,刚刚我们给tom用户添加对应了所有的主机。现在到自动生成的关系表里看到了预期的结果。

Django进阶(二) 数据库的ORM操作之多对多_第3张图片


正向查找

既然正向添加已经完成了,那么正向查找的操作其实和一对多的是一样的。代码如下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 获取用户tom对应的全部那一行的对象
    admin_obj = models.HostAdmin.objects.get(username='tom')
    # 通过这一行的host字段跨表到Host表里找到对应的全部数据行的对象
    host_obj = admin_obj.host.all()
    # 遍历Host表里获取的对象
    for host_line in host_obj:
        # 打印每一行对象里的ip字段
        print host_line.ip
    return HttpResponse('ok')

这样我们就通过HostAdmin表正向查到了Host表里的数据



反向添加

和正向操作相反,在通过操作Host表给主机添加添加管理员的操作就是反向添加。与一对多操作一样。反向操作获取数据的时候我们需要用到xxx_set的方法跨表获得关联表的数据。现在我们给ip4.4.4.4的主机添加jack为管理员,代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 获取唯一的一行数据对象
    host_obj = models.Host.objects.get(ip='4.4.4.4')
    # 获取jack所在行的对象
    # id__lt=3 等价于 id<3
    admin_obj = models.HostAdmin.objects.filter(id__lt=2)
    # 使用Host表里隐藏字段hostadmin
    # 使用hostadmin_set反向关联HostAdmin表
    host_obj.hostadmin_set.add(*admin_obj)
    return HttpResponse('ok')

通过查看关系表,我们已经把jack所在行的id与4.4.4.4所在行的id关联到了一起


反向查找

通过主机ip查找管理员的操作就是反向查找了。反向查找的方式也和一对多反向查找一样

代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 获取唯一的一行数据对象
    host_obj = models.Host.objects.get(ip='4.4.4.4')
    # 通过Host表里的隐藏字段+_set获取HostAdmin表里对应的对象集合
    admin_obj = host_obj.hostadmin_set.all()
    # 遍历每个对象
    for admin_line in admin_obj:
        # 打印对象里的用户名
        print admin_line.username
    return HttpResponse('ok')

查询的到的用户名如下



总结:

1、多对多操作的关联表的时候,必须要像一对多时候一样。在主要操作的表里选中唯一的一行数据来关联另外一张表的多行数据。简单来说举例来说 要先用get找到唯一的管理员的一行对象,再关联多个主机对象。不能一次提取多个管理员关联多个主机。

2、添加操作的add()方法只适用于django自动创建多对多关系表的情况。如果是自建的关系表是用不了add()方法的

3、表里定义的多对多字段是在查看表结构的时候是显示不出来的。



###########################################################################################






自定义多对多关系表

上面讲的正向查询反向查询等操作使用的都是django自动给我们生成的第三方关系表。虽然自动生成很方便,但是如果我们想自定义表名或者给这个表里再添加其他字段等操作就不行了。鉴于这些需求我可以在创建表的通过through参数来指定关系表的名称,models.py代码如下

# coding:utf-8
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Host(models.Model):
    ip = models.CharField(max_length=32)
    port = models.IntegerField()
# 在管理员表里配置多对多字段,建立管理员表与主机表的关联
class HostAdmin(models.Model):
    username = models.CharField(max_length=32)
    # 通过through方法指定多对多关系表
    host = models.ManyToManyField(Host, through='HostRelation')
class HostRelation(models.Model):
    # 通过外键关联主机表和管理员表
    hostadmin = models.ForeignKey(HostAdmin)
    host = models.ForeignKey(Host)

手动创建的HostRelation表的结构和自动创建的结构是一样的

wKiom1cXrePQ8SOrAAAoT0vGLdU791.png


现在我们来给关系表添加数据。添加数据有两种方式(因为是手动创建的关系表,所以上文用到的add()方法添加数据不行了)


第一种  通过数据行对象的方式添加

这种方式是在python层面以对象object的方式向数据库中添加数据,假设我们现在要给用户jack分配Host表中id号>2的主机管理权限。那么代码如下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 获取HostAdmin表里jack的唯一数据对象
    admin_obj = models.HostAdmin.objects.get(username='jack')
    # 获取Host表里id>2的对象集合
    host_obj = models.Host.objects.filter(id__gt=2)
    # 遍历Host表里获取的每一行对象
    for host_line in host_obj:
        # 因为每一个外键的值就是对应的一行数据
        #  所以就以对象的形式将一行对象赋值给关系表字段的值
        models.HostRelation.objects.create(hostadmin=admin_obj, host=host_line)
    return HttpResponse('ok')

执行结果可以实现我们要求,这种方法不太好理解。而且这种方法对数据库进行4次操作,两次查询两次写入。

wKioL1cXsl6ADewcAAAjJpnzAZQ529.png



第二种方法 在数据库层面写入添加数据。(这种方法比较直观)

我们知道创建外键的时候django会自动在我们定义的字段名后面追加一个_id作为最后的字段名。如果我们现在要给用户tom添加对主机1.1.1.1的管理权限。我们只需要知道Host表中主机对应的行id和HostAdmin表中用户对应的行id号。然后按照HostRelation表里指定的字段以字典形式把字段的值传进去就可以完成添加了。代码如下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    insert_dic={
        # 每一行的id号必须是整数,不是字符串
        'hostadmin_id': 2,
        'host_id': 1
    }
    # 通过**方法将字典作为参数传入
    models.HostRelation.objects.create(**insert_dic)
    return HttpResponse('ok')

运行结果如下

Django进阶(二) 数据库的ORM操作之多对多_第4张图片


这种方法对数据的操作次数最小。尤其是适用于根据models里的值在前段生成下拉菜单的情况。通过下拉菜单可以直接获知用户选择的那一行对应的id值,传入后端马上就可以根据id进行操作了。


至于数据查询,我们可以直接在操作HostRelation表对关联的表进行正向查询,大幅降低多对多查询的复杂程度。例如查看被jack管理的主机ip,代码如下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    # 通过双下滑线正向跨表查询HostAdmin表里的username
    host_obj=models.HostRelation.objects.filter(hostadmin__username='jack').select_related()
    for host_line in host_obj:
        # 通过'.'连接符跨表获取Host表里的数据
        print host_line.host.ip
    return HttpResponse('ok')


反过来查询主机1.1.1.1的管理员也是类似的方法

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def m2m(request):
    admin_obj=models.HostRelation.objects.filter(host__ip='1.1.1.1').select_related()
    for admin_line in admin_obj:
        print admin_line.hostadmin.username
    return HttpResponse('ok')

wKiom1cXuTWiwXjkAAAFjBsmpEU775.png


通过HostRelation表,怎么查询都是正向查询。非常方便,所以一般处理多对多关系的时候建议自己创建关系表。