Django – How I Added More Views to My Hacker News Clone

The beginning of this project tutorial on coding the Hacker News clone saw us create three models and two view functions to be rendered on templates for users to view. The first view function simply displays all the posts in the database while the second displays recent posts.

As you can see, the templates contain links to several pages that haven’t yet been created.

Let’s see how far we can go about creating all these views. Remember, the purpose of this project is to strengthen your understanding of Python and Django as used in web development.

The Past Posts

The second view function of the previous series shows the most recent posts. What if we want to view past posts? The Hacker News website has a link to see posts submitted 30 minutes ago or less. We want to add that feature to our application.

from django.utils import timezone
from datetime import datetime, timedelta


def PastPostListView(request):
    time = str((datetime.now(tz=timezone.utc) - timedelta(minutes=30)))
    posts = Post.objects.filter(created_on__lte = time)
    for post in posts:
        post.count_votes()
        post.count_comments()

    context={
        'posts': posts,
    }
    return render(request, 'pastpostlist.html', context)

This view function filters the posts posted 30 minutes ago. It does that by first defining the time using the datetime module. The module gets the user’s current time zone, subtract 30 minutes from it using the timedelta type, and passes it to the query function. The function has a created_on__lte parameter.

The created_on is the name of the datetime field in the database (check your models.py file) while __lte here means less than or equal.

Thus, it queries the database and filters it based on posts submitted 30 minutes ago or less.

The URL will be like this:

path('past', PastPostListView, name='past_home'),

And the template will be the same as the previous two templates.

Displaying user’s info

On the Hacker News website, each post comes with the name of the person (creator) who submitted it. Also, a link is attached to the name for others to click to learn more about the creator. So, we have to create the views for such.

What do we expect to see on the user’s info page? Of course, the name of the creator, and a link showing all the posts created by the creator. That means we will create two view functions. One for information about the creator and the other for all the posts created by the creator.

def UserInfoView(request,username):
    user = User.objects.get(username=username)
    context = {
        'user': user,
    }
    return render(request, 'user_info.html', context)

Here, we call the User class to retrieve information about a particular user and pass it to the render() function which will then be displayed in the template. Since a username is passed as a parameter to the function, the route will also include the username as we can see below:

path('user/<username>', UserInfoView, name='user_info'),

And the template will be like this:

{% extends 'base.html' %}
{% block content %}

<div class="topnav">
  <a class="active" href="{% url 'home'%}">Hacker News</a>
  <a href="{% url 'new_home'%}">New</a>
  <a href="{% url 'past_home'%}">Past</a>
  <a href="{% url 'submit'%}">Submit</a>

  {% if request.user.is_authenticated %}
    <div class="topnav-right">
      <a href="{% url 'signout' %}">Sign Out </a>
    </div>
  {% else %}

<div class="topnav-right">
      <a href="{% url 'signin' %}">Sign In </a>
    </div>
  {% endif %}
</div>

<div class="w3-panel w3-light-grey w3-leftbar w3-border-grey">
<p><strong>User: </strong>{{user.username}}</p>
<p><strong>Created: </strong>{{user.date_joined}}</p>
</div>

<a href="{% url 'user_posts' user.username %}">Submissions</a>

Once an individual is logged in, he can see information about the creator. Notice that the user info also shows the date the creator joined our Hacker News clone website.

Now, to see all the posts by a particular creator, there is also a link attached to ‘Submissions.’  This is the second view function we will now create.

def UserSubmissions(request,username):
    user = User.objects.get(username=username)
    posts = Post.objects.filter(creator = user)
    for post in posts:
        post.count_votes()
        post.count_comments()
    return render(request, 'user_posts.html', {'posts': posts})

We call two model Classes in this view function. The first was used to get a particular user (creator) using the username parameter passed to the function, and passes it to the Post class. The second filters all posts based on a particular creator it received from the User class.

As usual, the votes and comments fields for each post have to be populated with their respective number by calling the methods.

The URL routes will be:

path('posts/<username>',UserSubmissions, name='user_posts'),

Did you see how the route corresponds to the link attached to ‘Submission’ in the template above? By using the name specified in the URL route, we are referring to this view function. But that is not all. Remember that in the previous view function, the user variable was passed in the context dictionary.

It encompasses every information about the user. Therefore to get the name of the user that will be passed as a parameter to this second view function, user.username has to be included in the link. Once the name is passed to this view function, every post submitted by the person bearing that name will be displayed.

The template for this view function will be:

{% extends 'base.html' %}
{% block content %}

<div class="topnav">
  <a class="active" href="{% url 'home'%}">Hacker News</a>
  <a href="{% url 'new_home'%}">New</a>
  <a href="{% url 'past_home'%}">Past</a>
  <a href="{% url 'submit'%}">Submit</a>

  {% if request.user.is_authenticated %}
    <div class="topnav-right">
      <a href="{% url 'signout' %}">Sign Out </a>
    </div>
  {% else %}
    <div class="topnav-right">
      <a href="{% url 'signin' %}">Sign In </a>
    </div>
  {% endif %}
</div>
<ol>
{% for post in posts %}
  <div class="w3-panel w3-light-grey w3-leftbar w3-border-grey">
  <li><p><a href = "{{ post.url }}">{{ post.title }}</a></p>
  <p>{{ post.votes }} | Created {{ post.created_on }}| <a href = "{% url 'user_info' post.creator.username %}">{{ post.creator.username }}</a> | <a href="{% url 'post' post.id %}"> {{ post.comments }} Comments</a></p></li>
</div>
{% endfor %}
</ol>
{% endblock %}

All the templates we have created so far may appear to look similar but they are not. This one displays every field we created in the post model based on a given creator. Again, using Django template engine in the {% … %} and {{ … }} blocks simplifies everything for us.

Conclusion

So far, we have created five views for the Hacker News clone Django website. Make sure you import the corresponding modules and functions at the top of the urls.py and views.py files. You can always check the full code on GitHub.

Take your time to go through what we have done. if certain things are not clear, you can check the documentation. When we are done coding this application, we will test everything – not in the test.py file – but by populating our database using information from the Hacker News website.

Hopefully, it will help clarify some points. See you in the next series.

🔗 Recommended: How I Coded a Hacker News Clone in Django