Python中Django数据库模型的一对多、多对多关系问题如何解决?

一个域名监控

思路如下

域名 -> IP -> IP 段

但是 IP 与 IP 段都分别有 v4 与 v6 的 且域名可能同时拥有 v4 与 v6 的 IP

想了半天也不知道怎么去设计

看了很多 Django 的多对多(ManyToManyField) 的文章也弄不懂

如下代码 Domain 类里面的属性 ip 是不是应该用多对多的方式

方法一的代码是不是不应该这样子写的

方法一

class IPv4Range(models.Model):
    ipv4_range = models.CharField(max_length=20)
    last_update = models.DateTimeField(auto_now_add=True)

class IPv6Range(models.Model): ipv6_range = models.CharField(max_length=40) last_update = models.DateTimeField(auto_now_add=True)

class IPv4Address(models.Model): iprange = models.ForeignKey(‘IPv4Range’, on_delete=models.PROTECT) ipv4 = models.CharField(max_length=20) last_update = models.DateTimeField(auto_now_add=True)

class IPv6Address(models.Model): iprange = models.ForeignKey(‘IPv6Range’, on_delete=models.PROTECT) ipv6 = models.CharField(max_length=40) last_update = models.DateTimeField(auto_now_add=True)

class Domain(models.Model): ip = models.ForeignKey(IPv4Address, on_delete=models.PROTECT) domain = models.CharField(max_length=255) checked = models.BooleanField(default=False) keyword = models.CharField(max_length=255) add_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True)

方法二

class IPRange(models.Model):
    ipv4_range = models.CharField(max_length=20, null=True)
    ipv6_range = models.CharField(max_length=40, null=True)
    last_update = models.DateTimeField(auto_now_add=True)

class IPAddress(models.Model): iprange = models.ForeignKey(‘IPRange’, on_delete=models.PROTECT) ipv4 = models.CharField(max_length=20, null=True) ipv6 = models.CharField(max_length=40, null=True) last_update = models.DateTimeField(auto_now_add=True)

class Domain(models.Model): ip = models.ForeignKey(IPAddress, on_delete=models.PROTECT) domain = models.CharField(max_length=255) checked = models.BooleanField(default=False) keyword = models.CharField(max_length=255) add_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True)


Python中Django数据库模型的一对多、多对多关系问题如何解决?

10 回复

方法 1,ipv4address 和 ipv6address 都加 domain 的 foreignkey, 再加个 related_name


在Django里处理一对多和多对多关系其实挺直接的,主要就是用好ForeignKeyManyToManyField这两个字段。

一对多关系:用ForeignKey。比如一个作者(Author)可以写多本书(Book),那就在“多”的那一方(Book模型)里加个外键指向作者。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    # 这就是一对多的关键:这本书属于哪个作者
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

这样你就能用some_author.books.all()查一个作者的所有书,或者用some_book.author找一本书的作者。

多对多关系:用ManyToManyField。比如一本书可以有多个标签(Tag),一个标签也能对应多本书。

class Tag(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    # 这就是多对多的关键:这本书有哪些标签
    tags = models.ManyToManyField(Tag, related_name='books')

这样Django会自动在数据库里创建一张中间表来管理这个关系。你可以用some_book.tags.add(some_tag)来添加关系,或者用some_tag.books.all()来查一个标签下的所有书。

几个关键点

  1. on_delete参数在一对多里必须指定,常用的是CASCADE(删除作者时他的书也一起删)或PROTECT(有书存在时阻止删除作者)。
  2. related_name参数很好用,它让你能从关系的另一头反向查询,不写的话Django会自动生成一个(比如book_set),但自己设一个更清晰。
  3. 多对多关系如果需要有额外属性(比如给打标签加个时间戳),可以用through参数指定一个自定义的中间模型。

总结:外键处理一对多,多对多字段处理多对多,记得设好反向查询名。

#1
有个问题是 域名有可能会有多个 IP , IP 也有可能会有多个域名 .
找你说的设计也好像有问题
Domain 类的 ip 属性好像不能同时指定 ipv4 和 ipv6

我的意思是 domain 里的 ip field 可以删了, 直接用 related name 反查,比如 domain.ipv4_address。不过一个 ip 对多个 domain 有点奇怪

django 我不太熟,但是你这个需求应该有点像 rails has_and_belongs_to_many,在 rails 中是通过一张没有主键的表来完成的,表中存放着两方的 ID (主键)

#3
这样子做好像也实现不知到
域名有可能会有多个 IP , IP 也有可能会有多个域名 .

只需要 ip 的 FK 换成 M2M 即可,能表示一个域名有多个 ip,一个 ip 有多个域名,关于查询可以看这个:
https://mp.weixin.qq.com/s/JVh4UnS2Tql9gUVaBSoGuA

#6 也就是要用方法二这种数据库模型了?

我觉得楼主把问题复杂化了。
楼主学习一上 DNS 解析原理,仿照 DNS 数据库直接弄个解析记录表就可以了
域名和 IP 本身都是有唯一性的,没必要再外键一下了。其实就在一个模型就行

域名,记录类型,主机 IP 只要这三个字段,记录类型表示后面的主机 IP 类型,A:V4 ,AAAA:V6
一个域名多个 IP,就多条记录
abc.com, A, 123.2.4.12
abc.com, A, 34,12,22,45
abc.com, AAAA, as234:345345:345
def.com, A, 56.7.8.9
ghj.com, A, 78.1.2.3

查询时直接用 domain 或 IP 来 filter 出多条记录就可以了。

#8
对哦 还可以这样
现在这个逻辑想来想去太复杂了

回到顶部