Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: model has two ManyToMany relations through intermediate model

So I am new to Django, and want to describe the scenario: there are a bunch of Persons, and there are a bunch of Items, and a person passes Items to another Person.

I have the following model:

class Item(models.Model):
    title = models.CharField(max_length=1024, blank=False)

    def __unicode__(self):
        return self.title

class Person(models.Model):
    name = models.CharField(max_length=127, blank=False)
    out_item = models.ManyToManyField(
        Item,
        through='Event',
        through_fields=('from_user', 'item'),
        related_name='giver'
    )
    in_item = models.ManyToManyField(
        Item,
        through='Event',
        through_fields=('to_user', 'item'),
        related_name='receiver'
    )

    def __unicode__(self):
        return self.name

class Event(models.Model):
    item = models.ForeignKey(Item)
    from_user = models.ForeignKey(Person, related_name='event_as_giver')
    to_user = models.ForeignKey(Person, related_name='event_as_receiver')

But makemigrations tells me app.Person: (models.E003) The model has two many-to-many relations through the intermediate model 'app.Event'.

I wonder what I did wrong? Or what is a clean way to achieve the scenario? Perhaps I can separate Event into GiveEvent and ReceiveEvent? But that just makes less sense intuitively, since there is actually only a single event when item is passed.

like image 669
Lelouch Avatar asked Oct 18 '14 04:10

Lelouch


1 Answers

What you're describing sounds reasonable enough. There may be a technical reason why that's disallowed; one semantic reason is that each ManyToManyField implies the creation of a new table, and there can't be two tables with the same name (i.e. represented by the same class).

One alternative approach (shorter and more DRY) would be this:

class Person(models.Model): 
    name = models.CharField(max_length=127, blank=False)
    to_users = models.ManyToManyField(
        'self', 
        symmetrical=False, 
        related_name='from_users',
        through='Event', 
        through_fields=('from_user', 'to_user'),
    )

class Event(models.Model):
    item = models.ForeignKey(Item, related_name='events')
    from_user = models.ForeignKey(Person, related_name='events_as_giver')
    to_user = models.ForeignKey(Person, related_name='events_as_receiver')

The table structure is the same but the descriptors are different. Accessing related people is a bit easier but accessing related items is a bit harder (for example, instead of person.out_items.all() you would say Item.objects.filter(events__from_user=person).distinct()).

like image 54
Kevin Christopher Henry Avatar answered Sep 18 '22 00:09

Kevin Christopher Henry