DEV Community

Cover image for Build a Social Media Website with Django — Part 5 (Feed App Templates)
Kumar Shubham
Kumar Shubham

Posted on • Originally published at towardsdatascience.com

Build a Social Media Website with Django — Part 5 (Feed App Templates)

So, in the fourth part, we defined all the backend part of the Feed App, like we defined all the URLs, views, models, forms and registered our models with the admin panel.

So, now in the fifth part, we will focus on all the templates for displaying the logic we wrote in the Feed app. As you already know, Feed app deals with the posts, the likes, the comments and searching for posts part.

So, in this part, there would be lots of HTML since we are dealing with the part of the templates. It will take in the data passed by the views.py file of Feed app and will help display the data to the user and take user input via forms.

So, we put all our templates in the following location: templates/feed inside of the feed folder. It is the Django’s naming convention to put data there as Django looks for templates in that location.

We will deal with templates one by one. There are six of them. We won’t go into detail much as its mostly HTML and CSS classes and we would talk only about the logic part which we use in templates to define who has access to what and what part does what job.

So, let’s begin:

create_post.html

Let’s start with the create_post part. This part deals with the creation of new posts. This template will let users easily create new posts by adding the relevant details such as image (compulsory), details about the image and the tags (optional).

The function of this template is to display the required form which would accept user input. There is a submit button to save and submit the post and to add it to the database.

Also, there is a search bar displayed inside the navigation bar which allows you to search for specific posts by relevant tags.
The template extends the layout template which all other templates of Feed and Users templates were using. We will see layout.html file soon.

