05 Django2.2.12-ORM 关联关系操作

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"
        }
    ]
}

安照示例数据,内存表的基本字段应该有:

idcapacityslotmdelspeddmanufacturersn

另外,为了表现内存表和服务器表的多对一的关系,还需要增加一个属性为 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_deletepost_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。应该查到的应该是一个复数,而不是一个单一的对象。

image.png

接上例实验继续做查询示例演示

返回对象集

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'
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349