50 votes

Relations génériques plusieurs-à-plusieurs

Je suis en train de créer un système de messagerie où un message de l'expéditeur et les destinataires peuvent être des entités génériques. Cela semble très bien pour l'expéditeur, où il y a seulement de l'objet de référence (GenericForeignKey) mais je ne peux pas comprendre comment aller à ce sujet pour les bénéficiaires (GenericManyToManyKey ??)

Ci-dessous est un exemple simplifié. PersonClient et CompanyClient hériter des attributs de Client, mais ont leurs propres spécificités. La dernière ligne est le point d'achoppement. Comment voulez-vous autoriser les destinataires d'un message à un ensemble de CompanyClients et 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)

62voto

elo80ka Points 4450

Vous pouvez implémenter cela à l'aide de relations génériques en créant manuellement la table de jonction entre le message et le destinataire:

 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)
 

Vous utiliseriez les modèles ci-dessus comme suit:

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

Comme vous pouvez le voir, c'est une solution beaucoup plus détaillée (compliquée?). Je resterais probablement simple et j'irais avec la solution de Prariedogg ci-dessus.

6voto

Prairiedogg Points 3594

Vous pouvez contourner ce problème en simplifiant votre schéma d'inclure un seul Client tableau avec un drapeau pour indiquer quel type de client qu'elle a été, au lieu d'avoir deux modèles distincts.

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)

Si vous faites les choses de cette façon, vous pourriez ne pas avoir à déranger les Génériques Clés Étrangères à tous. Pour plus de commodité, vous pouvez également écrire personnalisé gestionnaires pour le modèle Client comme Client.corporate.all(), Client.person.all(), de retour pré-filtrée querysets contenant uniquement le type de clients que vous souhaitez.

Ce peut aussi ne pas être la meilleure façon de résoudre votre problème. Je suis juste jeter là-bas comme une possibilité potentielle. Je ne sais pas si il y a la sagesse conventionnelle sur smashing ensemble deux modèles similaires et en utilisant un enregistrement de la remplacer pour assurer l'intégrité des données. Il semble que cela pourrait être problématique ... je vais laisser la communauté apprendre de moi sur cet un.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X