Building a Hacker News clone in Django - Part 1

You are reading a post from a four-part tutorial series

There is no better way to learn something than to watch someone else do it1. So, if you have been waiting to go beyond the basics in Django, you have come to the right place.

In this video tutorial series, I would take you through building a social news site called “Steel Rumors” from scratch in Django 1.5. In case you don’t like videos and prefer to read the steps, you can find them here too.

Even though we will start from the basics, if you are an absolute beginner to Django, I would suggest reading the tutorial or my previous screencast on building a blog in 30 mins

The completed site would support user signups, link submissions, comments, voting and a cool ranking algorithm. My approach would be to use as many builtin Django features as possible and use external apps only when absolutely necessary.

Check out a [demo of Steel Rumors][demo] yourself.

Click on the image below to watch the screencast or scroll down to read the steps.

Screencast

If you liked this tutorial, then you should sign up for my upcoming book “Building a Social News Site in Django”. It tries to explain in a learn-from-a-friend style how websites are built and gradually tackles advanced topics like database migrations and debugging.

Step-by-step Instructions

Here is the text version of the video for people who prefer to read. We are going to create a social news site similar to Hacker News or Reddit. It will be called “Steel Rumors” and would be a place to share and vote some interesting rumors about “Man of Steel”.

The outline of Part 1 of the screencast is:

  • Objective
  • VirtualEnv - Start from Scratch!
  • Model Managers - Dream Job #78
  • Basic Template
  • Generic Views - ListView and DetailView
  • Pagination - For free!

Setup Virtual Environment

  1. We will create a virtual development environment using virtualenv and virtualenvwrapper. Make sure you have installed them first:

    mkvirtualenv djangorocks
    

    I use an Ubuntu variant called Xubuntu in my screencast. But you should be able to replicate these steps in other OSes with minimal changes.

  2. Install Django (make sure you already have pip installed)

    pip install Django==1.5
    

    You can also use Django 1.5.1. The latest Django version may or may not work with our code, hence it is better to specify a version to follow this tutorial.

