In this lecture, we began to develop the function of detail page, which is to play a single video and display the relevant information of the video, such as video title, description, comment information, relevant recommendations, etc. You’ll learn about the use of the general-purpose view class DetailView, the dynamic loading of comments, and how to implement the likes and favorites functionality through Ajax, with cool pieces of code to illustrate these capabilities.

Results show

The overall function

You can browse the website effect through the website demo address first. Click on a video to navigate to the details page. The details page realizes the display of a single video, and users can see some meta information of the video, including title, description, number of views, number of likes, number of favorites and so on. In addition, the site also implemented a comment function, by pulling up the page to load the list of comments, users can add comments. The sidebar of the web page is the list of recommended videos. The recommendation logic used here is relatively simple, that is, the videos that are recommended for the most times.

We divided the detail page into four small business modules to develop, which are: video details display, like and favorites function, comment function and recommendation function. Below, we explain the development of these four functional modules respectively.

Video Details display

Since we already built the video model in the last lecture, we didn’t have to create a new model, so we just extended the video model. Last time, we created fields like title, desc, CLASSIFICATION, file, cover, status, and create_time. These fields are not enough for now, so we need to add a few more fields, which need to add the number of views, favorite users, favorite users. The video model is extended as follows

class Video(models.Model):
    STATUS_CHOICES = (
        ('0'.'In release'),
        ('1'.'Not published'),
    )
    title = models.CharField(max_length=100,blank=True, null=True)
    desc = models.CharField(max_length=255,blank=True, null=True)
    classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True)
    file = models.FileField(max_length=255)
    cover = models.ImageField(upload_to='cover/',blank=True, null=True)
    status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True)
    view_count = models.IntegerField(default=0, blank=True)
    liked = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   blank=True, related_name="liked_videos")
    collected = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   blank=True, related_name="collected_videos")
    create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20)

Copy the code

Three fields have been added

  • View_count Number of views. The data type is IntegerField and the default is 0
  • Liked by a user. The data type is ManyToManyField, which is a many-to-many relationship, indicating that a video can be liked by multiple users, and a user can also like multiple videos. Remember to set the user table to settings.auth_user_model
  • Collected users. The data type is ManyToManyField, which is a many-to-many relationship, indicating that a video can be bookmarked by multiple users, and a user can also bookmark multiple videos. Set the user table to settings.AUTH_USER_MODEL

For more information on how to use the ManyToManyField, check out the Django website.

Next is the detail display stage. We first configure the routing information of the detail page and append the routing information of the detail in video/urls.py.


app_name = 'video'
urlpatterns = [
    path('index', views.IndexView.as_view(), name='index'),
    path('search/', views.SearchListView.as_view(), name='search'),
    path('detail/<int:pk>/', views.VideoDetailView.as_view(), name='detail'),]Copy the code

Path (‘detail/ /’, views.videoDetailView.as_view (), name=’detail’) So set the path match to detail/ /, where denotes the primary key, which is django’s way of representing a primary key. So we can in the browser input 127.0.0.1:8000 / video/detail/XXX to access details.

To display the details, Django wisely provides us with a DetailView. The view class set in urls.py is VideoDetailView, so let’s make the VideoDetailView inherit from DetailView.

class VideoDetailView(generic.DetailView):
    model = Video
    template_name = 'video/detail.html' 
Copy the code

It seems super simple, but Django is so cool that it only takes a few lines of code to configure and do a lot of powerful things. Here we set the model to Video and the template to Video /detail.html, leaving the rest of the work to Django. Oh, thats great.

Template file is located in the templates/video/detail. HTML, its code is simpler, it won’t stick.

As you can see from the renderings, there is also a view count, which is essentially an increment field in the database, where view_count is automatically incremented by 1 each time it is viewed. For this small requirement, we need to do two things. First, add an increase-of-number function to the video model, named Increase_view_count, which is simple, as follows:

    def increase_view_count(self):
        self.view_count += 1
        self.save(update_fields=['view_count'])
Copy the code

