order_with_respect_to for reverse relationship - django

Suppose I have the following models:
class Playlist(models.Model):
name = models.TextField()
songs = models.ManyToManyField(Song)
class Song(models.Model):
title = models.TextField()
So a Playlist can have multiple songs and a song can be in multiple playlists.
I want to add an "order" field so that a user can reorder songs in a playlist. I found order_with_respect_to, which appears to be a perfect solution. However, I would need to add that meta option to the Song model, e.g.:
class Song(models.Model):
title = models.TextField()
class Meta:
order_with_respect_to = 'playlists'
Obviously, there is no playlist field specified on Song since I've got the relationship specified in the Playlist model. Is there a way to specify the reverse relationship for order_with_respect_to using related_name? Or could I slap a ForeignKey reference to playlists in the Song model?

order_with_respect_to is (arguably) useful when you have a ForeignKey, but not a ManyToManyField. It works by adding an extra field to the model, but in your case the order varies depending on the Playlist.
The straightforward way to do this in Django is to put the order on an explicitly created through table.
class Playlist(models.Model):
name = models.TextField()
songs = models.ManyToManyField(Song, through='PlaylistSong')
class Song(models.Model):
title = models.TextField()
class PlaylistSong(models.Model):
playlist = models.ForeignKey(Playlist)
song = models.ForeignKey(Song)
order = models.PositiveSmallIntegerField()
So to get the Songs in a Playlist in the right order, you would do something like:
Song.objects.filter(playlistsong__playlist_id=1)
.order_by('playlistsong__order')

Related

How to filter foreign key field by text input in django-filter

I have a City model which contains names of all cities of my country. Also I have Post model with foreign key set to City model. And I created filter to let user filter Posts by city name:
class PostFilter(django_filters.FilterSet):
class Meta:
model = Post
fields = ['city']
And the problem is that, basically foreign key field contains id of related object and filter will find nothing if user inputs actual city name. So, how do I make it so that when user enters city name, it actually works?
Edit:
My models:
class Cities(models.Model):
name = models.CharField(max_length=63)
class Post(models.Model):
city = models.ForeignKey(Cities, on_delete=models.RESTRICT, null=True, blank=False)
You can filter with:
class PostFilter(django_filters.FilterSet):
city = django_filters.CharFilter(field_name='city__name')
class Meta:
model = Post
fields = []
You can of course add extra fields on which you wish to filter.

Limit options of Django ManytoManyField

