Django - ForeignKey to model with multiple choices - python

Sorry if the title is a bit unclear, I am a beginner and I have a few questions about database design.
I am building django app that would store info about a Book and another class Category that contains a list of genres. I would like to connect these tables so that one could choose category when inserting the book, however the categories are not showing up (I'm using django forms). Do you maybe know why?
CATEGORIES = (('Thriller', 'Thriller'), ...)
class Category(models.Model):
name = models.CharField(max_length=40, choices=CATEGORIES, default='Unknown')
class Book(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
title = models.CharField(max_length=200)
.......
category = models.ForeignKey(Category, on_delete=models.CASCADE)
Or should I just include category inside the Book class and not connect them with ForeignKey? What would be the pros and cons of this? I already changed my DB few times splitting things up and again just putting it back to Book class as I don't really know what is better design in this situation.
Thanks

Related

Is it possible to use a related field as a choice field in Django?

In Django, if I have something like this:
class Library(models.Model):
name = models.CharField(...)
address = models.CharField(...)
book_of_the_week = ?
class Book(models.Model):
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name="books")
name = models.CharField(...)
This gives me the ability to create multiple libraries, each with a large number of books.
For book_of_the_week, I want this to be a reference to a Book instance, so that in Django Admin, the field is represented as a dropdown that lets you select from the books in the current library, and in code, you can use .book_of_the_week to access a specific instance of Book.
Is this possible?
Sure, it's possible. But if you do this, you'll only ever be able to save the current book of the week. What happens if you want to show a library's book-of-the-week history? Seems like a likely scenario to me. Consider doing something like:
class Library(models.Model):
name = models.CharField(...)
address = models.CharField(...)
book_of_the_week = ?
class Book(models.Model):
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name="books")
name = models.CharField(...)
class BookOfTheWeek(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='book_of_the_week')
library = models.ForeignKey(Library, ...etc)
week_of = models.DateField()
In this manner every book of the week will be a discrete database record, and you'll be able to track a history of each library's book of the week over time.
However...
That said, if you do not need to do this and a single instance record is fine, you should be able to create a ForeignKey relation from Library to Book. However, you may run into some circular and/or hierarchical reference issues depending on how where your model classes are written. In the example you show above, you'd need to declare your ForeignKey model name as a string, like so:
book_of_the_week = models.ForeignKey('Book', on_delete ... etc)
instead of:
book_of_the_week = models.ForeignKey(Book, on_delete ... etc)
...otherwise Django will throw an error because the Book model class is referenced before it is defined. Making 'Book' a string will let Django parse the full models.py file, build the logic, and avoid the error.
Sidenote:
Depending on your specific case you may also consider a ForeignKey to self, like so:
class Book(models.Model):
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name="books")
name = models.CharField(...)
book_of_the_week = models.ForeignKey('self', on_delete=models.SET_NULL, ...etc)
This would allow any Book on the database table to reference another single Book instance.

Django - How to dynamically restrict category of blog post?

I'm working on a blog project. I want to set primary and secondary categories for the posts. For example,
primary category: Music, Movie, Computer
and secondary categories for each primary category like:
Music - Dance, Rock, Country /
Movie - Script, Teaser, Review /
Computer - Hardware, Software /
And when I create a new post I want to restrict secondary category choices according to the primary category I chose.
(to be precise, in the post creation form,
first, I want both primary and secondary categories to be shown in dropdown menus, and
second, after I choose a primary category I want only the secondary category choices which belong to the primary category I chose to be shown in the dropdown menu for secondary category.)
Currently my models.py:
class PrimaryCategory(models.Model):
title = models.CharField('Primary Category', max_length=50)
class SecondaryCategory(models.Model):
title = models.CharField('Secondary Category', max_length=50)
primary = models.ForeignKey(PrimaryCategory,on_delete=models.CASCADE)
class Post(models.Model):
title = models.CharField(max_length=256)
content = models.TextField()
create_date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
primary_category = models.ForeignKey(PrimaryCategory, on_delete=models.CASCADE, null=True)
secondary_category = models.ForeignKey(SecondaryCategory, on_delete=models.CASCADE, null=True)
and I searched and I can maybe use ForeignKey.limit_choices_to in my ModelForms, but I'm stuck here. Could anybody kindly help writing my forms.py?
currently I only have forms.py like:
from django import forms
from myblog.models import Post,PrimaryCategory,SecondaryCategory
class PostForm(forms.ModelForm):
secondary_category = models.ForeignKey(
PrimaryCategory,
on_delete=models.CASCADE,
limit_choices_to= ??? ,
)
class Meta:
model=Post
to summerize:
how to show category choices in dropdown menu instead of free text input?
how to dynamically restrict secondary category choices according to primary category choice in the form?
(Added) 3. I have no working knowledge of javascript, so I want to do it without JS if possible..If it is not possible to change choices dynamically, then is it at least possible to make the form to give me an error message if secondary category choice I made is not under chosen primary cagetory? I think I'll work on it but any advice would be of great help.
I would appreciate any help. Thank you.
I googled and found exactly what I wanted here:
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html
It needs jQuery and I actually didn't fully understand what's happening in the jQuery part of the solution, but otherwise everything's clear and it works perfect!

What field Django used in FOO_set?

I am little bit comfused. Lets say I have such models.
models.py:
class Company(models.Model):
name = models.CharField(blank=False, null=False)
class Game(models.Model):
developer = models.ForeignKey(Company, on_delete=models.CASCADE)
publishers = models.ManyToManyField(Company)
If I use next code:
current_company = Company.object.get(pk=1)
current_company.game_set.all()
as I understand it return all games of current_company, but what field (developer or publishers) Django used?
But this code wouldn't be valid, for precisely this reason. If you tried to run it, Django would tell you that there was a conflict in the reverse relation.
If you have two relationships pointing to the same model, you need to explicitly set related_name on one of them to avoid this conflict.

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

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