Then, we also need to call this function in the VideoDetailView class. This is where get_object() comes in handy. Because Every time DetailView is called, Django calls back to get_object(). So we can execute the increase_view_count() inside the get_object(). The perfect code is as follows

class VideoDetailView(generic.DetailView):
    model = Video
    template_name = 'video/detail.html'

    def get_object(self, queryset=None):
        obj = super().get_object()
        obj.increase_view_count()  Call the increment function
        return obj
Copy the code

So far, we can see the title, description, views, favorites and likes on the details page. Preview the following

Although can display the number of collection, like the number of. But there’s no “like”/” favorites “yet. So let’s do that.

Favorites and likes feature

Favorites and likes are a set of actions, so they can be implemented with Ajax: the user clicks and invokes the back-end interface, which returns JSON data, and the front-end displays the results.

Since we need an interface, let’s add a route to our favorite/favorite interface and append the following code to video/urls.py

path('like/', views.like, name='like'),
path('collect/', views.collect, name='collect'),
Copy the code

Because the function of “like” and “collect” is very similar, limited to space, we only implement the “like” function.

Let’s write the like function first:

@ajax_required
@require_http_methods(["POST"])
def like(request):
    if not request.user.is_authenticated:
        return JsonResponse({"code": 1, "msg": "Please log in first."})
    video_id = request.POST['video_id']
    video = Video.objects.get(pk=video_id)
    user = request.user
    video.switch_like(user)
    return JsonResponse({"code": 0."likes": video.count_likers(), "user_liked": video.user_liked(user)})

Copy the code

If the user is logged in, switch_like(user) is called to implement the like or dislike function. Finally, JSON is returned. Note that two annotations @ajax_required and @require_http_methods([“POST”]) have been added to verify that the request must be an Ajax and POST request, respectively.

The switch_like() function is written in video/model.py

    def switch_like(self, user):
        if user in self.liked.all():
            self.liked.remove(user)
        else:
            self.liked.add(user)
Copy the code

With all the back-end work in place, let’s move on to the front end. The front end is mostly ajax code.

Due to the large amount of Ajax code, we wrap it in a separate JS file ==> static/js/detail.js

In detail.js, we implement our favorite Ajax call first:

$(function() {// write CSRF $.getscript ()"/static/js/csrftoken.js"); / / like $("#like").click(function(){
      var video_id = $("#like").attr("video-id");
      $.ajax({
            url: '/video/like/',
            data: {
                video_id: video_id,
                'csrf_token': csrftoken
            },
            type: 'POST',
            dataType: 'json',
            success: function (data) {
                var code = data.code
                if(code == 0){
                    var likes = data.likes
                    var user_liked = data.user_liked
                    $('#like-count').text(likes)
                    if(user_liked == 0){
                        $('#like').removeClass("grey").addClass("red")}else{$('#like').removeClass("red").addClass("grey")}}else{
                    var msg = data.msg
                    alert(msg)
                }

            },
            error: function(data){
              alert("Failed to like")}}); });Copy the code

The key code in the above code is the $.ajax() function, where we pass in the parameters video_id and csrFToken. The csrftoken can be generated using /static/js/csrftoken.js. In the SUCCESS callback, determine if you liked a new one by determining the value of user_liked, and then change the CSS in the template.

recommendations

Each site has its own recommendation function, and each has its own recommendation logic. The recommendation logic of our viewpoint is to recommend to users in descending order according to the n videos with the highest number of visits.

It’s easy to implement, as we know the detail page implementation uses the VideoDetailView, and we can pass recommendations to the front-end template in get_context_data(). We just need to rewrite the VideoDetailView get_context_date() function.

    def get_context_data(self, **kwargs):
        context = super(VideoDetailView, self).get_context_data(**kwargs)
        form = CommentForm()
        recommend_list = Video.objects.get_recommend_list()
        context['form'] = form
        context['recommend_list'] = recommend_list
        return context
Copy the code

After rewriting, we add a line

recommend_list = Video.objects.get_recommend_list()
Copy the code

