ManyToManyField or ForeignKeys in an intermediary model? - django

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.

Related

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?

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

Django models asymmetric relationship

With following Django models:
class Author(models.Model):
name = models.CharField(max_length=30)
bestbookaccordingtome=models.????(Author,null=True, blank=True, default = None)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.OnetoOneField(Author)
I want the classic relationship each book has one author (only).
But I also want to be able to assign a chosen book to author (my favourite book of this author for example).
I tried a foreign key but django didn't like it.
Any python clean way to do it?
What you need is ManyToManyField.
class Author(models.Model):
name = models.CharField(max_length=30)
bestbookaccordingtome = models.ManyToManyField('self', symmetrical=False, related_name='best_book_according_to_me')
Also, if you need to specify extra fields in your n-m model, you can use through to indicate the name of the model.
Hope it helps!

Understanding ManyToMany fields in Django with a through model

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.

How do I write a Django model with ManyToMany relationsship with self through a Model

I want to have a model with a ManyToMany relationship with itself, I don't know how to write this but I'l try to write some code to illustrate what I want to do.
class Person(models.Model):
name = models.CharField()
occupation = models.CharField()
friends = models.ManyToManyField('self', through = PersonFriends)
My Model that I want the friends to go through
class PersonFriends(models.Model)
???
comment = models.CharField()
In a ManyToMany field with through relationship if the other model's name was "Pet" for example I'd name my fields in that through class person and pet and make them models. ForeignKey(Person) and Pet for example
What to I name my fields in my PersonFriends model for the two person-fields now that they are the same model?
You can do something like this:
class Person(models.Model):
name = models.CharField(max_length = 255)
occupation = models.CharField(max_length = 255)
friends = models.ManyToManyField('self', through = 'PersonFriends',
symmetrical = False)
# ^^^^^^^^^^^
# This has to be false when using `through` models. Or else your
# model will not validate.
class PersonFriends(models.Model):
source = models.ForeignKey(Person, related_name = 'source')
# ^^^^^^^^^^^^
# You need different `related_name` for each when you have
# multiple foreign keys to the same table.
target = models.ForeignKey(Person, related_name = 'target')
comment = models.CharField(max_length = 255)
Everything is described in the official docs for ManyToManyField.through_fields (you can search for 'recursive relationships' phrase there to quickly find what you need):
for django 1.11 you have to specify through and (!) through_fields arguments:
class Person(models.Model):
name = models.CharField(max_length=50)
# note the additional arguments here
friends = models.ManyToManyField(
'self',
# recursive relationships to self with intermediary
# through model are always defined as non-symmetrical
symmetrical=False,
through='PersonFriend',
# this argument is required to define a custom
# through model for many to many relationship to self
# position matters: 1 - source (from), 2 - target (to)
through_fields=('person', 'friend'),
)
class PersonFriend(models.Model):
# required relationship-defining foreign keys
# (note that the order does not matter, it matters
# in 'through_fields' argument in 'friends' field of the 'Person' model)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
friend = models.ForeignKey(Person, on_delete=models.CASCADE)
# additional fields
comment = models.CharField()
Without assuming that friendships are symmetrical. Because Buzz Lightyear might be Woody's friend, but Woody isn't friends with Buzz Lightyear till near the end of the film. You can simplify both models and still have reasonable lookup names. You would of course need to make sure that you define two PersonFriends if it's a good friendship.
class Person(models.Model):
name = models.CharField()
occupation = models.CharField()
class PersonFriends(models.Model):
from_person = models.ForeignKey(Person, related_name='friends_with')
to_person = models.ForeignKey(Person, related_name='friends')
comment = models.CharField()
class Meta:
unique_together = ('from_person', 'to_person')
This has the added bonus of a comment for each direction of the friendship. i.e. Tyrion thinks Sansa is a lovely and intelligent, but lost girl. Whereas Sansa might think that Tyrion is an ugly but clever and kind-hearted kinda guy.
class PersonFriends(models.Model):
from_person = models.ForeignKey(Person, related_name='from_person')
to_person = models.ForeignKey(Person, related_name='to_person')
this is from db table structure of a ManyToMany relation to self from my Model structure. Django defines it like that..