Django2.2.12-ORM 关联关系操作
一、ORM 多表的用途
之前保存了服务器的基础信息,其实服务器的信息应该还包括这台服务器的内存信息、硬盘信息、网卡信息等。
大家可以思考一下,一台服务器只允许有一条内存条,一块硬盘,一块网卡吗?认为是的扣 1, 不是的扣 2。
假如你自己组装过台式电脑,或者给自己的笔记本添加内存条的话,就会明白,普通的电脑都可以通过增加内存条来获取更高的性能,服务器当然可以拥有多条内存,多块硬盘,多块网卡了,并且部分物理服务器都是支持热插拔的。热插拔就是可以在服务器不断电运行中添加内存、硬盘等硬件,并且能够识别。
显然这些信息也是需要保存起来的。那就拿内存来说,它和服务器的关系就是属于多对一了,多条内存属于一台服务器。
之前学习 MySQL 的时候都知道,多对一或者说是一对多的关系可以使用外键实现,并且对外键的值进行约束。
这个外键在 Django 的 ORM 中的表现就是,在多的那一方表里添加一个字段,这个字段对应的属性就是 models.ForeignKey("一的那一方模型类名称")
下面接上次Server
表的实例,设计一下内存表。
根据之前获取服务器信息的程序,内存信息的数据如下:
{
"base": {
"host_name": "iZ2zecj761el8gvy7p9y2kZ",
"kernel": "3.10.0-957.21.3.el7.x86_64",
"os": "CentOS Linux release 7.6.1810 (Core) ",
"manufacturer": "Alibaba Cloud",
"pod_name": "Alibaba Cloud ECS",
"sn": "0f7e3d86-7742-4612-9f93-e3a9e4754157"
},
"cpu": {
"cpu_name": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz",
"cpu_num": 1,
"cpu_cores_each": 1
},
"mem": [
{
"capacity": "4096 MB",
"slot": "DIMM 0",
"model": "RAM",
"speed": "Unknown",
"manufacturer": "Alibaba Cloud",
"sn": "Not Specified"
}
]
}
安照示例数据,内存表的基本字段应该有:
id
、capacity
、slot
、mdel
、spedd
、manufacturer
、sn
另外,为了表现内存表和服务器表的多对一的关系,还需要增加一个属性为 models.ForeignKey
的外键字段,就起名字为 server
即可;一般情况下我们会记录表中每条数据的创建时间和更新时间,这就需要用的之前学的 models.DateTimeField
。
好基于以上考虑,我们设计的内存模型类如下:
class Memory(models.Model):
server = models.ForeignKey( # 外键字段
"Server", # 服务器表的模型类名称
verbose_name="服务器", # 后台管理页面显示的值
related_name="memory", # 用于反向查询是使用的名字,反向查找就是查询某一台服务器的所有内存。
on_delete=models.CASCADE # 级联删除属性,意思就是
)
capacity = models.CharField(
verbose_name="内存容量",
max_length=16,
null=True # 运行为空,因为有的内存插槽中没有插内存条
)
slot = models.CharField(
verbose_name="插槽", max_length=8) # 这里的插槽号应该都是有值的,所以按照默认的此字段必须有值
model = models.CharField(
verbose_name="内存类型",
max_length=16, null=True)
speed = models.CharField(
verbose_name="速率",
max_length=8, null=True)
manufacturer = models.CharField(
verbose_name="内存厂商",
max_length=64, null=True)
sn = models.CharField(
verbose_name="产品序列号",
max_length=128, null=True)
latest_date = models.DateField(
verbose_name='更新时间',
auto_now=True)
create_at = models.DateTimeField(
verbose_name='创建时间',
auto_now_add=True)
class Meta:
verbose_name = "内存表"
verbose_name_plural = verbose_name
db_table = "memory"
二、Django Model 如何表示表之间的关系
模型之间的三种关系:一对一,多对一,多对多。
一对一:实质就是在主外键(author_id就是foreign key)的关系基础上,给外键加了一个UNIQUE=True的属性;
多对一:就是主外键关系;(foreign key)
多对多:(ManyToManyField) 自动创建第三张表(当然我们也可以自己创建第三张表:两个foreign key)
# 一对一:
models.OneToOneField(
OtherModel, on_delete=models.CASCADE)
# 多对一:
models.ForeignKey(OtherModel, on_delete=models.CASCADE)
# 多对多:
models.ManyToManyField(OtherModel)
Django2.x 的 多对一表关系设置时,外键需要添加
on_delete=models.CASCADE
。
表示当删除表中的数据的时候,执行级联删除动作。
on_delete 了解
关于 on_delete
的参数:
-
models.CASCADE
级联删除。当
ForeignKey
字段的关联对象(主表的某条数据)被删除的时候,这些有关联关系的数据(从表的相关数据)都会被自动删除 。Model.delete()
不会在相关模型上调用,但 会为所有已删除对象发送pre_delete
和post_delete
信号。 -
models.SET_NULL
当主表的一条数据被删除后,此
ForeignKey
字段将会被设置为 None(数据库中是 Null)前提条件是,此
ForeignKey
字段必须设置属性:null = True
。 -
PROTECT
通过引发
django.db.IntegrityError
的子类ProtectedError
来 防止删除引用的对象 。就是当你删除主表的某一条数据时候,必须保证没有任何数据和这条数据关联。
SET_DEFAULT
将为ForeignKey
设置默认值;必须为此 ForeignKey
字段添加 default
属性。
三、 目前 Model (模型)的样子
结合之前的服务器表和现在的内存表,目前最终的 model 应是像下面这样
class Server(models.Model):
server_type_choices = (
(1, '机架式'),
(2, '刀片式'),
(3, '塔式'),
)
server_type = models.PositiveSmallIntegerField(
verbose_name="服务器类型",
choices=server_type_choices,
default=1
)
host_name = models.CharField(
verbose_name='主机名', max_length=128, unique=True)
kernel = models.CharField(verbose_name="内核", max_length=128)
sn = models.CharField(
verbose_name='SN号',
max_length=64,
db_index=True) # 为此字段创建索引
os = models.CharField(verbose_name='操作系统', max_length=64)
manufacturer = models.CharField(verbose_name='厂商', max_length=64,)
pod_name = models.CharField(verbose_name='产品型号', max_length=32,)
manage_ip = models.GenericIPAddressField(
verbose_name='管理IP', null=True, blank=True)
cpu_name = models.CharField(verbose_name='CPU 型号', max_length=64)
cpu_num = models.IntegerField(verbose_name='CPU 颗数')
cpu_cores_each = models.IntegerField(verbose_name='每颗 CPU 核心数')
latest_date = models.DateTimeField(
verbose_name='更新时间', auto_now=True, null=True)
create_at = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
class Meta:
verbose_name = "服务器表"
verbose_name_plural = verbose_name
db_table = 'server'
def __str__(self):
return self.host_name
class Memory(models.Model):
server = models.ForeignKey("Server", verbose_name="服务器",
related_name="memory", on_delete=models.CASCADE)
capacity = models.CharField(verbose_name="内存容量", max_length=16, null=True)
slot = models.CharField("插槽", max_length=8)
model = models.CharField(verbose_name="内存类型", max_length=16, null=True)
speed = models.CharField(verbose_name="速率", max_length=8, null=True)
manufacturer = models.CharField(verbose_name="内存厂商", max_length=64, null=True)
sn = models.CharField(verbose_name="产品序列号", max_length=128, null=True)
latest_date = models.DateField(verbose_name='更新时间', auto_now=True)
create_at = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
class Meta:
verbose_name = "内存表"
verbose_name_plural = verbose_name
db_table = "memory"
def __str__(self):
return "{}-{}".format(self.slot, self.capacity)
Model.__str__()
__str__()
方法在每当你对一个对象调用str()
时候。 Django在许多地方使用str(obj)
。 最明显的是在Django 的Admin 站点显示一个对象和在模板中插入对象的值的时候。 所以,你应该始终让__str__()
方法返回模型的一个友好的、人类可读的形式。
使用之前的情况:
In [2]: Server.objects.first()
Out[2]: <Server: Server object (1)>
In [3]: server = Server.objects.first()
In [4]: str(server)
Out[4]: 'Server object (1)'
使用之后的情况:
In [2]: server = Server.objects.first()
In [3]: str(server)
Out[3]: 'bj-dev-webserver-01'
四、同步数据库
python3 manage.py makemigrations
python3 manage.py migrate
五、ORM 多表操作
1 ForeignKey
多对一(一对多)
1.1 添加数据
对应内存表添加数据时,其他的字段基本和之前添加服务器表数据时一样。
这里主要是需要考虑 ForeignKey
(外键)字段
方式有两种:
- 保存时,给
ForeignKey
字段赋值为具体关联对象的主键 id,比如server_id=1
- 保存时,给
ForeignKey
字段赋值为关联的对象, 比如server=server_obj
注意:
内存表的ForeignKey
字段名称 server
在数据库中实际的字段名是 server_id
server_obj
是从服务器表中查询到的具体的一个数据对象,也就是模型类 Server
的一个实例。
示例演示: 赋值为 id 方式
模拟两根内存条数据:
In [4]: mem_info = [
{
"capacity": "4096 MB",
"slot": "DIMM 0",
"model": "RAM",
"speed": "Unknown",
"manufacturer": "Alibaba Cloud",
"sn": "Not Specified"
},
{
"capacity": "8192 MB",
"slot": "DIMM 1",
"model": "RAM",
"speed": "Unknown",
"manufacturer": "Alibaba Cloud",
"sn": "Not Specified"
}
]
这两条内存数据属于服务器名为:bj-dev-dbserver-01
的,下面是保存到数据库的示例代码:
In [5]: server_id = Server.objects.filter(
host_name="bj-dev-dbserver-01").values_list(
"id", flat=True)[0]
In [6]: server_id
Out[6]: 1
In [7]: for mem in mem_info:
mem["server_id"] = server_id
Memory.objects.create(**mem)
示例演示: 赋值为对象的方式
再次模拟两根内存条数据:
In [19]: mem_info2 = [
{
"capacity": "8192 MB",
"slot": "DIMM 0",
"model": "RAM",
"speed": "Unknown",
"manufacturer": "Alibaba Cloud",
"sn": "Not Specified"
},
{
"capacity": "8192 MB",
"slot": "DIMM 1",
"model": "RAM",
"speed": "Unknown",
"manufacturer": "Alibaba Cloud",
"sn": "Not Specified"
}
]
这两条内存数据属于服务器名为:iZ2zecj761el8gvy7p9y2kZ
的,下面是保存到数据库的示例代码:
需要导入 Memory
In [119]: server_obj = Server.objects.filter(host_name="iZ2zecj761el8gvy7p9y2kZ").first()
In [120]: server_obj
Out[120]: <Server: iZ2zecj761el8gvy7p9y2kZ>
In [125]: for mem in mem_info2:
mem["server"] = server_obj
Memory.objects.create(**mem)
或者使用更简单的方式:
无需导入 Memory
In [126]: server_obj = Server.objects.filter(host_name="iZ2zecj761el8gvy7p9y2kZ").first()
In [127]: server_obj
Out[127]: <Server: iZ2zecj761el8gvy7p9y2kZ>
In [128]: for mem in mem_info2:
mem["server"] = server_obj
server_obj.memorys.create(**mem)
1.2 查询数据
多对一关系的数据查询分为正向查询和方向查询
- 正向查询 指的是从含有
ForeignKey
字段的表去查不含ForeignKey
字段的表中的数据。比如希望查询某个内存信息属于那台服务器的。 - 反向查询 指的就是从不含
ForeignKey
表去查含有ForeignKey
字段表里的数据。比如查询某台服务器的所有内存信息。
1.2.1 正向查找:获取到对象
已知一个内存数据的 id 是 1 ,现在希望查询它是属于那台服务器。
第一步需要先获取到这个内存数据对象
In [128]: mem_obj = Memory.objects.filter(id=1).first()
In [129]: mem_obj
Out[129]: <Memory: DIMM 0-4096 MB>
第二步通过 .ForeignKey字段的名称
进行夸表查询到对应的服务器数据对象,此示例中是 ForeignKey
字段名名称是 server
In [130]: mem_obj.server
Out[130]: <Server: bj-dev-dbserver-01>
In [131]: mem_obj.server.host_name
Out[131]: 'bj-dev-dbserver-01'
1.2.2 正向查找:一次性获取到字段的值
有时候我们希望在查询出内存数据的同时也能查询出对应服务器的主机名或者其他字段的值。
可以通过在 .values()
中使用 ForeignKey字段名称+双下线+夸表后的字段
的方式直接进行夸表查询到需要的数据。
比如 .values("server__host_name")
In [134]: Memory.objects.filter(id=1).values("slot", "capacity", "server__host_name")
Out[134]: <QuerySet [{'slot': 'DIMM 0', 'capacity': '4096 MB', 'server__host_name': 'bj-dev-dbserver-01'}]>
1.2.3 反向查找:获取到QuerySet
比如目前希望查询出服务器 bj-dev-dbserver-01
的所有内存。
首先要清楚的是,一台服务器是有多条内存的,也就是查询到的结果会是多条数据。ORM 中查询后返回的结果是含有 多个对象的 QuerySet 对象集。
In [136]: Server.objects.filter(host_name="bj-dev-dbserver-01").first()
Out[136]: <Server: bj-dev-dbserver-01>
In [137]: server = Server.objects.filter(host_name="bj-dev-dbserver-01").first()
In [138]: server.memorys
Out[138]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x10a1cd590>
In [140]: server.memorys.all()
Out[140]: <QuerySet [<Memory: DIMM 0-4096 MB>, <Memory: DIMM 1-8192 MB>]>
注意:这里需要使用
related_name
属性的值
1.2.4 反向查找:一次性获取到字段的值
同样使用related_name
的值加上双下划线 __
可以实现直接夸表查询。
In [147]: Server.objects.filter(
host_name="bj-dev-dbserver-01").values(
"host_name", "memorys__capacity")
Out[147]: <QuerySet [{'host_name': 'bj-dev-dbserver-01', 'memory__capacity': '4096 MB'}, {'host_name': 'bj-dev-dbserver-01', 'memory__capacity': '8192 MB'}]>
1.3 更新数据
更新数据的操作和单表的一致。
需要注意的是,当更新的数据中需要更新 ForeignKey
字段时,需要先获取到 ForeignKey
字段对应的那个对象或者对象的 id,就像是之前创建时一样。
示例
假如希望更新服务器 bj-dev-dbserver-01
所有的内存为 8192 MB
In [17]: server = Server.objects.filter(host_name='bj-dev-dbserver-01')[0]
In [18]: server.memorys.update(capacity="8192 MB")
Out[18]: 2
In [19]: server.memorys.values("capacity")
Out[20]: <QuerySet [{'capacity': '8192 MB'}, {'capacity': '8192 MB'}]>
2 ManyToManyField
多对多关系
1 表设计
大家知道 Ansible 的资产清单中,一个主机可以放到多个组中,对于主机和组之间的关系就是 一对多的关系;多个主机可以放到一个组中,同样对于主机和组之间的关系就是 多对一的关系。
1.1 关系数据库的表设计
在关系型数据库中,要表示这样的关系需要使用第三张表。比如:
主机表
id | host |
---|---|
1 | bj-dev-dbserver-01 |
2 | bj-prod-dbserver-01 |
3 | iZ2zecj761el8gvy7p9y2kZ |
主机组表
id | group |
---|---|
1 | db |
2 | dev |
host2group
第三张表示关系的表
id | host_id | group_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | |
1 | 2 | |
这是在关系型数据库中需要设计的表结构。
可以看到 ID 为 1 的主机属于 db 组(id 为 1),也属于 dev 组(id 为 2),并且 db 组中包含了 2 个主机,分别是 bj-dev-dbserver-01
和
bj-prod-dbserver-01
1.2 Djnago ORM 中的 model 设计
在 Django 中,第三张表的模型类不需要写,只需要写主机表和主机组表的模型类,之后在其中任何一张表中添加属性为 ManyToManyField
的字段即可。Django 会自动在数据库中创建第三张表。
class Host(models.Model):
host = models.CharField(
"主机", max_length=150,
help_text="可以是主机名或者 IP")
def __str__(self):
return self.host
class Meta:
db_table = 'host'
managed = True
verbose_name = '主机表'
verbose_name_plural = verbose_name
class HostGroup(models.Model):
group = models.CharField("主机组名", max_length=150)
hosts = models.ManyToManyField(
"Host", related_name="hostgroups",
verbose_name="属组")
def __str__(self):
return self.group
class Meta:
db_table = 'host_group'
managed = True
verbose_name = '主机组表'
verbose_name_plural = verbose_name
这里把
ManyToManyField
字段放到了主机表里。并且在主机表里的
host
字段中添加了help_text
属性,这个属性和增加对这个字段的描述信息,相当于 MySQL 中字段的comment
属性。
verbose_name
属性的值改成了中文,这将会在后面讲到的后台管理中体现出来。
2 创建表到数据库中
切换到项目的虚拟环境下,并进入项目主目录下执行如下迁移命令和创建表的命令。
python3 manage.py makemigrations
python3 manage.py migrate
3 创建数据
多对多数据次创建有很多方事
夸表字段名.create() 适用于关联表中没有数据,每次创建一个,此时会先在关联表中创建这个对象,之后在第三张表中添加一条关系数据。
夸表字段名.add() 适用于关联表中已有数据,每次添加一个或者多个,多个对象之间用英文逗号隔开,这会在第三章表中直接创建一条或者多条关系数据。
夸表字段名.set() 适用于一次创建多条关系数据,前提是关联表里已经有需要添加的数据了,并且要注意这是创建新的关系数据条目,不是增加,假如有重复的,会被覆盖掉。
.create()
In [7]: db,e = HostGroup.objects.get_or_create(group="db")
In [8]: db
Out[8]: <HostGroup: db>
In [9]: db.hosts.create(host="bj-dev-dbserver-01")
Out[9]: <Host: bj-dev-dbserver-01>
.get_or_create()
方法会先查询是否有此对象,有就获取到此对象并返回,没有就创建此对象并返回这个对象。它返回一个二元组,第一个元素是数据对象本身,第二个是 一般布尔值 True/False
, 假如此次创建了对象返回 True
,否则返回 False
。
.add()
In [10]: db,e = HostGroup.objects.get_or_create(group="db")
In [11]: e
Out[11]: False
In [12]: db
Out[12]: <HostGroup: db>
In [13]: h2 = Host.objects.create(host="bj-prod-dbserver-01")
In [14]: h2
Out[14]: <Host: bj-prod-dbserver-01>
In [15]: db.hosts.add(h1)
.set()
In [52]: h1, e = Host.objects.get_or_create(host="bj-dev-dbserver-01")
In [53]: h3, e = Host.objects.get_or_create(
host="iZ2zecj761el8gvy7p9y2kZ")
In [54]: h1,h3
Out[54]: (<Host: bj-dev-dbserver-01>, <Host: iZ2zecj761el8gvy7p9y2kZ>)
In [55]: dev, e = HostGroup.objects.get_or_create(group="dev")
In [56]: dev.set([h1, h3])
4 查询数据
对于多对多,无论从那一方查询另一方,得到的结果都是一个 QuerySet。应该查到的应该是一个复数,而不是一个单一的对象。
接上例实验继续做查询示例演示
返回对象集
In [60]: dev.hosts.all()
Out[60]: <QuerySet [<Host: bj-dev-dbserver-01>, <Host: iZ2zecj761el8gvy7p9y2kZ>]>
In [61]: h1
Out[61]: <Host: bj-dev-dbserver-01>
In [62]: h1.hostgroups.all()
Out[62]: <QuerySet [<HostGroup: db>, <HostGroup: dev>]>
In [63]: h3.hostgroups.all()
Out[63]: <QuerySet [<HostGroup: dev>]>
返回数据集
In [66]: Host.objects.filter(id=1).values("host", "hostgroups__group")
Out[66]: <QuerySet [{'host': 'bj-dev-dbserver-01', 'hostgroups__group': 'db'}, {'host': 'bj-dev-dbserver-01', 'hostgroups__group': 'dev'}]>
In [68]: HostGroup.objects.filter(
group='db').values("group", "hosts__host")
Out[68]: <QuerySet [{'group': 'db', 'hosts__host': 'bj-dev-dbserver-01'}, {'group': 'db', 'hosts__host': 'bj-prod-dbserver-01'}]>
去重
查询主机开头是 bj
的所有组
In [86]: HostGroup.objects.filter(hosts__host__startswith="bj")
Out[86]: <QuerySet [<HostGroup: db>, <HostGroup: db>, <HostGroup: dev>]>
In [87]: HostGroup.objects.filter(hosts__host__startswith="bj").distinct()
Out[87]: <QuerySet [<HostGroup: db>, <HostGroup: dev>]>
去重后统计
In [88]: HostGroup.objects.filter(hosts__host__startswith="bj").distinct().count()
Out[88]: 2
5 删除数据
比如从 dev 组中删除主机 iZ2zecj761el8gvy7p9y2kZ
In [76]: dev = HostGroup.objects.get(group='dev')
In [77]: dev.hosts.all()
Out[77]: <QuerySet [<Host: bj-dev-dbserver-01>, <Host: iZ2zecj761el8gvy7p9y2kZ>]>
In [78]: h3 = dev.hosts.all()[1]
In [79]: h3
Out[79]: <Host: iZ2zecj761el8gvy7p9y2kZ>
In [80]: dev.hosts.remove(h3)
In [81]: dev.hosts.all()
Out[81]: <QuerySet [<Host: bj-dev-dbserver-01>]>
6 更新数据
注意:这里所说的更新是通过一个对象更新关联对象的某个字段,而不是跟下关系表。
示例如下:
更新 dev 组的所有主机的 host
字段的值为 "11"
。
其实这并不是很安全的操作,这里不应该更新关联对象的主键和唯一属性的键。除非关系对象还有其他的字段,否则不能这么干。
In [117]: dev.hosts.update(host="111")
Out[117]: 2
In [118]: dev.hosts.all()
Out[118]: <QuerySet [<Host: 111>, <Host: 111>]>
In [119]: Host.objects.all()
Out[119]: <QuerySet [<Host: 111>, <Host: bj-prod-dbserver-01>, <Host: 111>]>
In [120]:
要实现更新关系表的条目
可以先删除需要被更新的关系条目
再添加新的需要更新为的关系条目
In [132]: dev.hosts.all()
Out[132]: <QuerySet [<Host: 111>, <Host: 111>]>
In [133]: Host.objects.values()
Out[133]: <QuerySet [{'id': 1, 'host': '111'}, {'id': 2, 'host': 'bj-prod-dbserver-01'}, {'id': 3, 'host': '111'}]>
In [134]: h2 = Host.objects.filter(id=2)
In [135]: h2 = h2[0]
In [136]: h2
Out[136]: <Host: bj-prod-dbserver-01>
In [137]: h3 = dev.hosts.filter(id=3)[0]
In [138]: h3
Out[138]: <Host: 111>
In [139]: dev.hosts.remove(h3)
In [140]: dev.hosts.all()
Out[140]: <QuerySet [<Host: 111>]>
In [141]: dev.hosts.add(h2)
In [142]: dev.hosts.all()
Out[142]: <QuerySet [<Host: 111>, <Host: bj-prod-dbserver-01>]>
3 OneToOneField
一对一关系
在企业中需要管理的资产不仅仅是服务器,还有网络设备,比如交换机、路由器、防火墙等设备。
那这样的情况下,服务器和网络设备(交换机、路由器、防火墙)一般需要管理的信息不一样的,也就是表里的字段不一样,比如交换机需要有 vlan,路由器需要有路由协议等。这样每种不同的设备就需要设计创建不同的表。
那有时候我们希望通过一个表格去展示出公司所有的资产信息,这种情况下,可以设计一个表,这个表中的每条数据都对应了其他设备表的一条数据。这就是一对一关系,Django ORM 中使用 OneToOneField
。其实是对Foreignkey
字段增加了 unique=True
属性。
1 设计表
通常我们在设计资产表的时候也会加上资产状态字段,用于表示 上线、下线、上架、下架、故障等资产的状态,添加资产类型字段,用于表示 服务器、交换机、路由器等。
设备信息表
资产的 models 应该这样
class Asset(models.Model):
ASSET_STATUS_CHOICES = (
("0", "故障"),
("1", "上线"),
("2", "下线"),
("3", "上架"),
("4", "下架"),
)
ASSET_TYPE_CHOICES = (
("1", "服务器"),
("2", "路由器"),
("3", "交换机"),
("4", "防火墙"),
)
asset_type = models.CharField(
"资产类型", max_length=1,
choices=ASSET_TYPE_CHOICES, default='1')
asset_status = models.CharField(
"资产状态", max_length=1,
choices=ASSET_STATUS_CHOICES, default='3')
def __str__(self):
return "{}-{}".format(
self.get_asset_type_display(),
self.get_asset_status_display())
class Meta:
db_table = 'asset'
managed = True
verbose_name = '资产信息表'
verbose_name_plural = verbose_name
这里我们只讨论服务器信息表和资产信息表的关系,网络设备基本和此方式一样的道理,不同的就是网络设备需要管理的字段可能有些不同而已,可以根据公司不同的需求进行定义即可。
更新服务器信息表
服务器信息表需要增加一对一的关系字段。
class Server(models.Model):
server_type_choices = (
(1, '机架式'),
(2, '刀片式'),
(3, '塔式'),
)
# 每个服务器都和资产表一一对应
asset = models.OneToOneField(
'Asset', verbose_name='对应资产',
null=True, # 允许此字段在数据库中的值为 null
blank=True, # 允许此字段在表单提交的时候留空
related_name='server', # 用于反向查找时使用的名字
on_delete=models.CASCADE)
注意:这里对一对一字段的值设置 null=True
, 是因为目前服务器表里已经有数据了,假如不设置可为空,则在重新创建表的时候,必须为这些已经存在的数据提供一个默认值;注意,由于此字段是关系字段,所以默认值必须在关联表里存在默认值对应的数据,现在目前是无法实现。
解决的办法就是先设置为允许为空,之后等数据补充完整后,在将其为空的属性去掉。
2 更新到数据库
python3 manage.py makemigrations
python3 manage.py migrate
3 增加示例数据
首先增加资产数据
In [1]: from cmdb.models import Asset,Server
In [2]: a1 = Asset(asset_type="1",asset_status="1")
In [3]: a1.save()
In [4]: a2 = Asset(asset_type="1",asset_status="2")
In [5]: a3 = Asset(asset_type="1",asset_status="3")
In [6]: a4 = Asset(asset_type="1",asset_status="4")
In [7]: a2.save();a3.save();a4.save()
In [8]: Asset.objects.all()
Out[8]: <QuerySet [<Asset: 服务器-上线>, <Asset: 服务器-下线>, <Asset: 服务器-上架>, <Asset: 服务器-下架>]>
给现有的服务器数据添加一对一的关系
服务器 | 资产类型 | 资产状态 |
---|---|---|
bj-dev-dbserver-01 | 服务器 | 上线 |
iZ2zecj761el8gvy7p9y2kZ | 服务器 | 下线 |
bj-prod-dbserver-01 | 服务器 | 上架 |
bj-prod-dbserver-03 | 服务器 | 下架 |
In [29]: online = Asset.objects.filter(
asset_type="1",asset_status="1").first()
In [30]: offline = Asset.objects.filter(
asset_type="1",asset_status="2").first()
In [31]: up = Asset.objects.filter(
asset_type="1",asset_status="3").first()
In [32]: down = Asset.objects.filter(
asset_type="1",asset_status="4").first()
In [33]: s1 = Server.objects.filter(host_name="bj-dev-dbserver-01").first()
In [34]: s1.asset=online
In [35]: s1.save()
In [36]: Server.objects.filter(host_name="iZ2zecj761el8gvy7p9y2kZ").update(asset=offline)
Out[36]: 1
In [37]: Server.objects.filter(host_name="bj-prod-dbserver-01").update(asset=up)
Out[37]: 1
In [38]: Server.objects.filter(host_name="bj-prod-dbserver-03").update(asset=down)
Out[38]: 1
In [39]: Server.objects.values("host_name", "asset__asset_status")
Out[39]: <QuerySet [
{'host_name': 'bj-dev-dbserver-01',
'asset__asset_status': '1'},
{'host_name': 'iZ2zecj761el8gvy7p9y2kZ',
'asset__asset_status': '2'},
{'host_name': 'bj-prod-dbserver-01',
'asset__asset_status': '3'},
{'host_name': 'bj-prod-dbserver-03',
'asset__asset_status': '4'}]>
4 查询数据
查询数据的时候,方法和之前的关系表查询一样,但是返回的结果都是当对象,这和查询多对多关系时发回结果是不同的。
正向查找关联的对象
In [48]: s1 = Server.objects.get(id=1)
In [49]: s1
Out[49]: <Server: bj-dev-dbserver-01>
In [50]: s1.asset
Out[50]: <Asset: 服务器-上线>
In [51]: s1.asset.get_deferred_fields()
Out[51]: set()
In [52]: s1.asset.get_asset_status_display()
Out[52]: '上线'
In [53]: s1.asset.get_asset_type_display()
Out[53]: '服务器'
反向查找关联的对象
In [44]: a1 = Asset.objects.first()
In [45]: a1
Out[45]: <Asset: 服务器-上线>
In [46]: a1.server
Out[46]: <Server: bj-dev-dbserver-01>
In [47]: a1.server.host_name
Out[47]: 'bj-dev-dbserver-01'