Filter based on mutually related models - django

I have Three models
class Song(models.Model):
title = models.CharField(max_length=100)
bandname = models.ManyToManyField(Band)
albumname = models.ManyToManyField(Album)
class Band(models.Model):
title = models.CharField(max_length=100)
class Album(models.Model):
title = models.CharField(max_length=100)
Now I wants to list all albums for a particular band.
I've tried this way, in view.py
band = Band.objects.get(id=no)
song_list = band.song_set.all()
album = [i.bandname for i in song_list]
It's not working. Please help me.

It seems what you're actually trying to get is a list of all the albums that contain songs by a particular band, either exclusively or in duet. Here's how you could get that using your existing models:
band = Band.objects.get(id=target_band_id)
song_list = Song.objects.filter(bandname=band)
album_list = Album.objects.filter(song__in=song_list).distinct()
Check "Reverse m2m queries" within the Django Many to Many docs.
I would also suggest re-naming bandname and albumname to simply band and album because they are referring to band or album model instances, not the actual names of the band or album. That would get confusing later on if you wanted to add a name field to either the Band or Album models.

Related

How to choose one item in many to many relationship as special?

What is the best practice to choose one item from a m2m relationship?
Lets say I've got an album of photos:
class Photo(models.Model):
img = models.FileField()
class Album(models.Model):
photos = models.ManyToManyField("Photo")
But now I also want to pick one photo as a cover. I could use a Foreign Key in Album to one Photo, but then I'd always need to check whether this photo is actually in the photos of that album. Is there a better way?
Sorry for the basic question I just somehow can't find the right words to google it.
Thanks,
Michael
You can make a custom through=… model [Django-doc] with a boolean cover that is set to True in case it is the cover item:
from django.db.models import Q, UniqueConstraint
class Photo(models.Model):
img = models.FileField()
class Album(models.Model):
photos = models.ManyToManyField(Photo, through='AlbumPhoto')
class AlbumPhoto(models.Model):
photo = models.ForeignKey(Photo, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=models.CASCADE)
cover = models.BooleanField(default=False)
class Meta:
constraints = [
UniqueConstraint(fields=['photo', 'album'], name='no_photo_multiple_times'),
UniqueConstraint(fields=['album'], condition=Q(cover=True), name='one_cover_per_album'),
]
The first constraint guarantees that you can not add the same Photo multiple times to the same Album, whereas the second condition gurantees that each Album has at most one Photo for which cover=True.

How to retrieve related instances without FK using one query in django

Imagine there are three models named Movie, Actor, and Participation.
class Movie(models.Model):
identifier = models.CharField()
class Actor(models.Model):
name = models.CharField()
class Participation(models.Model):
movie_identifier = models.CharField()
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Let's assume that I can't use ForgeinKey for the movie in the Participation model.
how can I retrieve all the participation records of a movie with only one query?
Here is the solution if I had a foreign key for the movie in the participation table:
qs = Movie.objects.filter(identifier="an_identiier").prefetch_related("participations_set")
How can I do this without having a Movie foreign key in the Participation model?
Thanks!
One of the most important things when designing a database (hence when designing your models) is database normalization [Wikipedia].
You talk about Participation being related to multiple models like Movie, Series, Episode, etc. this means that Movie, Series, Episode all can be said to have something in common or they can be said to be a specialization of another entity let us say Participatable for the lack of a better word, or we can say Participatable is a generalization of Movie, Series, Episode, etc.
How do we model these? Well we will just have an extra model that our other models will have a OneToOneField with:
class Participatable(models.Model):
# Any common fields here
MOVIE = 'M'
SERIES = 'S'
TYPE_CHOICES = [
(MOVIE, 'Movie'),
(SERIES, 'Series'),
]
subject = models.CharField(max_length=1, choices=TYPE_CHOICES)
class Movie(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='movie',
)
class Series(models.Model):
# uncommon fields
participatable = models.OneToOneField(
Participatable,
on_delete=models.CASCADE,
related_name='series',
)
class Participation(models.Model):
participatable = models.ForgeinKey(Participatable, on_delete=models.CASCADE)
actor = models.ForgeinKey(Actor, on_delete=models.CASCADE)
Other than this solution which I find is the best for such modelling you can go with using the content-types framework which will essentially do what you do currently. That is it will use a field that stores the related id and also a foreign key that points to an entry in a table that will simply describe which table this id is for.

