通用多对多关系
我正在尝试创建一个消息系统,在这个系统中,消息的发送者和接收者可以是各种不同的实体。对于发送者来说,这个想法似乎没问题,因为只需要引用一个对象(GenericForeignKey),但我不知道如何处理接收者(是不是要用GenericManyToManyKey?)
下面是一个简化的例子。PersonClient和CompanyClient都继承了Client的属性,但它们有自己特定的细节。最后一行是个难点。怎么才能让消息的接收者既可以是CompanyClients,也可以是PersonClients呢?
class Client(models.Model):
city = models.CharField(max_length=16)
class Meta:
abstract = True
class PersonClient(Client):
first_name = models.CharField(max_length=16)
last_name = models.CharField(max_length=16)
gender = models.CharField(max_length=1)
class CompanyClient(Client):
name = models.CharField(max_length=32)
tax_no = PositiveIntegerField()
class Message(models.Model):
msg_body = models.CharField(max_length=1024)
sender = models.ForeignKey(ContentType)
recipients = models.ManyToManyField(ContentType)
3 个回答
你可以通过简化你的数据库结构来解决这个问题,比如只用一个 Client
表,并加一个标记来说明这个客户是什么类型的,而不是分成两个不同的模型。
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Client(models.Model):
PERSON, CORPORATION = range(2)
CLIENT_TYPES = (
(PERSON, _('Person')),
(CORPORATION, _('Corporation')),
)
type = models.PositiveIntegerField(choices=CLIENT_TYPES, default=PERSON)
city = models.CharField(max_length=16)
first_name = models.CharField(max_length=16, blank=True, null=True)
last_name = models.CharField(max_length=16, blank=True, null=True)
corporate_name = models.CharField(max_length=16, blank=True, null=True)
tax_no = models.PositiveIntegerField(blank=True, null=True)
def save(self, *args, **kwargs):
"""
Does some validation ensuring that the person specific fields are
filled in when self.type == self.PERSON, and corporation specific
fields are filled in when self.type == self.CORPORATION ...
"""
# conditional save logic goes here
super(Client, self).save(*args, **kwargs)
这样做的话,你可能就不需要去处理通用外键了。为了方便起见,你还可以为 Client 模型写一些自定义管理器,比如 Client.corporate.all()
和 Client.person.all()
,这样可以直接返回你想要的那种客户的筛选结果。
不过,这可能不是解决你问题的最佳方法。我只是提出一个可能的思路。我不知道把两个相似的模型合并在一起,并用保存覆盖来确保数据完整性是否是个常见的做法。这样做可能会有一些问题……我会让社区来告诉我这个问题的答案。
最好的办法是使用一个叫做django-gm2m的库。
pip install django-gm2m
然后如果我们有我们的模型。
>>> from django.db import models
>>>
>>> class Video(models.Model):
>>> class Meta:
>>> abstract = True
>>>
>>> class Movie(Video):
>>> pass
>>>
>>> class Documentary(Video):
>>> pass
还有一个用户。
>>> from gm2m import GM2MField
>>>
>>> class User(models.Model):
>>> preferred_videos = GM2MField()
我们可以这样做。
>>> user = User.objects.create()
>>> movie = Movie.objects.create()
>>> documentary = Documentary.objects.create()
>>>
>>> user.preferred_videos.add(movie)
>>> user.preferred_videos.add(documentary)
听起来不错吧?
想了解更多信息可以去这里:
http://django-gm2m.readthedocs.org/en/stable/quick_start.html
你可以通过创建一个通用的关系来实现这个功能,也就是手动建立一个连接表,用来连接消息和接收者:
from django.db import models
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
class Client(models.Model):
city = models.CharField(max_length=16)
# These aren't required, but they'll allow you do cool stuff
# like "person.sent_messages.all()" to get all messages sent
# by that person, and "person.received_messages.all()" to
# get all messages sent to that person.
# Well...sort of, since "received_messages.all()" will return
# a queryset of "MessageRecipient" instances.
sent_messages = generic.GenericRelation('Message',
content_type_field='sender_content_type',
object_id_field='sender_id'
)
received_messages = generic.GenericRelation('MessageRecipient',
content_type_field='recipient_content_type',
object_id_field='recipient_id'
)
class Meta:
abstract = True
class PersonClient(Client):
first_name = models.CharField(max_length=16)
last_name = models.CharField(max_length=16)
gender = models.CharField(max_length=1)
def __unicode__(self):
return u'%s %s' % (self.last_name, self.first_name)
class CompanyClient(Client):
name = models.CharField(max_length=32)
tax_no = models.PositiveIntegerField()
def __unicode__(self):
return self.name
class Message(models.Model):
sender_content_type = models.ForeignKey(ContentType)
sender_id = models.PositiveIntegerField()
sender = generic.GenericForeignKey('sender_content_type', 'sender_id')
msg_body = models.CharField(max_length=1024)
def __unicode__(self):
return u'%s...' % self.msg_body[:25]
class MessageRecipient(models.Model):
message = models.ForeignKey(Message)
recipient_content_type = models.ForeignKey(ContentType)
recipient_id = models.PositiveIntegerField()
recipient = generic.GenericForeignKey('recipient_content_type', 'recipient_id')
def __unicode__(self):
return u'%s sent to %s' % (self.message, self.recipient)
你可以像这样使用上面的模型:
>>> person1 = PersonClient.objects.create(first_name='Person', last_name='One', gender='M')
>>> person2 = PersonClient.objects.create(first_name='Person', last_name='Two', gender='F')
>>> company = CompanyClient.objects.create(name='FastCompany', tax_no='4220')
>>> company_ct = ContentType.objects.get_for_model(CompanyClient)
>>> person_ct = ContentType.objects.get_for_model(person1) # works for instances too.
# now we create a message:
>>> msg = Message.objects.create(sender_content_type=person_ct, sender_id=person1.pk, msg_body='Hey, did any of you move my cheese?')
# and send it to a coupla recipients:
>>> MessageRecipient.objects.create(message=msg, recipient_content_type=person_ct, recipient_id=person2.pk)
>>> MessageRecipient.objects.create(message=msg, recipient_content_type=company_ct, recipient_id=company.pk)
>>> MessageRecipient.objects.count()
2
正如你所看到的,这种方法比较复杂(或者说冗长?)。我可能会选择更简单的方式,采用下面Prariedogg的解决方案。