2009-05-31 28 views
37

Estoy tratando de crear un sistema de mensajería donde el remitente y los destinatarios de un mensaje puedan ser entidades genéricas. Esto parece estar bien para el remitente, donde solo hay un objeto de referencia (GenericForeignKey) pero no puedo encontrar la manera de hacerlo para los destinatarios (GenericManyToManyKey ??)Relaciones genéricas de muchos a

A continuación se muestra un ejemplo simplificado. PersonClient y CompanyClient heredan atributos del Cliente pero tienen sus propios detalles específicos. La última línea es el punto de fricción. ¿Cómo se permite que los destinatarios del mensaje a ser un conjunto de CompanyClients y 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) 

Respuesta

50

se puede implementar esta utilizando relaciones genéricas al crear manualmente la tabla de unión entre el mensaje y el 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) 

tendrá que utilizar los modelos anteriores de este 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 

Como se puede ver , esta es una solución mucho más detallada (¿complicada?). Probablemente lo mantendría simple y vaya con la solución de Prariedogg arriba.

+0

Wow. Esa es una gran solución. No muy detallado, pero un grado más complicado que Prairiedogg. Muchas gracias –

+0

En el modelo 'Cliente', no entiendo por qué' MessageRecipient' está en 'received_messages = generic.GenericRelation ('MessageRecipient', ...) '? ¿Tiene que ser 'Mensaje'? – user3595632

+1

@ user3595632 'received_messages' es una relación muchos a muchos entre' Cliente' y 'Mensaje'. Es por eso que tiene que estar en 'MessageRecipient', que explícitamente modela esa relación, ya que no hay' GenericManyToManyField'. ¿Tiene sentido? – elo80ka

4

Es posible solucionar este problema mediante la simplificación del esquema para incluir un solo Client mesa con una bandera para indicar qué tipo de cliente que era, en vez de tener dos modelos separados.

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 hace las cosas de esta manera, es posible que no tenga que perder el tiempo con llaves extranjeras genéricas. Como una ventaja adicional, también puede escribir administradores personalizados para el modelo de cliente como Client.corporate.all(), Client.person.all(), para devolver los conjuntos de consulta previamente filtrados que contienen solo el tipo de clientes que desea.

Esto también puede no ser la mejor manera de resolver su problema. Solo lo estoy lanzando como una posible posibilidad. No sé si existe la sabiduría convencional sobre la destrucción de dos modelos similares y el uso de una anulación de guardado para garantizar la integridad de los datos. Parece que podría ser potencialmente problemático ... Permitiré que la comunidad me conozca en este caso.

+0

Gracias @Prairiedogg. De acuerdo con todo lo que ha dicho. Todavía estoy interesado en ver si hay una solución usando relaciones genéricas ... –

3

La mejor manera absoluta de hacer esto es utilizar una biblioteca llamada django-gm2m

pip install django-gm2m 

Entonces, si tenemos nuestros modelos

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

y un usuario

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

Podemos hacer

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

Sweet right?

Para obtener más información, entra aquí:

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

Cuestiones relacionadas