Understanding ManyToMany fields in Django with a through model - django

I'm having trouble understanding the use of ManyToMany models fields with a through model. I can easily achieve the same without the ManyToMany field. Considering the following from Django's docs:
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
What I don't understand, is how is using the ManyToMany field better than simply dropping it and using the related manager. For instance, the two models will change to the following:
class Group(models.Model):
name = models.CharField(max_length=128)
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='members')
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
What am I missing here?

You're right, if you define the membership table explicitly then you don't need to use a ManyToManyField.
The only real advantage to having it is if you'd find the related manager convenient. That is, this:
group.members.all() # Persons in the group
looks nicer than this:
Person.objects.filter(membership_set__group=group) # Persons in the group
In practice, I think the main reason for having both is that often people start with a plain ManyToManyField; realize they need some additional data and add the table explicitly; and then continue to use the existing manager because it's convenient.

So I just wanted to add to anyone who is looking at this and may want another example to save them research. For one, I think it's important to note that in OP's questions, he should of removed the Group model not the People model and removed the matching field from the Membership model. That way, the model goes back to it's original meaning.
When looking at a many-to-many relationship, the through field can almost be contrived as the "why" to the many-to-many relationship. If we give the nomenclature a different name, it might change what the reader sees:
class Person(models.Model):
name = models.CharField(max_length=128)
class Club(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='RegistrationReceipt')
class RegistrationReceipt(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
club = models.ForeignKey(Club, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
paid_dues = models.BooleanField(default = True)
fee_payment_date = models.DateTimeField()
Now, you can imagine yourself adding all sorts of logic whenever a member joins this club. When they joined? Why did they join? Did they pay? When is their payment date? etc. You can obviously tackle this relationship in different ways, but you can see more clearly the use of "through" in a Many-to-Many relationship.
Also, for those that know SQL. The through attribute/field is the way you customize the intermediary table, the one that Django creates itself, that one is what the through field is changing.

I have some problem with the answer from Kevin Christopher Henry.
I don't think that the equivalent of the group.members.all() without a through="members" is Person.objects.....
Instead I think it is group.person_set.all() if the M2M field is on Person side. Or group.persons.all() if the M2M field is inside Group.
But I think without through=.. you have no control over the created table. It contains and will contain just 2 fields: both ID's of the related rows.
But with through=.. you can create the model yourself and add (now or later) the additional fields, which often can have a good reason. Example of such field: valid_from = DateField(), or so.

Related

ManyToManyField or ForeignKeys in an intermediary model?

I don't understand why should I use ManyToManyField if I will (or might) store extra information along the many-to-many relationships.
The doc here shows this example:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
Why even having the members field there, if the developer already defines the relationship model and also defines the ForeignKeys? :D Why not leaving out the members ManyToManyField ?? What is the benefit? Thank you.
I don't understand why should I use ManyToManyField if I will (or might) store extra information along the many-to-many relationships.
You do not need to do this. But it makes querying more effective. Indeed, you can now query with:
Group.objects.filter(members=some_person)
this is shorter and more descriptive than working with:
Group.objects.filter(membership__person=some_person)
It is thus a "coding shortcut" to go from the Group model to the Person model and vice versa.

django: Can not find the Data table row of the Extra field on many-to-many relationships?

Hello i am trying to understand Django Data Models and it´s possibilities. After using one-to-one and m2m i am now trying to understand the m2m extra fields. So i followed the Example of the Django Doc and populated the models with some Data.
models.py
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
So far everything is fine, but as i looked into the model tables, via pgAdmin 4 ( i am using PostgreSQL), i could not find any members column in the Group model. So i made a little visual of my model and some screenshots of pgAdmin, for better understanding.
As you can see Group has only a id and name column.
Question
my question: Is this m2m members relation of Group, only some kind of "virtual" created relation via the Membership.person.fk and Membership.group.fk? Meaning there is no field which will be populated? I try to imagine that it works like a recursive, instance query?

How to create a shared model in Django?

I have a couple of models that have need of a common set of fields. It is basically a set of various different types of contacts:
# models I would like to share
class Address(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
street1 = models.CharField(_('street1'), max_length=125, blank=True)
street2 = models.CharField(_('street2'), max_length=125, blank=True)
city = models.CharField(_('city'), max_length=50, blank=True)
state = models.CharField(_('state'), max_length=2, blank=True)
zip_code = models.CharField(_('zip_code'), max_length=10, blank=True)
class Phone(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
phone = models.CharField(_('phone'), max_length=50, blank=True)
# these are the models that I would like to have addresses and phone numbers
class Contact(models.Model):
name = models.CharField(_('name'), max_length=50, blank=False)
class Organization(models.Model):
name = models.CharField(_('name'), max_length=255, blank=False)
email = models.CharField(_('email'), max_length=100, blank=True)
website = models.CharField(_('website'), max_length=255, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.CharField(_('photo'), max_length=255, blank=True)
I would like to share the Phone and Address models with the Contact, Organization and UserProfile models. My first attempt was to add ForeignKey on each of Contact, Organization and UserProfile but after more research I believe this is backwards, so I moved the ForeignKey to Address and Phone but then discovered that ForeignKey can belong to one and only one model. In addition to sharing this data structure between multiple different contact types, I would like the ability to add more than one address or phone number to a contact. A contact could have a home address, work address, mobile number, and work number for example. So I have basically 2 questions:
1) Is sharing a model in this way a reasonable thing to do?
2) How would I go about setting up the models?
If I've understood correctly; you want your Contact, Organization, and UserProfile models to all have fields for Address and Phone. Moreover, they can each have more than one address/phone.
Is this reasonable? Sounds so to me.
How could you go about setting up the models? Generic Relations spring to my mind.
Consider just Contact and Address for now. Your second attempt is correct: we have a Many-to-one relationship (one contact, many addresses), so we need to make Contact a ForeignKey field in the Address model, like so:
class Address(models.Model):
<other_fields>
contact = models.ForeignKey('Contact', on_delete=models.CASCADE)
This allows you to assign multiple addresses to each contact.
Moving on, you essentially want your Address model to have multiple foreign keys: Contact, Organization, and UserProfile. One way of achieving this is to use Generic Relations. This makes use of Django's built-in "contenttypes" framework, and allows you to create GenericForeignKey fields that point to more than one model. I encourage you to read the docs linked, since generic relations aren't so trivial. In the end, you'll have something like:
class Address(models.Model):
label = models.CharField(max_length=50, blank=True)
street_1 = models.CharField(max_length=125, blank=True)
street_2 = models.CharField(max_length=125, blank=True)
etc...
models_with_address = models.Q(app_label='app_name', model='contact') | \
models.Q(app_label='app_name', model='organization') | \
models.Q(app_label='app_name', model='userprofile')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=models_with_address)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
With this, you can create several addresses for each of the models specified in the models_with_address query. To be able to query the addresses for a given contact/organization/etc., you'll need a reverse generic relation. Setting this up involves adding the line address = GenericRelation(Address) to the respective models.
For further generalisation, you could create a ContactableModel (or whatever) class:
class ContactableModel(models.Model):
address = GenericRelation('Address')
phone = GenericRelation('Phone')
Any model with an address and phone number (Contact, Organization, etc.) could then inherit this so that you don't have to repeatedly include those two fields. You could also improve the models_with_address limit, so that we have something like limit_choices_to=<subclasses_of_ContactableModel>.
Hope this helps!
A simple solution would be to add ManyToManyField(Address|Phone) (i.e. two M2M fields)
to Contact, Organization and UserProfile.
But that would mean different contacts/organizations/users could share addresses.
While this looks tempting, the problem with such a solution is that if someone
edits an address of a contact, they also change the address for all the remaining objects in the system!
Another possible solution which avoids the above problem
and doesn't require M2M fields or generic relationships would be to use
multi-table inheritance:
class Address(models.Model):
entity = models.ForeignKey(Entity)
...
class Entity(models.Model):
pass
class Contact(Entity):
...
class Organization(Entity):
...
(Under the hood Django creates implicit OneToOneField-s
that point from Contact & Organization to Entity.)

Django modify bridge table

I have two models in my django application, event/user, that have a many-to-many relationship.
Event can have many attendees(user).
User can attend many events.
Django automatically made a bridge table for for this relationship.
My question is how can I add new fields to this table using migrations as there is not a model for this Bridge table.
This can be done by creating a "through" table and adding the fields on it. Check out the docs at https://docs.djangoproject.com/es/1.10/topics/db/models/
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
This bridge table is called association table. Imo it is bad practice to edit this table that way. Connecting models by many to many field suggest that there is no model between. If your association table is supposed to represent additional data then you should create new model
class NewModel(models.Model):
attender = models.ForeignKey(User, null=False)
event = models.ForeignKey(Event, null=False)
# additional fileds
and remove many to many relationship from event

Checking integrity of ManyToManyField

I have a Book model that stores the authors as a ManyToManyField to Person through table Author. The intermediate Author table exists because it adds the order property:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ManyToManyField(Person, through='Author')
class Author(models.Model):
book = models.ForeignKey(Book)
person = models.ForeignKey(Person)
order = models.PositiveSmallIntegerField()
What I want is to verify that, for a given book, order starts with 1 and is contiguous. Where can I do this check? I can't do it in Book.save(), because first you need to save the book, then you add authors to it (that's how ManyToManyFields work AFAIU). I can't do it in Author.save(), because it is normal that there is temporary loss of integrity (e.g. if there are 4 authors, I delete author with order=3, then I update the last author setting order=3; there is loss of integrity between the two steps).
I guess the integrity should be checked on commit. Is there any standard or recommended practice for doing this?