List, filter and play movies
Published: 2022-12-30 17:59:55

![Django and Netflix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4qht1jfclf5qdz799mo.png)

Hello, welcome to the third part of this tutorial course in which you'll learn and understand the basics of Django by building a clone of the popular website [Netflix](https://www.netflix.com/).
_________________________________________


In the previous part, we set up the Django project with authentication. Now, we are going to add core Netflix features. We will add the ability to list, filter and play movies.

But to achieve that goal, let's populate our database with some fixtures data.

# Django fixtures
What are fixtures and what is their purpose?
Sometimes, or even most of the time when you're building software, you need test data to see how your software behaves.
For the case of a web page or blog, you need fake posts, articles, and content, to see how your page is rendered. Or you may need a fake account to be able to log in a test your website easily. Fixtures are there for that. You might also hear it is named `seeder` or `seed`.
That is what we are going to do. Currently, our `index.html` renders static HTML, so static movie. But we want to render movies from our database. So for each movie listed in the `index.html`, we will create a `Movie` model instance of it.
We have already learned mainly two ways to do that in Django: 
- We can open the Django shell and start creating them one by one using:
```python
Movie.objects.create(name="Blah Blah blah")
```
- Or can log in to *Django Admin* using a superuser account and create them using the graphical interface.

But no worries, Django provides a more convenient way to handle that instead of creating them one by one in two steps:

1- Create a fixture file

- Inside `Netflix` folder, create a folder named `fixtures`.
- then inside that folder create a fixture file. Let's name it `initial.json`. Note that it's a `.json` file.
I have already done the work for you. Just click on this link https://github.com/fayomihorace/django-netflix-clone/blob/main/netflix/fixtures/initial.json and copy-paste the content into your `initial.json`.

Now let's explain the content of that file.
A fixture should contain a list of JSON objects in this format:
```json
{
    "model": "netflix.Movie",
    "pk": 1,
    "fields": {
        "name": "The Road trick",
        "description": "The Road trick description",
        "category": 1,
        "tags": [1, 2],
        "watch_count": 10,
        "preview_image": "preview_images/p1.PNG",
        "file": "movies/blank.mp4",
        "date_created": "2022-05-05 14:08:15.221580"
    }
}
```
The example above is picked from our `initial.json` file.
Each object of the array has mainly 3 attributes:
    - `model`:  it's the name of the Django app (`netflix`) followed by a dot (`.`) and next the name of the item's model (`Category`).
    - `pk`: it's the primary key of the model
    - `fields`: it's a nested object that contains an object representation of the instance.
So that fixture will create an instance of `Movie`:
- with `1` as the primary key,
- with `The Road trick` as the name,
- with `1` as the foreign key to `Category` model (that means it should exist a `Category` with `pk=1` that is the category of that movie).
- with `"The Road trick description"` as the description
- with 2 tags (the first is the `Tag` model instance with pk=1, and the second tag has pk=2).
- that has been watched 5 times
- for which the `preview_image` is stored in the path `static/preview_images/p1.PNG`
- for which the movie file itself is stored in the path `static/movies/p1.PNG` (supposed to be a video but we used an image for the sake of this course)
- that has been created March 5th 2022 at 14h 08 min 15seconds.


2- Load the fixtures data

It's simple, open a terminal from the root of the project and run:
```shell
python manage.py loaddata netflix/fixtures/initial.json
```

*Important Note*: When you create a fixture with foreign key (fk=5 for example), make sure the foreign key you are using is either an existing primary key for the Model you're referencing or another fixture that comes before it.

Moreover, if in the database, there is already a model instance with a primary key you define in your fixture, then Django will update that model instance instead of creating a new one.


3- Add the movie assets

In our fixtures, you'll notice that we have defined some files for the preview and movie files like `preview_images/movie1.png` or `movies/mv1.mp4`. Normally those files are supposed to be uploaded from the admin interface. But we can add them directly. They are available in the [final source code of the project](https://github.com/fayomihorace/django-netflix-clone).
You can just clone or download the repo and then copy and replace your local folder `django_netflix_clone/media` with the source code folder.

Now if you log in to the admin interface and, and go to `movies` pages, you will see that our data is well displayed:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2q7bb8nn16fbfomied5w.png)

That is. Now let's go to the core features.

