2009-05-31 20 views
37

Sto provando a creare un sistema di messaggistica in cui i mittenti ei destinatari di un messaggio possono essere entità generiche. Ciò sembra soddisfacente per il mittente, dove è presente solo oggetto di riferimento (GenericForeignKey) ma non riesco a capire come procedere per i destinatari (GenericManyToManyKey ??)Relazioni generiche molti-a-molti

Di seguito è riportato un esempio semplificato. PersonClient e CompanyClient ereditano gli attributi dal client ma hanno i loro dettagli specifici. L'ultima riga è il punto critico. Come si fa a consentire ai destinatari del messaggio di essere un insieme di CompanyClients e 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) 

risposta

50

è possibile implementare questa utilizzando rapporti generici creando manualmente la tabella di giunzione tra il messaggio e il destinatario:

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) 

usereste i modelli di cui sopra in questo modo:

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

Come si può vedere , questa è una soluzione molto più prolissa (complicata?). Probabilmente lo terrei semplice e seguirò la soluzione di Prariedogg.

+0

Wow. Questa è un'ottima soluzione. Non molto prolisso ma un grado più complicato di Prairiedogg. Grazie mille –

+0

Nel modello 'Client', non capisco perché' MessageRecipient' è in 'received_messages = generic.GenericRelation ('MessageRecipient', ...) '? Deve essere "Messaggio"? – user3595632

+1

@ user3595632 'received_messages' è una relazione molti-a-molti tra' Client' e 'Message'. Ecco perché deve essere su 'MessageRecipient', che modella esplicitamente tale relazione, poiché non esiste' GenericManyToManyField'. Ha senso? – elo80ka

4

Si potrebbe ovviare a questo problema, semplificando lo schema per includere un singolo Client tavolo con una bandiera per indicare che tipo di cliente è stato, invece di avere due modelli separati.

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) 

Se si fanno le cose in questo modo, si potrebbe non avere a che fare con Generic Foreign Keys. Come ulteriore comodità, puoi anche scrivere gestori personalizzati per il modello Client come Client.corporate.all(), Client.person.all(), per restituire i set di filtri pre-filtrati contenenti solo il tipo di client che desideri.

Anche questo potrebbe non essere il modo migliore per risolvere il tuo problema. Sto solo lanciando là fuori come una possibilità potenziale. Non so se esiste la saggezza convenzionale circa la distruzione di due modelli simili e l'utilizzo di un override di salvataggio per garantire l'integrità dei dati. Sembra che potrebbe essere potenzialmente problematico ... lascerò che la comunità mi impari su questo.

+0

Grazie @Prairiedogg. D'accordo con tutto ciò che hai detto. Sono ancora interessato a vedere se c'è una soluzione che utilizza relazioni generiche ... –

3

Il modo migliore in assoluto per andare su questo è quello di utilizzare una libreria chiamata django-gm2m

pip install django-gm2m 

Poi se abbiamo i nostri modelli

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

e un utente

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

Possiamo fare

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

Dolce giusto?

Per maggiori informazioni vai qui:

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