We encapsulated the Video model for obtaining the recommendation list function get_shame_list (). In Video/models.py we append code:

class VideoQuerySet(models.query.QuerySet):
    def get_recommend_list(self):
        return self.filter(status=0).order_by('-view_count')[:4]
Copy the code

Self.filter (status=0).order_by(‘-view_count’)[:4]

Note that we are using the VideoQuerySet query here, so we need to add a dependency line under Video. Represents to use VideoQuerySet as the query manager for Video.

objects = VideoQuerySet.as_manager()
Copy the code

When the template gets the data, it renders it. Here we will recommend the sidebar code encapsulation to templates/video /. HTML.

# templates/video/recommend.html
{% load thumbnail %}
<span class="video-side-title"> </span> <div class="ui unstackable divided items">
    {% for item in recommend_list %}
    <div class="item">
        <div class="ui tiny image">
            {% thumbnail item.cover "300x200" crop="center" as im %}
            <img class="ui image" src="{{ im.url }}">
            {% empty %}
            {% endthumbnail %}
        </div>
        <div class="middle aligned content">
            <a class=" header-title" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a>
            <div class="meta">
                <span class="description"> {{item. View_count}} time watching < / span > < / div > < / div > < / div > {% empty %} no recommend < / h3 > < h3 > {% endfor %} < / div >Copy the code

And include it in detail.html

{% include "video/recommend.html" %}
Copy the code

The comment function

The comments section is located at the bottom of the details page and appears as follows. It is divided into two parts: comment form and comment list.

The comment function is an independent module with high generality. It is available in many other websites. In order to avoid repeating the wheel when developing other websites in the future, we set up a new application named Comment

python3 manage.py startapp comment
Copy the code

Next, we create the comment model

# in the comment/models. Pyclass Comment(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) nickname = models.CharField(max_length=30,blank=True, null=True) avatar = models.CharField(max_length=100,blank=True, null=True) video = models.ForeignKey(Video, on_delete=models.CASCADE) content = models.CharField(max_length=100) timestamp = models.DateTimeField(auto_now_add=True)  class Meta: db_table ="v_comment"
Copy the code
  • And user. The data type is ForeignKey, the ForeignKey is settings.AUTH_USER_MODEL, and the value is set to cascading deletion on_delete= models.cascade
  • Nickname of user nickname. The data type is CharField.
  • The avatar picture. The data type is CharField.
  • Video Indicates a video. The data type is ForeignKey, corresponding to the Video model. Cascading deletion on_DELETE = Models. CASCADE
  • Content Comments. The data type is CharField.
  • Timestamp Comment time. The data type is DateTimeField.

With the model in place, we can focus on writing business code, starting with the route file urls.py under comment. And write the code:

from django.urls import path
from . import views

app_name = 'comment'
urlpatterns = [
    path('submit_comment/<int:pk>',views.submit_comment, name='submit_comment'),
    path('get_comments/', views.get_comments, name='get_comments'),]Copy the code

We configured two routes: comment submission and comment retrieval.

To submit a comment, we need a form, which we put in video/forms.py

from django import forms
from comment.models import Comment

class CommentForm(forms.ModelForm):
    content = forms.CharField(error_messages={'required': 'Cannot be empty',},
        widget=forms.Textarea(attrs = {'placeholder': 'Please enter comments' })
    )

    class Meta:
        model = Comment
        fields = ['content']
Copy the code

Then add the relevant code of the form in the VideoDetailView of video/views.py.

class VideoDetailView(generic.DetailView):
    model = Video
    template_name = 'video/detail.html'

    def get_object(self, queryset=None):
        obj = super().get_object()
        obj.increase_view_count()
        return obj

    def get_context_data(self, **kwargs):
        context = super(VideoDetailView, self).get_context_data(**kwargs)
        form = CommentForm() 
        context['form'] = form 
        return context
Copy the code

In the get_context_data() function, we pass the form to the template.

Similarly, submitting comments is asynchronous, so we do it with Ajax, so we open up static/js/detail.js and say