# List movies
Currently, our index.html display static data. Let's remove them first.
To make it easy I've created the cleaned file for you available at [https://github.com/fayomihorace/django-netflix-clone/blob/main/templates/netflix/index_clean.html](https://github.com/fayomihorace/django-netflix-clone/blob/main/templates/netflix/index_clean.html).
Replace your current `index.html` file with it.


Now, our goal is to display movies stored in database in our index.html file in a way that each category will have 
it own section.
Let's take a look at the current code that renders a section and its movies:
```html
<section class="main-container">
      <div class="location" id="home">
          <h1 id="home">Action</h1>
          <div class="box">
            <a href="">
              <img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/p1.PNG?raw=true" alt="">
            </a>  
          </div>
      </div>
...
```
Here we have the section `Action` with one movie and it's rendered like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1l6cl8bdv9xqek4fxgk.png)
We had already seen that with Django Templates, we can send some variables to the HTML page and displays their values as we did to display the authenticated user.
We will do the same thing. Let's go:

1- modify the `index_view` so we can return the data we want to display:

```python
# django_netflix_clone/netflix/views.py
from .models import Movie  # Add this line
...

def index_view(request):
    """Home page view."""
    # We define the list of categories we want to display
    categories_to_display = ['Action', 'Adventure']

    # We create a dictionary that map each category with the it movies
    for category_name in categories_to_display:
        movies = Movie.objects.filter(category__name=category_name)
        # we limit the number of movies to PAGE_SIZE_PER_CATEGORY = 20
        data[category_name] = movies[:PAGE_SIZE_PER_CATEGORY]

    # We return the response with the data
    return render(request, 'netflix/index.html', { 'data': data.items()})
```
The code is explained. We have just retrieved movies for the two categories we defined in our fixtures.json (`Action` and `Adventure`).
The dictionary `data` should looks like:
```python
{
    'Action': <QuerySet [<Movie: The Road trick>, ...]>,
    'Adventure': <QuerySet [<Movie: Lost in space>, ...]>
}
```

Also, we returned `data.items()` instead of `data` because we need to send an iterable to the HTML page that we will loop to render the movies. But `data` is a dictionary and `dict.items()` return the iterable version.
We could then loop the data like this:
```python
for key, value in dict.items():
    print(key, value)
```
In our case:
```python
for category, movies in data.items():
    print(category)  # 'Action' or 'Adventure'
    print(movies)    # list of `Movie` instances that match the category  
```

2- Now let's render the data. Modify the HTML file by replacing this code:

```html
<div class="location" id="home">
          <h1 id="home">Action</h1>
          <div class="box">
            <a href="">
              <img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/p1.PNG?raw=true" alt="">
            </a>  
          </div>
      </div>

      <h1 id="myList">Adventure</h1>
      <div class="box">
        <a href="">
          <img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/t1.PNG?raw=true" alt="">
        </a>            
      </div>
```

by this:
```html
<div class="location" id="home">
  {% for category, movies in data %}
    <h1>{{category}}</h1>
    <div class="box">
    {% for movie in movies %}
      <a href="" style="text-decoration: none; color: white">
        <img src="/media/{{movie.preview_image}}" alt="{{movie.name}}">
        <span>{{movie.name}}</span>
      </a>
    {% endfor %}
    </div>
  {% endfor %}
</div>
```
This is similar to the normal loop in the python code I showed above.
- we make a loop through the data `{% for category, movies in data %}`  (iterable Dict) which give access to the `category` and the `movies` variables. Remember `category` contains the category name and `movies` is the list of movies matching that category.
- we render the category variable at the place where the section name should be. `<h1>{{category}}</h1>`.
- Now inside the first loop, so for each category, we make a second loop (`{% for movie in movies %}`) to go through the list of movies of that category and display the image with `src="/media/{{movie.preview_image}}"` and also the movie name with this part `<span>{{movie.name}}</span>`.


# Filter movies
1- Create the search form
Inside  `netflix/forms.py` add the following:
```python
class SearchForm(forms.Form):
    """Search form class."""
    search_text = forms.CharField(
        label="",
        widget=forms.TextInput(attrs={'placeholder': 'Search'})
    )
``` 
Note: 
- with `label=""` no label will be rendered as we just need the input.
- `widget=forms.TextInput(attrs={'placeholder': 'Search'})` allows to define the text in the input placeholder. You can customize the input as you want. Don't hesitate to check the documentation to read more about that customization: [https://docs.djangoproject.com/en/4.1/ref/forms/widgets/](https://docs.djangoproject.com/en/4.1/ref/forms/widgets/)