{% extends "feed/layout.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block cssfiles %}
{% endblock cssfiles %}
{% block searchform %}
<form class="form-inline my-2 my-lg-0 ml-5" action="{% url 'search_posts' %}" method="get">
  <input name="p" type="text" placeholder="Search posts..">
  <button class="btn btn-success my-2 my-sm-0 ml-4" type="submit">Search</button>
</form>
{% endblock searchform %}
{% block content %}
<br><br>
<div class="container">
    <div class="row">
      <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
        <div class="card card-signin my-5">
          <div class="card-body">
            <h5 class="card-title text-center"><b>Post</b></h5>
            <form class="form-signin" method = "POST" enctype="multipart/form-data">
            {% csrf_token %}
            <fieldset class="form-group"><br>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Post</button><br>
            </div>
        </form>
    </div>
    </div>
    </div>
    </div>
</div>

{% endblock content %}
{% block jsfiles %}{% endblock jsfiles %}
Enter fullscreen mode Exit fullscreen mode

home.html

It also extends the layout.html file. It is the home page for our app. Any user who logs in will be redirected to this page. This page is accessible without login too.

It also has the same search bar for searching posts. The main purpose of this homepage is to display the posts in a relevant manner.

It also checks whether a post has been liked by the user or not and displays like or unlike button as applicable.

It also has pagination done to display only 10 posts per page. It has links to next and previous pages also.

It also has the AJAX script for handling likes in real-time without refreshing the page. It changes the like button to unlike button as soon as the user likes it or vice versa. It changes the button color to represent the same.

{% extends "feed/layout.html" %}
{% load static %}
{% block cssfiles %}
{% endblock cssfiles %}
{% block searchform %}
<form class="form-inline my-2 my-lg-0 ml-5" action="{% url 'search_posts' %}" method="get">
  <input name="p" type="text" placeholder="Search posts..">
  <button class="btn btn-success my-2 my-sm-0 ml-4" type="submit">Search</button>
</form>
{% endblock searchform %}
{% block content %}
    <div class="container">
        <div class="row">

          <div class="col-md-8">
            {% for post in posts %}
            <div class="card card-signin my-5">
              <div class="card-body">
                <a href="{{ post.user_name.profile.get_absolute_url }}"><img src="{{ post.user_name.profile.image.url }}" class="rounded-circle" width="30" height="30" alt=""></a>
                <a class="text-dark" href="{{ post.user_name.profile.get_absolute_url }}"><b>{{ post.user_name }}</b></a>
                <br><small class="text-muted">Posted on {{ post.date_posted }}</small>
                <br><br>
                <p class="card-text text-dark">{{ post.description }}</p>
              </div>
              <a href="{% url 'post-detail' post.id %}"><img class="card-img-top" src="{{ post.pic.url }}" alt=""></a>
              {% if post.tags %}
              <br>
              <p class="text-danger ml-3"><b>Tags: <i>{{ post.tags }}</i></b></p>
              {% endif %}
              <div class="card-footer">
                <button class="btn btn-white mr-3 like" id="{{ post.id }}">
                    {% if post in liked_post %}
                        <a href="{% url 'post-like' %}" style="color:red;" id="likebtn{{ post.id }}"> Unlike</a> | {{post.likes.count}}
                    {% else %}
                        <a href="{% url 'post-like' %}" style="color:green;" id="likebtn{{ post.id }}"> Like</a> | {{post.likes.count}}
                    {% endif %}
                </button>
                <a class="btn btn-outline-info" href="{% url 'post-detail' post.id %}">Comments | {{ post.details.count }}</a>
              </div>
            </div>
            {% endfor %}
           </div>
        </div>
    </div>

{% if is_paginated %}

      {% if page_obj.has_previous %}
        <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
      {% endif %}

      {% for num in page_obj.paginator.page_range %}
        {% if page_obj.number == num %}
          <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
          <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
      {% endif %}

    {% endif %}

{% endblock content %}

{% block jsfiles %}
<script>
    $(".like").click(function (e) {
    var id = this.id;
    var href = $('.like').find('a').attr('href');
    e.preventDefault();

    $.ajax({
        url: href,
        data: {
          'likeId': id
        },
        success: function(response){
          if(response.liked){
            $('#likebtn' + id).html("Unlike");
            $('#likebtn' + id).css("color", "red")
          }
          else{
            $('#likebtn' + id).html("Like");
            $('#likebtn' + id).css("color", "green")
          }
        }
      })
});
</script>
{% endblock jsfiles %}
Enter fullscreen mode Exit fullscreen mode

layout.html

This is the layout file. It imports all the required CSS and JS files which we use on the website. We use Bootstrap4 and some custom CSS files.

Next, we have a Navbar which displays the relevant links for logged in users and users who are not logged in. It is done to show only the links which are required.

E.g.: We will show logout link to someone who is already logged in and will show login and register link for unauthenticated users.

We also have a small footer which just displays the name and copyright info.

You would notice that we have a {%block content%} {%endblock content%} which holds all the content info we have on other pages.
Similarly, we have a search block also which is for searching post or people part.

{% load static %}
<!DOCTYPE html>
<html>
<head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="icon" type="image/png" href="https://storage.googleapis.com/bytewalk/icon.png"/>
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700">
    <link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/bytewalk/profilepage.css">
    <link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/bytewalk/styles.css">
    {% block cssfiles %}{% endblock cssfiles %}
    <title>ByteWalk</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
      <a class="navbar-brand ml-4 mr-4" href="#"><b>ByteWalk</b></a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
        <div class="navbar-nav">
          <a class="nav-item nav-link" href="{% url 'home' %}">Home</a>
          <a class="nav-item nav-link" href="{% url 'users_list' %}">Add New Friends</a>
          {% if user.is_authenticated %}
            <a class="nav-item nav-link" href="{% url 'friend_list' %}">Friends</a> 
            <a class="nav-item nav-link" href="{% url 'my_profile' %}">Profile</a> 
            <a class="nav-item nav-link" href="{% url 'post-create' %}">Create Post</a>
            <a class="nav-item nav-link btn btn-danger ml-4  text-light" href="{% url 'logout' %}">Logout</a>
          {% else %}
            <a class="nav-item nav-link btn btn-success ml-4  text-light" href="{% url 'login' %}">Login</a>
            <a class="nav-item nav-link btn btn-info ml-3  text-light" href="{% url 'register' %} ">Register</a>
          {% endif %}
            {% block searchform %}{% endblock searchform %}
        </div>
      </div>
    </nav>
    <div class="container">
    {% block content %}{% endblock content %}
    </div>

    <footer class="page-footer font-small black">
      <div class="footer-copyright text-center py-3">ByteWalk © 2020
      <br><small class="text-muted">All rights reserved!</small>
    </div>
    </footer>

    <!-- JS Files -->
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
    {% block jsfiles %}{% endblock jsfiles %}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

post_detail.html

This template deals with the displaying of the post in detail. It shows the complete post details along with like and comment count. It also has a section to post comments (comment form is displayed) and below it is the section where all comments are displayed.

Also, there are links to update and delete posts. It only displays these links when the person is the same as the owner of the post.

Otherwise, it won’t show these buttons to any other person.
It also has the same AJAX script to handle like and unlike without refreshing.

{% extends "feed/layout.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block cssfiles %}
{% endblock cssfiles %}
{% block searchform %}
<form class="form-inline my-2 my-lg-0 ml-5" action="{% url 'search_posts' %}" method="get">
  <input name="p" type="text" placeholder="Search posts by tags..">
  <button class="btn btn-success my-2 my-sm-0 ml-4" type="submit">Search</button>
</form>
{% endblock searchform %}
{% block content %}
    <div class="container">
        <div class="row">
          <div class="col-md-8">
            <div class="card card-signin my-5">
              <div class="card-body">
                <a href="{{ post.user_name.profile.get_absolute_url }}"><img src="{{ post.user_name.profile.image.url }}" class="rounded-circle" width="30" height="30" alt=""></a>
                <a class="text-dark" href="{{ post.user_name.profile.get_absolute_url }}"><b>{{ post.user_name }}</b></a>
                <br><small class="text-muted">Posted on {{ post.date_posted }}</small>
                <br><br>
                <p class="card-text text-dark">{{ post.description }}</p>
              </div>
              <a href="{{ post.pic.url }}"><img class="card-img-top" src="{{ post.pic.url }}" alt=""></a>
              {% if post.tags %}
              <br><p class="text-danger ml-3"><b>Tags: <i>{{ post.tags }}</i></b></p>
              {% endif %}
              <div class="card-footer">
                <button class="btn btn-white mr-3 like" id="{{ post.id }}">
                    {% if is_liked %}
                        <a href="{% url 'post-like' %}" style="color:red;" id="likebtn{{ post.id }}"> Unlike | {{post.likes.count}}</a>
                    {% else %}
                        <a href="{% url 'post-like' %}" style="color:green;" id="likebtn{{ post.id }}"> Like | {{post.likes.count}}</a>
                    {% endif %}
                </button>

                {% if post.user_name == user %}
                <a class="btn btn-outline-info mr-3" href="{% url 'post-update' post.id %}">Update</a>
                <a class="btn btn-outline-danger" href="{% url 'post-delete' post.id %}">Delete</a>
                {% endif %}
              </div>
            </div>
           </div>
        </div>
        <br>
        <h4>Comments</h4>
        <div class="row">
            <div class="col-md-8">
                <div class="card card-signin my-5">
                    <div class="card-body">
                        <form class="form-signin" method = "POST">
                            {% csrf_token %}
                            <fieldset class="form-group"><br>
                                {{ form|crispy }}
                            </fieldset>
                            <div class="form-group">
                                <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Comment</button><br>
                            </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-md-8">
                {% if post.details.all %}
                <div class="card card-signin my-5">
                {% for detail in post.details.all %}
                    <div class="card-body">
                        <a href="{{ detail.username.profile.get_absolute_url }}">
                            <img src="{{ detail.username.profile.image.url }}" class="rounded-circle" width="30" height="30" alt="">
                        </a>
                        <a class="text-dark" href="{{ detail.username.profile.get_absolute_url }}"><b>{{ detail.username }}</b></a>
                        <br><small>{{ detail.comment_date }}</small><br><br>
                        <p class="card-text text-dark">{{ detail.comment }}</p>
                    </div>
                    <hr class="my-1">
                {% endfor %}
                </div>
                {% else %}
                    <p>No comments to show!</p>
                {% endif %}
            </div>
        </div>
    </div>
{% endblock content %}
{% block jsfiles %}
<script>
    $(".like").click(function (e) {
    var id = this.id;
    var href = $('.like').find('a').attr('href');
    e.preventDefault();

    $.ajax({
        url: href,
        data: {
          'likeId': id
        },
        success: function(response){
          if(response.liked){
            $('#likebtn' + id).html("Unlike | {{post.likes.count}}");
            $('#likebtn' + id).css("color", "red")
          }
          else{
            $('#likebtn' + id).html("Like | {{post.likes.count}}");
            $('#likebtn' + id).css("color", "green")
          }
        }
      })
});
</script>
{% endblock jsfiles %}
Enter fullscreen mode Exit fullscreen mode

search_posts.html

This part displays the posts which match the search terms. It displays all relevant posts (search results).

This is similar to the homepage as we need to display posts but unlike homepage which displays all posts, here we display selective posts only which matches the search term.

All other components are the same as the homepage like AJAX script to handle likes and display style.

{% extends "feed/layout.html" %}
{% load static %}
{% block cssfiles %}
{% endblock cssfiles %}
{% block searchform %}
<form class="form-inline my-2 my-lg-0 ml-5" action="{% url 'search_posts' %}" method="get">
  <input name="p" type="text" placeholder="Search posts..">
  <button class="btn btn-success my-2 my-sm-0 ml-4" type="submit">Search</button>
</form>
{% endblock searchform %}
{% block content %}
    <div class="container">
        <div class="row">
          <div class="col-md-8">
            {% if not posts %}
            <br>
            <h2><i>No posts match the tag provided!</i></h2>
            {% endif %}

            {% for post in posts %}
            <div class="card card-signin my-5">
              <div class="card-body">
                <a href="{{ post.user_name.profile.get_absolute_url }}"><img src="{{ post.user_name.profile.image.url }}" class="rounded-circle" width="30" height="30" alt=""></a>
                <a class="text-dark" href="{{ post.user_name.profile.get_absolute_url }}"><b>{{ post.user_name }}</b></a>
                <br><small class="text-muted">Posted on {{ post.date_posted }}</small>
                <br><br>
                <p class="card-text text-dark">{{ post.description }}</p>
              </div>
              <a href="{% url 'post-detail' post.id %}"><img class="card-img-top" src="{{ post.pic.url }}" alt=""></a>
              {% if post.tags %}
              <br><p class="text-danger ml-3"><b>Tags: <i>{{ post.tags }}</i></b></p>
              {% endif %}
              <div class="card-footer">
                <button class="btn btn-white mr-3 like" id="{{ post.id }}">
                    {% if post in liked_post %}
                        <a href="{% url 'post-like' %}" style="color:red;" id="likebtn{{ post.id }}"> Unlike</a> | {{post.likes.count}}
                    {% else %}
                        <a href="{% url 'post-like' %}" style="color:green;" id="likebtn{{ post.id }}"> Like</a> | {{post.likes.count}}
                    {% endif %}
                </button>
                <a class="btn btn-outline-info" href="{% url 'post-detail' post.id %}">Comments | {{ post.details.count }}</a>
              </div>
            </div>
            {% endfor %}
           </div>
        </div>
    </div>
{% endblock content %}
{% block jsfiles %}
<script>
    $(".like").click(function (e) {
    var id = this.id;
    var href = $('.like').find('a').attr('href');
    e.preventDefault();

    $.ajax({
        url: href,
        data: {
          'likeId': id
        },
        success: function(response){
          if(response.liked){
            $('#likebtn' + id).html("Unlike");
            $('#likebtn' + id).css("color", "red")
          }
          else{
            $('#likebtn' + id).html("Like");
            $('#likebtn' + id).css("color", "green")
          }
        }
      })
});
</script>
{% endblock jsfiles %}
Enter fullscreen mode Exit fullscreen mode

user_posts.html

This page displays posts by a particular user. It is almost same as the homepage with pagination, same AJAX script to handle likes and the same display style for posts.

The only difference is it displays only posts by a particular user.

{% extends "feed/layout.html" %}
{% load static %}
{% block cssfiles %}
{% endblock cssfiles %}
{% block searchform %}
<form class="form-inline my-2 my-lg-0 ml-5" action="{% url 'search_posts' %}" method="get">
  <input name="p" type="text" placeholder="Search posts..">
  <button class="btn btn-success my-2 my-sm-0 ml-4" type="submit">Search</button>
</form>
{% endblock searchform %}
{% block content %}
    <div class="container">
        <div class="row">
          <div class="col-md-8">
            {% for post in posts %}
            <div class="card card-signin my-5">
              <div class="card-body">
                <a href="{{ post.user_name.profile.get_absolute_url }}"><img src="{{ post.user_name.profile.image.url }}" class="rounded-circle" width="30" height="30" alt=""></a>
                <a class="text-dark" href="{{ post.user_name.profile.get_absolute_url }}"><b>{{ post.user_name }}</b></a>
                <br><small class="text-muted">Posted on {{ post.date_posted }}</small>
                <br><br>
                <p class="card-text text-dark">{{ post.description }}</p>
              </div>
              <a href="{% url 'post-detail' post.id %}"><img class="card-img-top" src="{{ post.pic.url }}" alt=""></a>
              {% if post.tags %}
              <br><p class="text-danger ml-3"><b>Tags: <i>{{ post.tags }}</i></b></p>
              {% endif %}
              <div class="card-footer">
                <button class="btn btn-white mr-3 like" id="{{ post.id }}">
                    {% if post in liked_post %}
                        <a href="{% url 'post-like' %}" style="color:red;" id="likebtn{{ post.id }}"> Unlike</a> | {{post.likes.count}}
                    {% else %}
                        <a href="{% url 'post-like' %}" style="color:green;" id="likebtn{{ post.id }}"> Like</a> | {{post.likes.count}}
                    {% endif %}
                </button>
                <a class="btn btn-outline-info" href="{% url 'post-detail' post.id %}">Comments | {{ post.details.count }}</a>
              </div>
            </div>
            {% endfor %}
           </div>
        </div>
    </div>

{% if is_paginated %}

      {% if page_obj.has_previous %}
        <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
      {% endif %}

      {% for num in page_obj.paginator.page_range %}
        {% if page_obj.number == num %}
          <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
          <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
      {% endif %}

    {% endif %}


{% endblock content %}
{% block jsfiles %}
<script>
    $(".like").click(function (e) {
    var id = this.id;
    var href = $('.like').find('a').attr('href');
    e.preventDefault();

    $.ajax({
        url: href,
        data: {
          'likeId': id
        },
        success: function(response){
          if(response.liked){
            $('#likebtn' + id).html("Unlike");
            $('#likebtn' + id).css("color", "red")
          }
          else{
            $('#likebtn' + id).html("Like");
            $('#likebtn' + id).css("color", "green")
          }
        }
      })
});
</script>
{% endblock jsfiles %}
Enter fullscreen mode Exit fullscreen mode

So, that’s all for the Feed App Templates. It was a lot of HTML content. Hope you are not bored.

Hope you enjoyed the tutorial. It was the last part in the five-part series of the Django Social Media tutorial.

I hope you all liked the complete series. I hope you all will be able to make your own social media project with Django and can add more aspects to it like tagging, showing posts only by friend circle, an enhanced recommendation system and much more.

The Github Repo for the complete series is here. Feel free to comment or make any pull requests to this repository which would help enhance it in any way.

Top comments (0)