Var FRM = $('#comment_form')
    frm.submit(function () {
        $.ajax({
            type: frm.attr('method'),
            url: frm.attr('action'),
            dataType:'json',
            data: frm.serialize(),
            success: function (data) {
                var code = data.code
                var msg = data.msg
                if(code == 0){
                    $('#id_content').val(""The $()'.comment-list').prepend(data.html);
                    $('#comment-result').text("Review success"The $()'.info').show().delay(2000).fadeOut(800)
                }else{$('#comment-result').text(msg)
                    $('.info').show().delay(2000).fadeOut(800);
                }
            },
            error: function(data) {
            }
        });
        return false;
    });
Copy the code

After the comment is submitted via Ajax, we receive the request in submit_comment(). To deal with the following

def submit_comment(request,pk):
    video = get_object_or_404(Video, pk = pk)
    form = CommentForm(data=request.POST)

    if form.is_valid():
        new_comment = form.save(commit=False)
        new_comment.user = request.user
        new_comment.nickname = request.user.nickname
        new_comment.avatar = request.user.avatar
        new_comment.video = video
        new_comment.save()

        data = dict()
        data['nickname'] = request.user.nickname
        data['avatar'] = request.user.avatar
        data['timestamp'] = datetime.fromtimestamp(datetime.now().timestamp())
        data['content'] = new_comment.content

        comments = list()
        comments.append(data)

        html = render_to_string(
            "comment/comment_single.html", {"comments": comments})

        return JsonResponse({"code": 0."html": html})
    return JsonResponse({"code": 1,'msg':'Comment failed! '})

Copy the code

In the receiving function, a record is saved through the validation function that comes with the form, and the record is returned to the front-end template.

Let’s begin the development of the comment list.

In the comment list part, we use the pull-up dynamic loading scheme, that is, when the page is pulled to the bottom, the JS loading code will automatically obtain the data of the next page and display it. For the front end, we use an open source loading plug-in based on JS. Based on this plug-in, it is easy to achieve the dynamic loading effect of web pages. It’s super easy to use, just calling $(‘.comments’).dropload({}). We wrap the calling code in static/js/load_comments.js.

The complete call code is as follows:

$(functionVar page = 0; Var page_size = 15; // dropload $('.comments').dropload({
        scrollArea : window,
        loadDownFn : function(me){
            page++;

            $.ajax({
                type: 'GET',
                url: comments_url,
                data:{
                     video_id: video_id,
                     page: page,
                     page_size: page_size
                },
                dataType: 'json',
                success: function(data){
                    var code = data.code
                    var count = data.comment_count
                    if(code == 0){
                        $('#id_comment_label').text(count + "A comment.");
                        $('.comment-list').append(data.html);
                        me.resetload();
                    }else{
                        me.lock();
                        me.noData();
                        me.resetload();
                    }
                },
                error: function(xhr, type){ me.resetload(); }}); }}); });Copy the code

Without too much explanation, this code is very, very clear, essentially ajax interface request call, call back to update the front-end web content.

We see that the interface for the Ajax call is get_comments, which we continue to implement in comment/views.py. The code is shown below, which is also very simple, with no technical sophistication. Once you get page and page_size, use the Paginator object to do the pagination. Finally, the HTML is passed to the template via render_to_string.

def get_comments(request):
    if not request.is_ajax():
        return HttpResponseBadRequest()
    page = request.GET.get('page')
    page_size = request.GET.get('page_size')
    video_id = request.GET.get('video_id')
    video = get_object_or_404(Video, pk=video_id)
    comments = video.comment_set.order_by('-timestamp').all()
    comment_count = len(comments)

    paginator = Paginator(comments, page_size)
    try:
        rows = paginator.page(page)
    except PageNotAnInteger:
        rows = paginator.page(1)
    except EmptyPage:
        rows = []

    if len(rows) > 0:
        code = 0
        html = render_to_string(
            "comment/comment_single.html", {"comments": rows})
    else:
        code = 1
        html = ""

    return JsonResponse({
        "code":code,
        "html": html,
        "comment_count": comment_count
    })
Copy the code