Django - selecting the related objects from the reverse model - e.g reverse select_related

I have the following models (just for example sake):
class Song(models.Model):
name = CharField()
album = ForeignKey(Album, related_name='songs')
class Album(models.Model):
name = CharField()
genre = ForeignKey(Genre, related_name ='albums')
class Genre(models.Model):
name = CharField()
I'm looking for a way to fetch all songs related to albums that relate to a specific (filtered) genres or genre, while doing the joins in the DB and not "in memory".
I know I can use the select_related clause to go the otherway around (following the good example for this blog post:
songs = Song.objects.select_related("album").select_related('genre').all()
and the the queryset would already include all the genre properties on the song album without hitting the DB again.
My question is how to this the other way around: start from a specific (or filtered) genre and get all songs -d own the foreign key chain.
Would
Song.objects.select_related("album").filter(album__genre=GENRE_YOU_WANT)
be what you want? For normal foreign keys, should should use just one query.
You can get all songs related to a genre by using a filter:
genre = Genre.objects.get(name='pop')
songs_in_genre = Songs.objects.filter(album__genre=genre)
If you want to fetch albums, and all their related songs at once, then use prefetch_related:
albums = Album.objects.filter(genre="rock").prefetch_related('songs')

Django modeling with filmography

I'm still a beginner in django, and I'm working on a small project.
Lets say I have a list full of actors/actresses along what films they've been in. I also have another list of films with specific details of the film such as date of release, casts, credits etc.
The main goal of the app is to be able to search a specific person and pull up the details of all their films.
So for the models right now I have:
class Person(models.Model):
name = models.TextField()
class Film(models.Model):
name = models.TextField()
year = models.IntegerField
The Film model will have a lot more information, but I'm not exactly sure how to link the two. For example I want a user to be able type in "Tom Hanks" and then have the details for his 5 most recent movies displayed.
So for the Person model should I add some field that has a list of their films as foreign key somehow, or is there a better way?
Thanks for the help.
models.py
class Person(models.Model):
name = models.TextField()
class Film(models.Model):
name = models.TextField()
year = models.IntegerField()
actors = models.ManyToManyField('Person')
and then access the film actors using the following command
#first get the film from db
f = Film.objects.all()[0] #get the first Film entry
actors = f.actors.all()

Django: distinct QuerySet based on a related field

In my Django app I allow users to create collections of movies by category. This is represented using 3 models, Movie, Collection, and Addition (the Addition model stores movie, collection, and user instances). Simplified versions of all three models are below.
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
class Addition(models.Model):
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
collection = models.ForeignKey(Collection)
So for example a user could create a collection called "80's movies", and add the movie "Indiana Jones" to their collection.
My question is: how do I display a distinct list of movies based on a set of query filters? Right now I am getting a bunch of duplicates for those movies that have been added to more than one collection. I would normally use distinct() to get distinct objects, but in this case I need distinct movies rather than distinct additions, but I need to query the Addition model because I want to allow the user to view movies added by their friends.
Am I setting up my models in an optimal way? Any advice/help would be much appreciated.
Thanks!
First. I don't think you need Addition model here. You try to create many-to-many relation, but there's documented way of doing this:
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
movies = models.ManyToManyField('Movie', blank=True, null=True)
Second. The documentation says: "To refer to a "reverse" relationship, just use the lowercase name of the model".
So the answer is (for the setup above):
Movie.objects.filter(collection__user=user).distinct()