Create Project and Apps

  1. Create a project called steelrumors:

    cd ~/proj
    django-admin.py startproject steelrumors
    cd steelrumors
    chmod +x manage.py
    
  2. Open steelrumors/settings.py in your favourite editor. Locate and change the following lines (changes in bold):

    1. ENGINE’: ‘django.db.backends.sqlite3
    2. NAME’: ‘database.db‘,
    3. At the end of INSTALLED_APPS = ( ‘django.contrib.admin’,
  3. Next, change steelrumors/urls.py by uncommenting the following lines:

    from django.conf.urls import patterns, include, url
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        url(r'^admin/', include(admin.site.urls)),
    )
    
  4. Sync to create admin objects and enter the admin details:

    ./manage.py syncdb
    
  5. Open a new tab or a new terminal and keep a server instance running (don’t forget to issue workon djangorocks in this terminal):

    ./manage.py runserver
    
  6. Visit the admin page (typically at http://127.0.0.1:8000/admin/) and login.

  7. Create links app:

    ./manage.py startapp links
    
  8. Enter the following two model classes into links/models.py:

    from django.db import models
    from django.contrib.auth.models import User
    
    class Link(models.Model):
        title = models.CharField("Headline", max_length=100)
        submitter = models.ForeignKey(User)
        submitted_on = models.DateTimeField(auto_now_add=True)
        rank_score = models.FloatField(default=0.0)
        url = models.URLField("URL", max_length=250, blank=True)
        description = models.TextField(blank=True)
    
        def __unicode__(self):
            return self.title
    
    class Vote(models.Model):
        voter = models.ForeignKey(User)
        link = models.ForeignKey(Link)
    
        def __unicode__(self):
            return "%s upvoted %s" % (self.voter.username, self.link.title)
    
  9. Create the corresponding admin classes. Enter the following into links/admin.py:

    from django.contrib import admin
    from .models import Link, Vote
    
    class LinkAdmin(admin.ModelAdmin): pass
    admin.site.register(Link, LinkAdmin)
    
    class VoteAdmin(admin.ModelAdmin): pass
    admin.site.register(Vote, VoteAdmin)
    
  10. Enter the following into links/views.py:

    from django.views.generic import ListView
    from .models import Link, Vote
    
    class LinkListView(ListView):
        model = Link
    
  11. Insert following lines into steelrumor/urls.py:

    from links.views import LinkListView
    ...
    urlpatterns = patterns('',
        url(r'^$', LinkListView.as_view(), name='home'),
    
  12. Create a new templates directory and enter the following at steelrumors/templates/links/link_list.html:

    <ol>
    {% for link in object_list %}
        <li>
        <a href="{{ link.url }}">
          <b>{{ link.title }}</b>
        </a>
        </li>
    {% endfor %}
    </ol>
    
  13. Edit settings.py to add our two apps to the end of INSTALLED_APPS = (

    'links',
    'steelrumors',
    )
    
  14. Sync to create link objects:

    ./manage.py syncdb
    

    Visit http://127.0.0.1:8000/admin/ and add a couple of Link objects. Now if you open http://127.0.0.1:8000/ you should see the added Links

Add Branding

  1. Create a common base template at steelrumors/templates/base.html:

    <html>
    <body>
    <h1>Steel Rumors</h1>
    
    {% block content %}
    {% endblock %}
    
    </body>
    </html>
    
  2. Modify steelrumors/templates/links/link_list.html and surround previous code with this:

    {% extends "base.html" %}
    
    {% block content %}
    ...
    {% endblock %}
    

VoteCount Model Manager

  1. We need a count of votes within our generic ListView. Add these to links/models.py:

    from django.db.models import Count
    
    class LinkVoteCountManager(models.Manager):
        def get_query_set(self):
            return super(LinkVoteCountManager, self).get_query_set().annotate(
                votes=Count('vote')).order_by('-votes')
    
  2. Insert these two lines into the Link class in links/models.py:

    class Link(models.Model):
    ...
    
        with_votes = LinkVoteCountManager()
        objects = models.Manager() #default manager
    
  3. Edit links/views.py and insert these two lines into the LinkListView class:

    class LinkListView(ListView):
    ...
    
        queryset = Link.with_votes.all()
        paginate_by = 3
    

Crazy Fun

You can add 100 votes to random headlines using the following lines in the django shell:

$ ./manage.py shell
>>> from links.models import Link, Vote
>>> from django.contrib.auth.models import User
>>> a = User.objects.all()[0]
>>> for i in xrange(100): Vote(link=Link.objects.order_by('?')[0],voter=a).save()

Now visit http://127.0.0.1:8000/admin/ to find lots of Votes objects.

Final Comments

In case, you are wondering if this version of the site would be useful, I would say that it works well for a private beta version. Any new user would have to be added by the admin interface manually. They must be of the staff kind if they have to login via the admin interface. Staff can vote by manually creating Vote objects.

The public facing part of the site can still show the top rumors based the votes received by the staff. Based on how well designed the templates are, this version could be also used to get feedback about the site’s design and branding.

That concludes Part 1. Follow me on Twitter at @arocks to get updates about the following parts.

EDIT: Check out Part 2!

Resources


  1. This became very controversial. Most people learn best by doing it themselves. But they need to read/hear/see it first from someone. I am actually a self-taught programmer. I learnt most of programming from books and trying it myself.

    Learning by doing is certainly the best way to learn. But among source of learning, watching an expert is probably the best. 

Hi! Welcome to ArunRocks, an odd collection of writeups on programming, travel, gadgets and practically anything under the sun. This state of affairs could be blamed on the ecelectic interests of your host, Arun Ravindran. He loves programming in several languages especially Python. In his day job he works as a Solution Manager at Unisys. Read more...

Comments