Django Model Bidirectional Many to Many Declaration? - python

I have two models, article and publication, in which I declare a manytomany field within Article. However, I also want to have a reference from publication to articles as well. Is the best way to just declare another ManyToManyField, i.e. Articles = models.ManyToManyField('Article'), and if so, how do I make it so that it's linked to that linking table?
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField('Publication')
class Publication(models.Model):
title = models.CharField(max_length=30)
articles = ???

You are declaring the model in wrong way
correct way should be:
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
headline = models.CharField(max_length=100)
publication = models.ForeignKey(Publication, on_delete=models.CASCADE)

Related

How to Connect a Django Model with ManyToMany Relationship?

I am making an app that is pretty much similar to google classroom in django.
I have a Course model and an assignment model, and I want to connect an assignment to the specified course.
These are my models
class Assignment(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
date_created = models.DateTimeField(default=timezone.now)
class Course(models.Model):
title = models.CharField(max_length=100)
subject = models.CharField(max_length=100)
image = models.ImageField(default='no_course_image.jpg', upload_to='course_images')
owner = models.ForeignKey(User, on_delete=models.CASCADE)
students_invited = models.ManyToManyField(User, null=True, blank=True)
assignments = models.ManyToManyField(Assignment, null=True, blank=True)
date_published = models.DateTimeField(default=timezone.now)
class Meta:
verbose_name_plural = 'Course'
ordering = ['-date_published']
def __str__(self):
return '{} - {}'.format(self.title, self.owner)
But i am getting an error when I specify the course field in the assignment model with the ForeignKey!
Could you please help me with how to connect the assignment to the Course model?
Thank you
ForeignKey is used to setup a many to one relationship. As you are trying to setup a ManyToManyField it won't work in this situation as you can see in the Django documentation
ForeignKey¶
class ForeignKey(to, on_delete, **options)¶
A many-to-one relationship. Requires two positional arguments:
the class to which the model is related and the on_delete option.
In fact you don't even need to set the relation in the Assignment Model as Django will take care of creating a third table linking the two together by their primary keys. You can see this in the documentation
from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
class Meta:
ordering = ['headline']
def __str__(self):
return self.headline
So every time you add the assignment to the course like so
>>> c1 = Course(title='Python Course')
>>> c1.save()
>>> a1 = Assignment(name='Python Assignment')
>>> a1.save()
>>> c1.assignments.add(a1)
And the relation will automatically be created and c1.assignments.all() will return all the assignments linked to the course
If you need to go the other way around then you would use a1.course_set.add(c1). When using the model that doesn't have the ManyToManyField object tied to it you need to use the *_set notation where * will be replaced by the model name in lower case. Can read more about Related Objects references in the docs here
When you try to create the Model Assignment with reference to the model Course, the Course Model has not yet created and vice versa and you will get an error either of the model is not defined
You can use the quotes for it
class Assignment(models.Model):
course = models.ForeignKey('Course', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
date_created = models.DateTimeField(default=timezone.now)
You can use a custom through model enter link description here
I guess the Course model has to be written before the Assignment model.

Using a django model as a field for another model?

I have been tasked with creating Django Models for a hypothetical apartment booking application.
My question is: can I use a model that I've defined, as a field in another model?
For example, I will have one model called "Listing" that represents an apartment being listed.
class Listing(models.Model):
address = models.IntegerField()
owner = models.CharField(max_length=256)
duration = models.DurationField()
price= models.IntegerField()
I also want to have a "Booking" model that represents an apartment once someone has booked it. It will have the exact same info as a Listing, with the addition of the username of the person who booked it. So can I have my Booking model use Listing as a field? And then just have one extra field for the booker's username.
Any other tips/critiques are highly appreciated as I am a complete beginner at Django.
I'm not 100% sure what you mean by use Listing as a field
But to me, you should be looking at the different built-in model relationships that exist in Django.
In your particular case, you will probably want to use a One-to-One relationship like so,
class Listing(models.Model):
address = models.IntegerField()
owner = models.CharField(max_length=256)
duration = models.DurationField()
price= models.IntegerField()
class Booking(models.Model):
listing= models.OneToOneField(
Listing,
on_delete=models.CASCADE,
)
username = models.Charfield()
Now if a user can book more than one apartment at a time, you'll be interested in a ForeignKey relationship like so,
class Listing(models.Model):
address = models.IntegerField()
owner = models.CharField(max_length=256)
duration = models.DurationField()
price= models.IntegerField()
class Booking(models.Model):
listing= models.ForeignKey(
Listing,
on_delete=models.CASCADE,
)
username = models.Charfield()
Note that in both examples I used Charfield for the username, feel free to use whatever Django field you need.
The concept of a model as field is odd. What you can do is establish relationships between models, or to inherit one from the other. Given your situation, you can maybe inherit Booking from Listing:
The docs on this topic.
You'll have something like this:
class Listing(models.Model):
address = models.IntegerField()
owner = models.CharField(max_length=256)
duration = models.DurationField()
price= models.IntegerField()
class Booking(Listing):
#your new fields

Django absract vs non-abstract model inheritance

Consider these two model classes Bookand Article:
class Book(models.Model):
title = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
Imagine this scenario: a book can have many annotations but an annotation can refer to only one book. Same thing for the article. I have developed two solutions:
Non-abstract solution
class Annotation(models.Model):
body = models.TextField()
article = models.ForeignKey(Article, blank=True)
book = models.ForeignKey(Book, blank=True)
class Meta:
default_related_name = 'annotations'
Pros
Code is DRY
One can access the annotations with book.annotationsor article.annotations
Cons
Waste database memory (one of the two fields article, book must be none)
Abstract classes solution
class Annotation(models.Model):
body = models.TextField()
class Meta:
abstract = True
class BookAnnotation(Annotation):
book = models.ForeignKey(Book, blank=True)
class Meta:
default_related_name = 'book_annotations'
class ArticleAnnotation(Annotation):
article = models.ForeignKey(Article, blank=True)
class Meta:
default_related_name = 'article_annotations'
Pros
Clear separation for book and article annotations
No memory waste
Cons
Does not quite match the concept of annotation (the object type should be the same)
One can access the annotations with book.book_annotationsor article.article_annotations (kind of ugly isn't it?). I can not use annotations as related name because Django prohibit that, it says reverse query name for BookAnnotation clashes with field name ArticleAnnotation.
Which is the best approach to deal with such situation?
You might consider generic relations here.
class Annotation(models.Model):
body = models.TextField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
annotated_object = GenericForeignKey('content_type', 'object_id')
class Book(models.Model):
...
annotations = GenericRelation('Annotation')
class Article(models.Model):
...
annotations = GenericRelation('Annotation')
now you can set annotation.annotated_object to any book or article instance, and in reverse do obj.annotations from both book and article objects.

How to get all objects referenced as ForeignKey from given field in a module in django

I have two classes: Author and Book. I want class Authors to have an attribute that contains all books written by the said author, as referenced to as foreign key in the class Books. The method I did does not appear to be working, which I assume is because when the database is being created in migrations, no Books objects exist yet. Or so I believe, I'm pretty new at django.
class Author(models.Model):
AuthorName = models.CharField(max_length=255, unique=True)
books = Book.objects.get(pk=object_instance.pk)
class Book(models.Model):
BookName = models.CharField(max_length=255)
Author = models.ForeignKey('Author')
The error message I get is:
NameError: name 'Book' is not defined
Which I get, is because I'm referencing to another class without actually having and instance of that class. I just can't figure out a proper way to do this.
EDIT: I reformatted it to be like this:
class Author(models.Model):
AuthorName = models.CharField(max_length=255, unique=True)
books = author.book_set.all()
class Book(models.Model):
BookName = models.CharField(max_length=255)
Author = models.ForeignKey('Author')
which yields error:
NameError: name 'author' is not defined
Maybe I should just query for the datapoints I need later on in views as opposed to creating own field for them in models though..
EDIT 2: solution from answers:
So my mistake all along was to try to add the "books" field in the author table. I guess there's no way to do this then. I can get that method to work in views so I guess this is sort of solved, although not in the way I was originally planning to do it.
doing
class Author(models.Model):
AuthorName = models.CharField(max_length=255, unique=True)
class Book(models.Model):
BookName = models.CharField(max_length=255)
Author = models.ForeignKey('Author')
and then later doing this in views:
author = Author.objects.get(pk=1)
books = author.book_get.all()
yields the wanted result (which I sort of knew beforehand, but I was trying to implement a books field in the models, which, if i correctly understood, is not possible at least not with this method).
another solution:
class Author(models.Model):
AuthorName = models.CharField(max_length=255, unique=True)
class Book(models.Model):
BookName = models.CharField(max_length=255)
Author = models.ForeignKey(Author, related_name = "books")
You don't need to create a separate field in Authors model
class Author(models.Model):
AuthorName = models.CharField(max_length=255, unique=True)
class Book(models.Model):
BookName = models.CharField(max_length=255)
Author = models.ForeignKey('Author')
You can get all books of a particular author like:
author = Author.objects.get(id=1)
books = author.book_set.all()
Learn more about backward relationships here
Just add related_name to ForeignKey and you will be able to get all books made by an author.
For example:
class Book(models.Model):
...
author = models.ForeignKey('Author', related_name='books')
...
and later...
author = Author.objects.get(pk=1)
books = author.books.all()
You did something weird in line:
books = Book.objects.get(pk=object_instance.pk)
Just delete it. You will be able to use author.book_set. You can also use related_name parameter of ForeignKey.
See the Django docs: https://docs.djangoproject.com/en/4.0/ref/models/fields/#foreignkey

Query ManyToMany relations without a named through field

I have this setup in my models:
class Author(models.Model):
name = models.CharField(max_length=100)
class Topic(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, null=True, blank=True)
topics = models.ManyToManyField(Topic, null=True, blank=True)
Given an author, I want to know which topics he wrote about:
def author_info(request, pk):
author = get_object_or_404(Author, pk=pk)
topics = ????
If I had specified a through field, I could use that, but now Django makes the through field for me, and since its supposed to be transparent, Id rather not reference the field (unless there is a proper Django construction for that).
Use Lookups that span relationships:
topics = Topic.objects.filter(article__authors=author).distinct()
Note: you have to use distinct here, because the same topic can be selected by different articles.

Categories