Подтвердить что ты не робот

Общие отношения "многие-ко-многим"

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

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

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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