Generic many-to-many relationships

后端 未结 3 521
無奈伤痛
無奈伤痛 2020-12-02 06:03

I\'m trying to create a messaging system where a message\'s sender and recipients can be generic entities. This seems fine for the sender, where there is only object to refe

相关标签:
3条回答
  • 2020-12-02 06:43

    You might get around this problem by simplifying your schema to include a single Client table with a flag to indicate what type of client it was, instead of having two separate models.

    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)
    

    If you do things this way you might not have to mess around with Generic Foreign Keys at all. As an added convenience you can also write custom managers for the Client model like Client.corporate.all(), Client.person.all(), to return pre-filtered querysets containing only the type of clients that you want.

    This also may not be the best way of solving your problem. I'm just throwing it out there as one potential possibility. I don't know if there's conventional wisdom about smashing together two similar models and using a save override to ensure data integrity. It seems like it could be potentially problematic ... I'll let the community learn me on this one.

    0 讨论(0)
  • 2020-12-02 06:50

    The absolute best way to go about this is to use a library called django-gm2m

    pip install django-gm2m
    

    Then if we have our models

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

    And a user

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

    We can do

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

    Sweet right?

    For more info go here:

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

    0 讨论(0)
  • 2020-12-02 06:53

    You can implement this using generic relationships by manually creating the junction table between message and recipient:

    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)
    

    You'd use the above models like so:

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

    As you can see, this is a far more verbose (complicated?) solution. I'd probably keep it simple and go with Prariedogg's solution below.

    0 讨论(0)
提交回复
热议问题