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?
Related
As a fresh coder, I seriously have problems to build my models relations.
Please check these two cases, How can I set current_reading_pages on my Scenario2?
from django.db import models
# Scenario1: Users can record their reading progress of a book.
class Book(models.Model):
name = models.CharField(max_length=128)
class User(models.Model):
current_reading_book = models.ForeignKey('Book', on_delete=models.CASCADE)
current_reading_page = models.IntegerField()
Result1: No problems about database, but Users can records their progress of only one book.
Other scenario, which I want to build:
from django.db import models
# Scenario2: Users can record their reading progress of multiple books.
class Book(models.Model):
name = models.CharField(max_length=128)
class User(models.Model):
current_reading_books = models.ManyToManyField('Book')
# current_reading_pages = ???
I want to save all progress of current books, for example,
User A is reading 3 books, book1 till page 100, book2 till page 10, book3 till page 0.
And I found 'through' parameter in django ManyToManyField,
My codes become like below but it does not work as I expected.
from django.db import models
# Scenario3: Using through parameter in ManyToManyField
class Book(models.Model):
name = models.CharField(max_length=128)
class User(models.Model):
current_reading_books = models.ManyToManyField('Book', through='ReadingBook')
class ReadingBook(models.Model):
book = models.ForeignKey('Book', on_delete=models.CASCADE)
current_reading_page = models.IntegerField()
ERRORS:
test_model.ReadingBook: (fields.E336) The model is used as an intermediate model by 'test_model.User.current_reading_books', but it does not have a foreign key to 'User' or 'Book'.
I think, that first scenario is easy to realize.
just add user attribute into the custom class UserBook
for example
class UserBooks(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
current_page = models.IntegerField(default=1)
if user wanna check his progress, in views.py you can just filter by books = UserBooks.objects.filter(user=request.user)
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.
I have a question about how to properly model my data in Django (and later in graphene).
I have a model exam which consists of date, subject, participants, results where subject,participants, results are references to other objects. I could of course have two lists of participants and results however it would be practical to have a map of type:
pseudocode:
results= map(participant,result)
To be honest I do not know if this is even possible without introducing a additional model object participant_results
Any insight very welcome.
Benedict
I would model it this way:
class Exam(models.Model):
date = models.DateField()
subject = models.TextField()
class Participant(models.Model):
person = models.ForeignKey(Person, on_delete=models.PROTECT, related_name="participants")
result = models.FloatField(null=True, blank=True)
exam = models.ForeignKey(Exam, on_delete=models.PROTECT)
class Person(models.Model):
name = ...
You retrieve all participant from an exam using
exam.participants
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!
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.