前言:多对多的关系简单来说就是两张表里的数据,每一行都可以对应另外一张表里的多行数据。采用我们在一对多时使用的例子。加入我们放开限制,主机表Host表的数据都有可以有多个管理员。同时反过来HostAdmin表里的每个管理员也同时管理多个主机。
多对多来说Django会为咱们自动创建第三张表。
class Host(models.Model):
hostname = models.CharField(max_length=32)
port = models.IntegerField()
class HostAdmin(models.Model):
username = models.CharField(max_length=32)
email = models.CharField(max_length=32)
host = models.ManyToManyField(Host)
现在有个问题,我现在Host表里,和HostAdmin表里添加数据和第三张表有关系吗?
半毛钱关系没有!
正向操作
准备数据
给主机表里添加数据:
from api.models import *
Host.objects.create(hostname='host1.test.com',port=80)
Host.objects.create(hostname='host2.test.com',port=80)
Host.objects.create(hostname='host3.test.com',port=80)
Host.objects.create(hostname='host4.test.com',port=80)
给用户表里添加数据:
HostAdmin.objects.create(username='alex',email='[email protected]')
HostAdmin.objects.create(username='dali',email='[email protected]')
HostAdmin.objects.create(username='haojie',email='[email protected]')
HostAdmin.objects.create(username='wusir',email='[email protected]')
主机列表:
id hostname port
1 host1.test.com 80
2 host2.test.com 80
3 host3.test.com 80
4 host4.test.com 80
用户列表:
id username email
1 alex [email protected]
2 dali [email protected]
3 haojie [email protected]
4 wusir [email protected]
第三张表自动创建。
正向添加
>>> admin_obj = HostAdmin.objects.get(username='dali')
SELECT "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostadmin" WHERE "api_hostadmin"."username" = 'dali'
>>> host_list = Host.objects.filter(id__lt=3)
>>> admin_obj.host.add(*host_list)
SELECT "api_host"."id", "api_host"."hostname", "api_host"."port" FROM "api_host" WHERE "api_host"."id" < 3 #先查找 id>3 的主机
SELECT "api_hostadmin_host"."host_id" FROM "api_hostadmin_host" WHERE ("api_hostadmin_host"."hostadmin_id" = 2 AND "api_hostadmin_host"."host_id" IN (1, 2))
INSERT INTO "api_hostadmin_host" ("hostadmin_id", "host_id") SELECT 2, 1 UNION ALL SELECT 2, 2
结果,看第三张表
id hostadmin_id host_id
1 2 1
2 2 2
正向查找
正向查找的操作其实和一对多的是一样的,我们查下dali 有多少主机
>>> admin_obj = HostAdmin.objects.get(username='dali')
>>> host_obj = admin_obj.host.all()
>>> for host_line in host_obj:
... print host_line.hostname
...
host1.test.com
host2.test.com
反向操作
反向添加
和正向操作相反,在通过操作Host表给主机添加添加管理员的操作就是反向添加。与一对多操作一样。反向操作获取数据的时候我们需要用到xxx_set的方法跨表获得关联表的数据。
之前是给用户添加主机,现在我们给主机添加用户 alex
>>> host_obj = Host.objects.get(id=3)
SELECT "api_host"."id", "api_host"."hostname", "api_host"."port" FROM "api_host" WHERE "api_host"."id" = 3
>>> admin_list = HostAdmin.objects.filter(username='alex')
>>> host_obj.hostadmin_set.add(*admin_list)
SELECT "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostadmin" WHERE "api_hostadmin"."username" = 'alex'
SELECT "api_hostadmin_host"."hostadmin_id" FROM "api_hostadmin_host" WHERE ("api_hostadmin_host"."hostadmin_id" IN (1) AND "api_hostadmin_host"."host_id" = 3)
INSERT INTO "api_hostadmin_host" ("hostadmin_id", "host_id") SELECT 1, 3
反向查找
通过主机ip查找管理员的操作就是反向查找了。反向查找的方式也和一对多反向查找一样
>>> host_obj = Host.objects.get(hostname='host1.test.com')
SELECT "api_host"."id", "api_host"."hostname", "api_host"."port" FROM "api_host" WHERE "api_host"."hostname" = 'host1.test.com'
>>> admin_obj = host_obj.hostadmin_set.all()
>>> for admin_line in admin_obj:
... print admin_line.username
...
SELECT "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostadmin" INNER JOIN "api_hostadmin_host" ON ("api_hostadmin"."id" = "api_hostadmin_host"."hostadmin_id") WHERE "api_hostadmin_host"."host_id" = 1
dali # 查询的到的用户名
自定义多对多关系表
上面讲的正向查询反向查询等操作使用的都是django自动给我们生成的第三方关系表。虽然自动生成很方便,但是如果我们想自定义表名或者给这个表里再添加其他字段等操作就不行了。
鉴于这些需求我可以在创建表的通过through参数来指定关系表的名称,models.py代码如下
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)
#through告诉Django用那张表做关联
host = models.ManyToManyField(Host , through='HostRelation')
class HostRelation(models.Model):
host = models.ForeignKey('Host')
hostadmin = models.ForeignKey('HostAdmin')
添加数据
让我们轻轻插入
通过数据行对象的方式添加
>>> admin_obj = HostAdmin.objects.get(username='alex')
SELECT "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostadmin" WHERE "api_hostadmin"."username" = 'alex'
>>> host_obj = Host.objects.filter(id__gt=2)
>>> for host_line in host_obj:
... HostRelation.objects.create(hostadmin=admin_obj, host=host_line)
...
SELECT "api_host"."id", "api_host"."hostname", "api_host"."port" FROM "api_host" WHERE "api_host"."id" > 2
INSERT INTO "api_hostrelation" ("host_id", "hostadmin_id") VALUES (3, 1)
INSERT INTO "api_hostrelation" ("host_id", "hostadmin_id") VALUES (4, 1)
查看第三张表
id host_id hostadmin_id
1 3 1
2 4 1
在数据库层面写入添加数据
我们知道创建外键的时候django会自动在我们定义的字段名后面追加一个_id作为最后的字段名。如果我们现在要给用户添加对某台主机的管理权限。我们只需要知道Host表中主机对应的行id和HostAdmin表中用户对应的行id号。然后按照HostRelation表里指定的字段以字典形式把字段的值传进去就可以完成添加了。
>>> host_dict = {'host_id':2, 'hostadmin_id':2}
>>> HostRelation.objects.create(**host_dict)
INSERT INTO "api_hostrelation" ("host_id", "hostadmin_id") VALUES (2, 2)
这种方法对数据的操作次数最小。尤其是适用于根据models里的值在前段生成下拉菜单的情况。通过下拉菜单可以直接获知用户选择的那一行对应的id值,传入后端马上就可以根据id进行操作了。
查询数据
我们可以直接在操作HostRelation表对关联的表进行正向查询,大幅降低多对多查询的复杂程度。
查看被alex管理的主机
>>> host_obj=HostRelation.objects.filter(hostadmin__username='alex').select_related()
>>> for host_line in host_obj:
... print host_line.host.hostname
...
SELECT "api_hostrelation"."id", "api_hostrelation"."host_id", "api_hostrelation"."hostadmin_id", "api_host"."id", "api_host"."hostname", "api_host"."port", "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostrelation" INNER JOIN "api_hostadmin" ON ("api_hostrelation"."hostadmin_id" = "api_hostadmin"."id") INNER JOIN "api_host" ON ("api_hostrelation"."host_id" = "api_host"."id") WHERE "api_hostadmin"."username" = 'alex'
host3.test.com
host4.test.com
反过来查询主机host3.test.com的管理员也是类似的方法
>>> admin_obj=HostRelation.objects.filter(host__hostname='host3.test.com').select_related()
>>> for admin_line in admin_obj:
... print admin_line.hostadmin.username
...
SELECT "api_hostrelation"."id", "api_hostrelation"."host_id", "api_hostrelation"."hostadmin_id", "api_host"."id", "api_host"."hostname", "api_host"."port", "api_hostadmin"."id", "api_hostadmin"."username", "api_hostadmin"."email" FROM "api_hostrelation" INNER JOIN "api_host" ON ("api_hostrelation"."host_id" = "api_host"."id") INNER JOIN "api_hostadmin" ON ("api_hostrelation"."hostadmin_id" = "api_hostadmin"."id") WHERE "api_host"."hostname" = 'host3.test.com'
alex
通过HostRelation表,怎么查询都是正向查询。非常方便,所以一般处理多对多关系的时候建议自己创建关系表
总结:
1、多对多操作的关联表的时候,必须要像一对多时候一样。在主要操作的表里选中唯一的一行数据来关联另外一张表的多行数据。简单来说举例来说 要先用get找到唯一的管理员的一行对象,再关联多个主机对象。不能一次提取多个管理员关联多个主机。
2、添加操作的add()方法只适用于django自动创建多对多关系表的情况。如果是自建的关系表是用不了add()方法的
3、表里定义的多对多字段是在查看表结构的时候是显示不出来的。
本文参考:霹雳豆包, fengzao's path