运维咖啡吧

享受技术带来的乐趣,体验生活给予的感动

Django Model关联关系之ForeignKey

ForeignKey定义了一个多对一的关系,需要两个位置参数:模型相关的类和on_delete选项

例如:models.Foreignkey(User,on_delete=models.CASCADE),其中User就是模型相关的类,表示与User表相关联,而on_delete=models.CASCADE则是on_delete选项,定义关联表数据删除时的动作

参数

必选参数

两个必选参数,分别是模型类和on_delete选项

模型类

关联的模型,默认情况下指向关联的模型对象,某些情况下需要模型对象关联自身,例如用户表里有个leader字段用来标识当前用户的直属领导,直属领导也是个用户,所以需要跟用户表自身相关联,那模型类就需要写成self

class User(models.Model):
    username = models.CharField(max_length=16, verbose_name='用户名称')
    leader = models.ForeignKey('self', on_delete=models.PROTECT, verbose_name='直属领导')

    def __str__(self):
        return self.username

on_delete

on_delete的值主要有以下几个

其他参数

表现

默认情况下,当migrate应用表修改后,查询数据库会发现,所有ForeignKey字段在数据库里的字段名称会加上_id,例如上边User表的leader字段,在数据库里讲会变成leader_id

当然你可以通过db_column来改变字段在数据库中的名字,不过一般没必要,通常保持默认就好了

 CREATE TABLE `accounts_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(16) NOT NULL,
  `leader_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `accounts_user_leader_id_28d81690` (`leader_id`)
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8

管理

以下演示代码参考如下表设计

class Host(TimeBaseModel):
    ip = models.GenericIPAddressField(verbose_name='IP地址')
    port = models.SmallIntegerField(verbose_name='主机端口')


class Password(TimeBaseModel):
    host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name='主机')
    user = models.CharField(max_length=32, verbose_name='账号')
    password = models.CharField(max_length=32, verbose_name='密码')
    contact = models.CharField(max_length=64, verbose_name='联系人')
    note = models.TextField(max_length=64, verbose_name='备注')

新增

>>> h1 = Host.objects.create(ip='192.168.3.1', port=22)
>>>
>>> h2 = Host.objects.create(ip='192.168.3.2', port=22)
>>>
>>>
>>> h1.id
29
>>> h2.id
30
>>> p1 = Password.objects.create(host=h1, user='root', password='123', contact='fei.liu', note='')
>>>
>>> p2 = Password.objects.create(host_id=30, user='root', password='123', contact='fei.liu', note='')
>>>
>>>
>>> p1.host.id
29
>>> p2.host.id
30

可以通过instance实例instance_id两种方式来新增数据

查询

正向查询

>>> p = Password.objects.get(id=3)
>>> p.host.ip
'192.168.3.1'

反向查询

>>> h = Host.objects.get(id=30)
>>> h.password_set.all()
<QuerySet [<Password: Password object (5)>]>

related_name

class Password(TimeBaseModel):
    host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='users', verbose_name='主机')
>>> h = Host.objects.get(id=30)
>>> h.users.all()
<QuerySet [<Password: [email protected]>]>

related_name配置存在时,则可以通过related_name定义的值进行查询

修改

>>> p = Password.objects.get(id=5)
>>> p.host_id = 29
>>> p.save()
>>>
>>> p.host
<Host: 192.168.3.1>
>>> h = Host.objects.get(id=30)
>>> h.ip
'192.168.3.2'
>>>
>>> p.host = h
>>> p.save()
>>>
>>> p.host
<Host: 192.168.3.2>

同样的也能通过instance实例instance_id两种方式来修改数据

删除

>>> p = Password.objects.get(id=5)
>>> p.delete()
(1, {'cmdb.Password': 1})