I have an app that has 3 models: Podcasts, Episodes, and Categories.
Categories have a ForeignKey for Podcast:
class Category(models.Model):
...
podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
...
So, a podcast can have a bunch of associated categories.
Each episode can be associated with a podcast's category. I've added that as a ManyToManyField:
class Episode(models.Model):
podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
categories = models.ManyToManyField(Category)
...
The problem is, in the admin, and modelforms, the categories field shows a list of every category for every podcast, not just the ones associated to this podcast.
How can I use limit_choices_to to only limit categories with the foreign key of the same podcast the episode is related to? I've tried the following, but obviously doesn't work because 'self' is not defined.
categories = models.ManyToManyField(Category, limit_choices_to={'podcast': self.podcast})
I'd like to do this at the model level if possible so I dont have to add additional logic around the rest of the app.
Thanks for any help!
To me it seems like the case when you would normally want to use ManyToMany relationship with an intermediate model (Podcast in this case). Unless you have to keep your relations as they are, consider this solution:
class Category(models.Model):
title = models.CharField(max_length=120)
class Episode(models.Model):
title = models.CharField(max_length=120)
categories = models.ManyToManyField(Category, through='Podcast')
class Podcast(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
episode = models.ForeignKey(Episode, on_delete=models.CASCADE)
title = models.CharField(max_length=120)
https://docs.djangoproject.com/en/3.0/topics/db/models/#extra-fields-on-many-to-many-relationships

Django QuerySet for ManytoMany relation using Bridging Table

I am new to Django and was looking for an efficient way to retrieve and post to a ManytoMany relation using an intemrediary table.
models:
from django.db import models
# Create your models here.
class Actor(models.Model):
name = models.CharField(max_length = 50, primary_key = True)
bio = models.CharField(max_length=150)
class Movie(models.Model):
name = models.CharField(max_length=100)
release = models.CharField(max_length=100)
class NormActors(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
I have checked the documentation and it got confusing, so any reference would be helpful as well. If on request, I want to send -
for /movies: get list of all movies + details with the actor names in the respective movies
for /actors: get list of all actors + details with the movies they have been in
Should the ORM query look something like this?
actor = Actor.objects.get(name="XYZ")
movies = actor.movie_set.all()
How should I go about fr the first one?
Firstly, you should explicitly declare the m2m and identify the through table as such:
class Movie(models.Model):
...
actors = models.ManyToManyField('Actor', through='NormActor')
Now for any movie you can do movie.actors.all(), and for any actor you can do actor.movie_set.all().
Note, if you only have those two fields on NormActor, you don't actually need to declare it explicitly; you can remove that model and the through attribute and Django will manage it for you, with the added bonus that in the admin interface you can now edit actors inline with movies.
To help me avoid confusion, I always call a "through" table by the name of 2 other tables. Thus, in your case, I would have done:
class Actor(models.Model):
name = models.CharField(max_length = 50, primary_key = True)
bio = models.CharField(max_length=150)
movies = models.ManyToMany('Movie', through='ActorMovie',
related_name='actors')
class Movie(models.Model):
name = models.CharField(max_length=100)
release = models.CharField(max_length=100)
class ActorMovie(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
Notes:
the use of related_name is important and imagine it's the reverse direction: you declared the ManyToMany in Actor so reverse is Movie -> Actor
'Movie' in quote because it's declared after this declaration. You can remove quotes but then declare Movie class first.
same for ActorMovie
with a ManyToMany declared this way you can access in both directions (= from Actor but also from Movie) in a very clear way, for example two working and very clean queries.
Examples:
# get all film with Harrison Ford:
for movie in Actor.objects.filter(name="Harrison Ford").movies.all():
pass # do whatever with movie
# get all actors from Die Hard movie:
for actor in Movie.objects.filter(name__istartswith="Die Hard").actors.all():
pass # do whatever with actor
If you want to get all the movies related to an actor, you need to have ManyToMany relationship with Movie model with respect to Actor model.
class Movie(models.Model):
name = models.CharField(max_length=100)
release = models.CharField(max_length=100)
class Actor(models.Model):
name = models.CharField(max_length = 50, primary_key = True)
bio = models.CharField(max_length=150)
movies = models.ManyToManyField(Actor)
Remember, you need to define Movie model before you creating a manytomany relationship with the Actor model.
actor = Actor.objects.get(name="XYZ")
movies = actor.movies.all()

Django: modelling relations

In my app, there is a model "Image" and couple of models that an image can relate to (Event, Place, Member). I am considering two ways of modelling the image relations. The first one is to put relation field in Image for each related model (the field will be either ForeignKey or ManyToManyField). The second way is to put field "images" to each model that can have associated images (in case of some models it will be just one image). Which way is more recommended?
# the first way
class Member(models.Model):
name = models.CharField('name', max_length=128)
class Event(models.Model):
name = models.CharField('name', max_length=128)
class Place(models.Model):
name = models.CharField('name', max_length=128)
class Image(models.Model):
title = models.CharField('title', max_length=128)
members = models.ManyToManyField(Member)
place = models.ForeignKey(Place)
event = models.ForeignKey(Event)
# the second way
class Image(models.Model):
title = models.CharField('title', max_length=128)
class Member(models.Model):
name = models.CharField('name', max_length=128)
images = models.ManyToManyField(Image)
class Event(models.Model):
name = models.CharField('name', max_length=128)
images = models.ManyToManyField(Image)
class Place(models.Model):
name = models.CharField('name', max_length=128)
images = models.ManyToManyField(Image)
I could also use generic relations (third way) or I could make models like EventImage, PlaceImage, MemberImage (fourth way) but I have already decided these would not work for me that well.
If your relationship is Many-to-one, then the proper way to model it is with ForeignKey.
For Many-to-many relationships, it's less clear on which model to define one. Django documentation says the following:
It doesn’t matter which model has the ManyToManyField, but you should
only put it in one of the models – not both.
Generally, ManyToManyField instances should go in the object that’s
going to be edited on a form. In the above example, toppings is in
Pizza (rather than Topping having a pizzas ManyToManyField ) because
it’s more natural to think about a pizza having toppings than a
topping being on multiple pizzas. The way it’s set up above, the Pizza
form would let users select the toppings.

OneToOneField QuerySet returning empty dict

I have 2 models called Manufacturer and Car . The Car model has a foreignKey to Manufacturer which mean a many cars can belong to a single Manufacturer.
In the model manfacturer , their is an OneToOneField called showcase which allows a Manufacturer to have a single car to showcase,
How can I show all the Manufacturers which have a car to showcase which mean , show all Manufacturer with a OneToOneField objects.
I tried Manufacturer.objects.filter(showcase=True) but it return an empty dictionary []
class Manufacturer(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=55)
showcase = models.OneToOneField('Car',related_name='Car',blank=True)
class Car(models.Model):
user = models.ForeignKey(User)
Manufacturer = models.ForeignKey(Manufacturer,blank=False,related_name='Manufacturer')
Try Manufacturer.objects.exclude(showcase=None)
also, in your model try to add null=True next to to blank=True in the showcase attribute of the model.