2- Send the form to index.html
Modify the `netflix/views.py`
```python
# netflix/views.py
from .forms import SearchForm
...
def index_view(request):
    ...
    data = {}
    # We create a dictionary that map each category with the it movies
    for category_name in categories_to_display:
        movies = Movie.objects.filter(category__name=category_name)
        if request.method == 'POST':
            search_text = request.POST.get('search_text')
            movies = movies.filter(name__icontains=search_text)
        # we limit the number of movies to PAGE_SIZE_PER_CATEGORY = 20
        data[category_name] = movies[:PAGE_SIZE_PER_CATEGORY]

    search_form = SearchForm()
    # We return the response with the data
    return render(request, 'netflix/index.html', {
        'data': data.items(),
        'search_form': search_form
    })
```
Note:
-  We use `if request.method == 'POST':` to check if the request is a post, and in that case, we override the query set to filter results using the search text (`movies = movies.filter(name__icontains=search_text)`)

3- Replace the search icon by the form in index.html
Repace this:
```html
<a href="#"><i class="fas fa-search sub-nav-logo"></i></a>
```
by this:
```html
<form action="/" method="POST">
    {% csrf_token %}
    {{ search_form.as_p }}
    <button type="submit">
      <i class="fas fa-search sub-nav-logo"></i>
    </button>
</form>
```
It should looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnrxzcpz8k1yk2pzcz6h.png)
Nice, you can now try to play with the search fiels to see that it works well.
Now, It's up to you to update the css style to change the rendering.


# Play movies

1- Create a file named `watch_movie.html` and copy the code from the repo. It's located at `/django_netflix_clone/templates/netflix/watch_movie.html`.
The most important part of the code is this:
```html
{% if movie %}
  <h3 style="padding: 15px">{{movie.name}}</h3>
  <video width="100%" height="70%" controls>
    <source src="/media/{{movie.file}}" type="video/mp4">
  </video>
{% else %}
  <strong style="font-size: 25px">Invalid url</strong>
{% endif %}
```
Here, if the id sent is invalid we display `Invalid url` but if the id is good, the video player will be rendered.

2- Create a view to render the file:

```python
# django_netflix_clone/netflix/views.py

def watch_movie_view(request):
    """Watch view."""
    # The primary key of the movie the user wants to watch is sent by GET parameters.
    # We retrieve that pk.
    movie_pk = request.GET.get('movie_pk')
    # We try to get from the database, the movie with the given pk 
    try:
        movie = Movie.objects.get(pk=movie_pk)
    except Movie.DoesNotExist:
        # if that movie doesn't exist, Movie.DoesNotExist exception is raised
        # and we then catch it and set the URL to None instead
        movie = None
    return render(request, 'netflix/watch_movie.html', {'movie': movie})
```
The code is explained.
But note that the use of `try/catch` is a good practice related to Python itself, because there is a **rule of thumb** in software engineering that is: ***Never trust client***. Here any user that could use our application is a client, and we cannot trust the client to send the primary key of an existing movie. So we should always make validation of client input, and check for edge cases that could raise exceptions or lead to weird behavior.

3- Add the route `watch` that will use `watch_movie_view`:

```python
# django_netflix_clone/django_netflix_clone/urls.py

from netflix.views import watch_movie_view # Add this line
...
urlpatterns = [
    ...
    path('watch', watch_movie_view, name='watch_movie'), # Add this line
    ...
]
```

4- Finally, we will make click on a movie item on index.html to redirect to the `watch` route. And we pass the `movie_url` as GET parameter `?movie_pk={{movie.pk}}`.
Modify the href of the movie from:

```html
<a href="" style="text-decoration: none; color: white">
```
to 
```html
<a href="/watch?movie_pk={{movie.pk}}" style="text-decoration: none; color: white">
```

And taddahh. Refresh the main page and try to click on a movie to see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ld666ekqg8lp5pft6pe9.png)

Note: You can use Django admin to modify the movie files with another real video so you can play with it using real video.

Congratulations for coming all the way here. We are almost done with this series.
In this article you've learned:
- What are fixtures and how to create and use them to add initial data to your Django database
- How to use `for` loops in Django templates
- The rule of thumb is that ***A server should never trust data sent by a client and always do the validation and verifications before***.

But we just scratched the surface of all Django is capable of. What remains next is to be able to write basic unit tests for our Django project and deploy it so everyone in the world can see the incredible work you did :sunglasses:.