外键指向抽象类(泛型关系)
我正在用Django做一个个人项目,目的是提升自己的技能(因为我喜欢Django,但感觉自己还不够熟练)。我已经有了基本的需求,知道Python,也仔细读过Django的书,至少两到三遍。
我的目标是创建一个简单的监控服务,搭建一个基于Django的网页界面,让我可以检查我的“节点”(服务器)的状态。每个节点有多个“服务”。这个应用会检查每个节点上每个服务的可用性。
我现在的问题是,我不知道怎么在数据库里表示不同类型的服务。我想到了两个“解决方案”:
- 单一服务模型,里面有一个“serviceType”字段,但这样会导致字段混乱。(我在数据库建模方面经验不多,但我觉得这样看起来... “不好”)
- 多个服务模型。我喜欢这个方案,但我不知道怎么在同一个字段里引用这些不同的服务。
这是我models.py文件中的一小段代码:(我去掉了与这个问题无关的部分)
from django.db import models
# Create your models here.
class service(models.Model):
port = models.PositiveIntegerField()
class Meta:
abstract = True
class sshService(service):
username = models.CharField(max_length=64)
pkey = models.TextField()
class telnetService(service):
username = models.CharField(max_length=64)
password = models.CharField(max_length=64)
class genericTcpService(service):
pass
class genericUdpService(service):
pass
class node(models.Model):
name = models.CharField(max_length=64)
# various fields
services = models.ManyToManyField(service)
当然,那个包含ManyToManyField的行是错误的。我不知道该用什么替代“*Service”。我真的搜索过这个问题的解决方案,听说过“通用关系”、三表连接,但我并没有真正理解这些东西。
而且,英语不是我的母语,所以在数据库结构和语义方面,我对我所读的内容的理解有限(但这也是我的问题)。
4 个回答
如果你想了解通用外键关系,可以看看Django的内容类型框架(这个框架是Django自带的)。文档里基本上解释了怎么使用它,以及如何处理通用关系。
我觉得你可以考虑一种方法叫做“子类化查询集”。简单来说,它允许你查询父模型,然后返回的结果中会包含子模型的实例。这意味着你可以进行类似这样的查询:
models.service.objects.all()
然后你会得到像下面这样的结果:
[ <sshServiceInstance>, <telnetServiceInstance>, <telnetServiceInstance>, ...]
如果你想了解如何实现这个,可以看看下面博客文章里的链接。
http://jazstudios.blogspot.com/2009/10/django-model-inheritance-with.html
不过,如果你使用这种方法,就不应该像例子中那样把你的服务模型声明为抽象的。虽然这样会多一个连接,但我发现子类化查询集在返回混合对象时效果还是不错的。
希望这对你有帮助,
Joe
首先,建议你使用Django的多表继承,而不是你现在使用的抽象模型。
这样你的代码就会变成:
from django.db import models
class Service(models.Model):
port = models.PositiveIntegerField()
class SSHService(Service):
username = models.CharField(max_length=64)
pkey = models.TextField()
class TelnetService(Service):
username = models.CharField(max_length=64)
password = models.CharField(max_length=64)
class GenericTcpService(Service):
pass
class GenericUDPService(Service):
pass
class Node(models.Model):
name = models.CharField(max_length=64)
# various fields
services = models.ManyToManyField(Service)
在数据库层面,这样会创建一个“服务”表,这个表的每一行都会通过一对一的关系与每个子服务的独立表相连接。
这种方法唯一的难点是,当你做类似下面的操作时:
node = Node.objects.get(pk=node_id)
for service in node.services.all():
# Do something with the service
在循环中访问的“服务”对象将是父类型的。如果你事先知道这些对象会是什么子类型,你可以用以下方式直接访问子类:
from django.core.exceptions import ObjectDoesNotExist
try:
telnet_service = service.telnetservice
except (AttributeError, ObjectDoesNotExist):
# You chose the wrong child type!
telnet_service = None
如果你事先不知道子类型,那就有点复杂了。有一些比较“hacky”或者说不太优雅的解决方案,比如在父模型上加一个“serviceType”字段,但更好的方法是使用“子类查询集”。正如Joe J提到的,django-model-utils中的InheritanceManager类可能是最简单的使用方式。你可以在这里查看它的文档,这段代码真的很不错。