1

I am having trouble accessing a list of data on a through table. The through table is called Cast and has a ManyToManyField for my Movie table (which lists every movie in the database) and another ManyToManyField for my Person table (which lists every actor).

The fields need to be ManyToMany because an actor can appear in many different movies, and a movie can contain many different actors.

I want to run a for-loop that lists all of the actors associated with a given movie (filtered by the movies primary key) and displays them on a web page. So far I cannot list anything on the web page (Movie.title displays the title, but I can't display anything from the Cast table.)

I have tried reading all the other related stackoverflow solutions (such as listing objects from ManyToManyField) but I still cannot get the actor's names to display on the website.

/Model.py 

class Movie(models.Model):
    title = models.CharField(max_length=250)
    

class Person(models.Model):
    name = models.CharField(max_length=100)


class Cast(models.Model):
    movie = models.ManyToManyField(
        Movie,
    )
    castmember = models.ManyToManyField(
        Person
    )
/View.py

class MoviePageView(TemplateView):
    template_name = '_movie_info.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['movie'] = Movie.objects.all()
        context['cast'] = Cast.objects.filter(movie__id=**kwargs)

I have also tried:
context['cast'] = Cast.objects.filter(movie__id=self.movie.id)
context['cast'] = Cast.objects.filter(movie__id=**kwargs)
context['cast'] = Cast.objects.all()

/URL.py
urlpatterns = urlpatterns = [
    path("<int:pk>", MovieDetailView.as_view(), name="movie_detail"),
]
html page

<div>{{ movie.title }}</div>

{% for info in cast.castmember.all %}
            <div>{{ info.name }}</div>
{% endfor %}

2 Answers2

2

Unless you need extra fields in the intermediate table (which I believe is not necessary) you can go with:

models.py

class Person(models.Model):
    name = models.CharField(max_length=100)


class Movie(models.Model):
    title = models.CharField(max_length=250)
    cast = models.ManyToManyField(Person)

views.py

class MoviePageView(TemplateView):
    template_name = "_movie_info.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        obj = Movie.objects.get(pk=context.get("pk"))
        context["movie"] = obj
        return context

_movie_info.html

...
<body>
    <h2>{{ movie.title }}</h2>
    <p>Cast:</p>
    <ul>
    {% for actor in movie.cast.all %}
        <li>{{ actor.name }}</li>
    {% endfor %}
    </ul>
</body>
...
Niko
  • 3,012
  • 2
  • 8
  • 14
2

Your Cast modeling makes not much sense, it acts as a group of people and that group of people can be assigned to multiple movies.

You can model this as:

class Movie(models.Model):
    title = models.CharField(max_length=250)
    cast = models.ManyToManyField('Person', through='Cast')


class Person(models.Model):
    name = models.CharField(max_length=100)


class Cast(models.Model):
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
    castmember = models.ForeignKey(Person, on_delete=models.CASCADE)
    role = models.CharField(max_length=128, null=True, default=None)

In that case, Cast acts as a junction table, and you can even add the role for each Person/movie combination.

As for the view, this is a simple DetailView [Django-doc]:

from django.views.generic import DetailView


class MoviePageView(DetailView):
    model = Movie
    template_name = '_movie_info.html'

and that's it for the view. In the template we can then access the cast:

<div>{{ movie.title }}</div>

{% for person in movie.cast.all %}
    <div>{{ person.name }}</div>
{% endfor %}

we can even add the role by prefetching the cast:

from django.db.models import Prefetch
from django.views.generic import DetailView


class MoviePageView(DetailView):
    model = Movie
    queryset = Movie.objects.prefetch_related(
        Prefetch('cast_set', Cast.objects.select_related('castmember'))
    )
    template_name = '_movie_info.html'

and render this with:

<div>{{ movie.title }}</div>

{% for person in movie.cast_set.all %}
    <div>{{ cast.castmember.name }} as {{ cast.role }}</div>
{% endfor %}
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555