python 通用的多对多关系

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/933092/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 21:08:34  来源:igfitidea点击:

Generic many-to-many relationships

pythondjangogenericsdjango-modelsmany-to-many

提问by Noel Evans

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. The last line is the sticking point. How do you allow message recipients to be a set of CompanyClients and PersonClients

下面是一个简化的例子。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)

回答by elo80ka

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 解决方案。

回答by Dr Manhattan

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

回答by prairiedogg

You might get around this problem by simplifying your schema to include a single Clienttable 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.

这也可能不是解决问题的最佳方式。我只是把它作为一种潜在的可能性扔出去。我不知道将两个相似的模型拼在一起并使用保存覆盖来确保数据完整性是否存在传统智慧。看起来它可能有潜在的问题......我会让社区在这个问题上学习我。