简体   繁体   English

通用的多对多关系

[英]Generic many-to-many relationships

I'm trying to create a messaging system where a message's sender and recipients can be generic entities. 我正在尝试创建一个消息传递系统,其中消息的发送者和接收者可以是通用实体。 This seems fine for the sender, where there is only object to reference (GenericForeignKey) but I can't figure out how to go about this for the recipients (GenericManyToManyKey ??) 对于发件人来说,这似乎很好,在该发件人中只有要引用的对象(GenericForeignKey),但我不知道如何为收件人进行处理(GenericManyToManyKey ??)。

Below is a simplified example. 下面是一个简化的示例。 PersonClient and CompanyClient inherit attributes from Client but have their own specific details. PersonClient和CompanyClient从Client继承属性,但具有其自己的特定详细信息。 The last line is the sticking point. 最后一行是症结所在。 How do you allow message recipients to be a set of CompanyClients and PersonClients 如何允许邮件收件人成为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)

You can implement this using generic relationships by manually creating the junction table between message and recipient: 您可以通过在消息和收件人之间手动创建联结表来使用通用关系来实现此目的:

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)

You'd use the above models like so: 您将像这样使用以上模型:

>>> 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

As you can see, this is a far more verbose (complicated?) solution. 如您所见,这是一个更为冗长(复杂?)的解决方案。 I'd probably keep it simple and go with Prariedogg's solution below. 我可能会保持简单,并在下面使用Prariedogg的解决方案。

The absolute best way to go about this is to use a library called django-gm2m 做到这一点的绝对最佳方法是使用一个名为django-gm2m的库

pip install django-gm2m

Then if we have our models 那如果我们有我们的模型

>>> from django.db import models
>>>
>>> class Video(models.Model):
>>>       class Meta:
>>>           abstract = True
>>>
>>> class Movie(Video):
>>>     pass
>>>
>>> class Documentary(Video):
>>>     pass

And a user 和一个用户

>>> from gm2m import GM2MField
>>>
>>> class User(models.Model):
>>>     preferred_videos = GM2MField()

We can do 我们可以做的

>>> user = User.objects.create()
>>> movie = Movie.objects.create()
>>> documentary = Documentary.objects.create()
>>>
>>> user.preferred_videos.add(movie)
>>> user.preferred_videos.add(documentary)

Sweet right? 甜美的吧?

For more info go here: 有关更多信息,请转到此处:

http://django-gm2m.readthedocs.org/en/stable/quick_start.html http://django-gm2m.readthedocs.org/en/stable/quick_start.html

You might get around this problem by simplifying your schema to include a single Client table with a flag to indicate what type of client it was, instead of having two separate models. 通过简化架构以包括一个带有标志以指示客户端类型的客户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)

If you do things this way you might not have to mess around with Generic Foreign Keys at all. 如果您以这种方式进行操作,则可能根本不必弄乱通用外键。 As an added convenience you can also write custom managers for the Client model like Client.corporate.all() , Client.person.all() , to return pre-filtered querysets containing only the type of clients that you want. 为了增加便利,您还可以为Client模型编写自定义管理器,例如Client.corporate.all()Client.person.all() ,以返回仅包含所需客户机类型的预过滤查询集。

This also may not be the best way of solving your problem. 这也可能不是解决问题的最佳方法。 I'm just throwing it out there as one potential possibility. 我只是把它扔出去,作为一种潜在的可能性。 I don't know if there's conventional wisdom about smashing together two similar models and using a save override to ensure data integrity. 我不知道是否有将两个相似的模型粉碎在一起并使用保存覆盖来确保数据完整性的传统知识。 It seems like it could be potentially problematic ... I'll let the community learn me on this one. 看来这可能有潜在的问题。我将让社区在这一方面让我学习。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM