Общие отношения многие ко многим - PullRequest
43 голосов
/ 01 июня 2009

Я пытаюсь создать систему обмена сообщениями, в которой отправитель и получатели сообщения могут быть общими объектами. Это нормально для отправителя, где есть только объект для ссылки (GenericForeignKey), но я не могу понять, как это сделать для получателей (GenericManyToManyKey ??)

Ниже приведен упрощенный пример. PersonClient и CompanyClient наследуют атрибуты от клиента, но имеют свои собственные конкретные данные. Последняя строка является точкой слипания. Как разрешить получателям сообщений быть набором 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 ]

55 голосов
/ 02 июня 2009

Вы можете реализовать это, используя общие отношения, вручную создав таблицу соединений между сообщением и получателем:

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 выше.

6 голосов
/ 07 октября 2015

Абсолютно лучший способ добиться этого - использовать библиотеку 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 / ен / стабильный / quick_start.html

5 голосов
/ 01 июня 2009

Вы можете обойти эту проблему, упростив вашу схему, включив в нее одну таблицу 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)

Если вы поступите таким образом, вам вообще не придется возиться с Generic Foreign Keys. В качестве дополнительного удобства вы также можете написать собственные менеджеры для модели клиента, такие как Client.corporate.all(), Client.person.all(), для возврата предварительно отфильтрованных наборов запросов, содержащих только тот тип клиентов, который вам нужен.

Это также может быть не лучшим способом решения вашей проблемы. Я просто выбрасываю это как одну потенциальную возможность. Я не знаю, существует ли общепринятая идея объединить две одинаковые модели и использовать переопределение сохранения для обеспечения целостности данных. Кажется, что это может быть потенциально проблематично ... Я позволю сообществу узнать меня об этом.

...