Building a Video Sharing Website: Part 5 Category Views and Similar Videos
In this tutorial we are going to use the category view that we built in the last tutorial. We are going to add similar videos to the detail view of each video. We are also going to add a link to view all videos in a category on the detail view. With that said, let’s go ahead and begin with the similar videos.
First, we need to update our DetailView. We will add a filter to get all the videos of a category and only get a maximum of 15 videos, and then we can pass that to the template:
class DetailVideo(View):
def get(self, request, pk, *args, **kwargs):
video = Video.objects.get(pk=pk) categories = Video.objects.filter(category=video.category)[:15] form = CommentForm()
comments = Comment.objects.filter(video=video).order_by('-created_on')
context = {
'object': video,
'comments': comments,
'categories': categories,
'form': form
}
return render(request, 'videos/detail_video.html', context) def post(self, request, pk, *args, **kwargs):
video = Video.objects.get(pk=pk) categories = Video.objects.filter(category=video.category)[:15] form = CommentForm(request.POST)
if form.is_valid():
comment = Comment(
user = self.request.user,
comment = form.cleaned_data['comment'],
video = video
)
comment.save() comments = Comment.objects.filter(video=video).order_by('-created_on')
context = {
'object': video,
'comments': comments,
'categories': categories,
'form': form
}
return render(request, 'videos/detail_video.html', context)
Now, let’s update our detailview template. We are going to add another column in the row with the video. Within that column, we will list out all of the category videos. We also need to add an if statement to check if the category video is the same as the actual main video being shown. We don’t also want to show that video in the similar videos list:
{% extends 'videos/base.html' %}
{% load crispy_forms_tags %}{% block content %}
<div class="container mb-5">
<div class="row mt-5">
<div class="col-md-8 col-sm-12 col-xs-12">
<video-js
width="720"
height="405"
controls
data-setup='{"playbackRates": [0.5, 1, 1.5, 2], "fluid": true}'>
<source src="/media/{{ object.video_file }}" type="video/mp4">
<p class="vjs-no-js">
To view this video please enable Javascript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video-js>
<h5 class="mt-4">{{ object.title }}</h5>
<div class="row">
<div class="col-md-6 text-muted">
<p>{{ object.date_posted | date:"M d, Y" }}</p>
<a class="category-link" href="{% url 'category-list' object.category.pk %}">{{ object.category }}</a>
</div>
<div class="col-md-6 text-muted text-right">
<ion-icon class="video-icons" name="share-social-outline" type="button" data-toggle="modal" data-target="#exampleModal">
Share
</ion-icon>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Share This Video!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body text-center">
<ion-icon class="social-share-icons twitter-social-color" name="logo-twitter" data-sharer="twitter" data-title="Checkout {{ object.title }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Twitter</ion-icon>
<ion-icon class="social-share-icons facebook-social-color" name="logo-facebook" data-sharer="facebook" data-title="Checkout {{ object.title }} by {{ object.uploader }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Facebook</ion-icon>
<ion-icon class="social-share-icons reddit-social-color" name="logo-reddit" data-sharer="reddit" data-title="Checkout {{ object.title }} by {{ object.uploader }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Reddit</ion-icon>
<p class="mt-3">Link: http://localhost:8000/videos/{{ object.pk }}</p>
</div>
</div>
</div>
</div> </div>
</div> {% if object.uploader == user %}
<a class="video-icons icon-color" href="{% url 'video-update' object.pk %}"><ion-icon name="pencil-outline"></ion-icon></a>
<a class="video-icons icon-color" href="{% url 'video-delete' object.pk %}"><ion-icon name="close-outline"></ion-icon></a>
{% endif %}
</div>
<div class="col-md-4 col-sm-12 col-xs-12">
<h3 class="text-center mb-3">Similar Videos</h3>
{% for category in categories %}
{% if object.pk != category.pk %}
<div class="position-relative d-flex flex-row mb-5 py-2 border-bottom">
<img src="{{ category.thumbnail.url }}" width="100" height="50" />
<h5 class="pl-3">{{ category.title }}</h5>
<a href="{% url 'video-detail' category.pk %}" class="stretched-link"></a>
</div>
{% endif %}
{% endfor %}
</div>
</div> <div class="row">
<div class="col-md-8 my-3 border-bottom">
<p>{{ object.description }}</p>
</div>
</div> <div class="row mt-3">
<div class="col-md-8 col-sm-12">
{% if user.is_authenticated %}
<form method="POST">
{% csrf_token %}
<legend class="border-bottom mb-4">Leave a Comment</legend>
{{ form | crispy }}
<button class="btn btn-outline-info btn-block">Post</button>
</form>
{% else %}
<a class="btn btn-outline-info btn-block" href="{% url 'account_login' %}">Sign In To Post a Comment!</a>
{% endif %}
</div>
</div> {% for comment in comments %}
<div class="row mt-3">
<div class="col-md-8 col-sm-12 border-bottom">
<h5>{{ comment.user }} says:</h5>
<p>{{ comment.comment }}</p>
</div>
</div>
{% endfor %}
</div>{% endblock content %}
Here is the code that was changed:
<div class="col-md-4 col-sm-12 col-xs-12">
<h3 class="text-center mb-3">Similar Videos</h3>
{% for category in categories %}
{% if object.pk != category.pk %}
<div class="position-relative d-flex flex-row mb-5 py-2 border-bottom">
<img src="{{ category.thumbnail.url }}" width="100" height="50" />
<h5 class="pl-3">{{ category.title }}</h5>
<a href="{% url 'video-detail' category.pk %}" class="stretched-link"></a>
</div>
{% endif %}
{% endfor %}
</div>
That is all we will need to do for the similar videos. Now let’s build an ability to see all of the videos in a category, in this list only 15 will show if there are more they won’t be visible here so we will add a link below the date to view all of the videos in a category.
To do this, we will create a new view first:
class VideoCategoryList(View):
def get(self, request, pk, *args, **kwargs):
category = Category.objects.get(pk=pk)
videos = Video.objects.filter(category=pk).order_by('-date_posted')
context = {
'category': category,
'videos': videos
} return render(request, 'videos/video_category.html', context)
In this view, we will inherit from the View class. We can then use the different methods based on what HTTP request we want to handle. In this case we only need to handle a get request since we aren’t updating any data with a form. In this method, we will get the category that matches with the pk passed into the url (similar to our video detail view) and we will get all of the videos with that category. We will pass both of those into the template.
Now let’s build the url pattern for it:
from django.contrib import admin
from django.urls import path
from .views import CreateVideo, DetailVideo, UpdateVideo, DeleteVideo, VideoCategoryListurlpatterns = [
path('create/', CreateVideo.as_view(), name='video-create'),
path('<int:pk>/', DetailVideo.as_view(), name='video-detail'),
path('<int:pk>/update', UpdateVideo.as_view(), name='video-update'),
path('<int:pk>/delete', DeleteVideo.as_view(), name='video-delete'),
path('category/<int:pk>/', VideoCategoryList.as_view(), name='category-list'),
]
This will look very similar to the detail view url, because we are pretty much doing the exact same thing as we would for a detail view at least when it comes to the url pattern and view. The one aspect that would be different would be the HTML template. Let’s go ahead and build that now:
{% extends 'videos/base.html' %}{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12 my-3">
<h2>{{ category.name }} Videos</h2>
</div>
</div>
<div class="row justify-content-center">
{% for video in videos %}
<div class="card col-md-3 col-sm-12 mr-md-2 mt-5 p-3 border-0">
<a href="{% url 'video-detail' video.pk %}"><img src="/media/{{ video.thumbnail }}" width="256" height="144"></a>
<div class="card-body">
<a class="link-text" href="{% url 'video-detail' video.pk %}"><h5 class="text-center">{{ video.title }}</h5></a>
<p class="text-muted text-center m-0">{{ video.uploader }}</p>
<p class="text-muted text-center">{{ video.date_posted | date:"M d, Y" }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock content %}
This will look very similar to the index page since all we are doing is looping through and displaying each video, except now we are only displaying the videos in this specific category that is passed into the url. The final piece we need to add is a link to get to this view on the video detail video page:
{% extends 'videos/base.html' %}
{% load crispy_forms_tags %}{% block content %}
<div class="container mb-5">
<div class="row mt-5">
<div class="col-md-8 col-sm-12 col-xs-12">
<video-js
width="720"
height="405"
controls
data-setup='{"playbackRates": [0.5, 1, 1.5, 2], "fluid": true}'>
<source src="/media/{{ object.video_file }}" type="video/mp4">
<p class="vjs-no-js">
To view this video please enable Javascript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video-js>
<h5 class="mt-4">{{ object.title }}</h5>
<div class="row">
<div class="col-md-6 text-muted">
<p>{{ object.date_posted | date:"M d, Y" }}</p>
<a class="category-link" href="{% url 'category-list' object.category.pk %}">{{ object.category }}</a>
</div>
<div class="col-md-6 text-muted text-right">
<ion-icon class="video-icons" name="share-social-outline" type="button" data-toggle="modal" data-target="#exampleModal">
Share
</ion-icon>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Share This Video!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body text-center">
<ion-icon class="social-share-icons twitter-social-color" name="logo-twitter" data-sharer="twitter" data-title="Checkout {{ object.title }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Twitter</ion-icon>
<ion-icon class="social-share-icons facebook-social-color" name="logo-facebook" data-sharer="facebook" data-title="Checkout {{ object.title }} by {{ object.uploader }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Facebook</ion-icon>
<ion-icon class="social-share-icons reddit-social-color" name="logo-reddit" data-sharer="reddit" data-title="Checkout {{ object.title }} by {{ object.uploader }}!" data-hashtags="" data-url="http://localhost:8000/videos/{{ object.pk }}">Share on Reddit</ion-icon>
<p class="mt-3">Link: http://localhost:8000/videos/{{ object.pk }}</p>
</div>
</div>
</div>
</div> </div>
</div> {% if object.uploader == user %}
<a class="video-icons icon-color" href="{% url 'video-update' object.pk %}"><ion-icon name="pencil-outline"></ion-icon></a>
<a class="video-icons icon-color" href="{% url 'video-delete' object.pk %}"><ion-icon name="close-outline"></ion-icon></a>
{% endif %}
</div>
<div class="col-md-4 col-sm-12 col-xs-12">
<h3 class="text-center mb-3">Similar Videos</h3>
{% for category in categories %}
{% if object.pk != category.pk %}
<div class="position-relative d-flex flex-row mb-5 py-2 border-bottom">
<img src="{{ category.thumbnail.url }}" width="100" height="50" />
<h5 class="pl-3">{{ category.title }}</h5>
<a href="{% url 'video-detail' category.pk %}" class="stretched-link"></a>
</div>
{% endif %}
{% endfor %}
</div>
</div> <div class="row">
<div class="col-md-8 my-3 border-bottom">
<p>{{ object.description }}</p>
</div>
</div> <div class="row mt-3">
<div class="col-md-8 col-sm-12">
{% if user.is_authenticated %}
<form method="POST">
{% csrf_token %}
<legend class="border-bottom mb-4">Leave a Comment</legend>
{{ form | crispy }}
<button class="btn btn-outline-info btn-block">Post</button>
</form>
{% else %}
<a class="btn btn-outline-info btn-block" href="{% url 'account_login' %}">Sign In To Post a Comment!</a>
{% endif %}
</div>
</div> {% for comment in comments %}
<div class="row mt-3">
<div class="col-md-8 col-sm-12 border-bottom">
<h5>{{ comment.user }} says:</h5>
<p>{{ comment.comment }}</p>
</div>
</div>
{% endfor %}
</div>{% endblock content %}
Here we added a link below the date to go to the category videos:
<a class="category-link" href="{% url 'category-list' object.category.pk %}">{{ object.category }}</a>
That is all we will need to do to add some functionality to our category model we created last time. Now we can show videos that are posted in the same category as the video that they are currently viewing. This is where we will stop for now, we will come back next time to add some more functionality to this application.