Building a Hacker News clone in Django - Part 3 (Comments and CRUD)

7 min read · Posted on: Jul 9, 2013 · Print this page

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

It feels like ages since part 2 was out. Meanwhile, the movie Man of Steel has actually been released. So there is really no need to check for rumors about what that movie is all about. Probably the industry is now abuzz with rumours about its sequel instead.

In this tutorial, I would be showing how to use features like comments and CRUD views which are integral to a social site. You can choose to watch the video or read the step by step description below or follow both. The goodies pack which was introduced in the last part has been updated and would be used again to save time to create templates.

This video would be a continuation of the previous video and I recommend watching it. Click on the image below to watch the screencast or scroll down to read the steps.

Screencast

Enjoyed 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 testing, security, database migrations and debugging.

Step-by-step Instructions

Here is the text version of the video for people who prefer to read. In part 2, we showed you how to create a beta-like site to publish rumors about “Man of Steel” where users can sign-up and create their own profiles.

The outline of Part 3 of the screencast is:

  • Comments framework
  • Create/Read/Update/Delete of a Link
  • Pagination

Get the goodies pack again

The goodies pack has changed since the last tutorial, so I would recommend downloading it again.

  1. Download sr-goodies-master.zip to any convenient location. On Linux, you can use the following commands to extract it to the /tmp directory.

        cd /tmp
        wget https://github.com/arocks/sr-goodies/archive/master.zip
        unzip master.zip
    

    Explore the extracted files in /tmp/sr-goodies-master

Pagination

So far we have been seeing just the first page of the list of links. But we are using Django’s ListView which provides pagination. Let’s implement a simple ‘Next’ link to visit the next page.

  1. Add the following snippet to steelrumors/templates/links/link_list.html just before {% endblock %}:

        {% if is_paginated %}
        <div class="pagination">
            {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">More &raquo;</a>
            {% endif %}
        </div>
        {% endif %}
    
  2. In the same template file we’ll need to change the first <ol> tag to ensure that the line numbers in page 2 and later appear correctly. Replace that line with <ol> with these lines:

        {% if is_paginated %}
        <ol start="{{ page_obj.start_index }}">
        {% else %}
        <ol>
        {% endif %}
    

    Now you can visit every page and read every submitted link!

  1. We have been using the admin interface to create/update/delete links. But this is only accessible to staff members. To allow users to submit links, we will need a new form, a new view class (generic CBV) and a new template with a form.

    Add this new ModelForm to `links/forms.py`:
    
        from .models import Link
        ...
    
        class LinkForm(forms.ModelForm):
            class Meta:
                model = Link
                exclude = ("submitter", "rank_score")    
    
  2. Time to implement the “C” of CRUD by importing CreateView and the form you just created:

    Add the `LinkCreateView` class to `links/views.py`:
    
        from django.views.generic.edit import CreateView
        from .forms import LinkForm
        ...
    
        class LinkCreateView(CreateView):
            model = Link
            form_class = LinkForm
    
            def form_valid(self, form):
                f = form.save(commit=False)
                f.rank_score = 0.0
                f.submitter = self.request.user
                f.save()
    
                return super(CreateView, self).form_valid(form)
    
  3. Copy link_form.html from goodies to steelrumors/templates/links/link_form.html:

        cp /tmp/sr-goodies-master/templates/links/link_form.html \
           ~/proj/steelrumors/steelrumors/templates/links/
    
  4. Add this view in steelrumours/urls.py:

        from links.views import LinkCreateView        
    
        url(r'^link/create/$', auth(LinkCreateView.as_view()),
            name='link_create'),
    

    Visit http://127.0.0.1:8000/link/create/ and submit a new link. Remember, you’ll need to be logged in to submit links.

  5. If you try to submit a link, you will see an error message asking you to “Either provide a url or define a get_absolute_url method on the Model.” So let’s define the get_absolute_url method.

    Add the following method in the Link class:

        from django.core.urlresolvers import reverse
        ...
    
        def get_absolute_url(self):
            return reverse("link_detail", kwargs={"pk": str(self.id)})
    
  6. Create a DetailView in links/views.py. This is the “R” of CRUD.

        from django.views.generic import DetailView
        ...
    
        class LinkDetailView(DetailView):
            model = Link
    
  7. Copy link_detail.html from goodies to steelrumors/templates/links/link_detail.html:

        cp /tmp/sr-goodies-master/templates/links/link_detail.html \
           ~/proj/steelrumors/templates/links/
    
  8. Add this detail view in steelrumours/urls.py:

        from links.views import LinkDetailView        
    
        url(r'^link/(?P<pk>\d+)/$', LinkDetailView.as_view(),
            name='link_detail'),
    

    Try submitting the add link form again and it should take you to the detail page, without error.

  9. For convenience, let’s add the urls to these new views in our template so that users can easily find them. Add only the line with a + sign to base.html (remove the + sign):

        {% if user.is_authenticated %}
        +   <a href="{% url 'link_create' %}">Submit Link</a> | 
            <a href="{% url 'logout' %}">Logout</a> |
    

    Make the following change (changed line starts with a + sign) to steelrumors/templates/links/link_list.html:

        {% for link in object_list %}
            <li> [{{ link.votes }}]
        +   <a href="{% url 'link_detail' pk=link.pk %}">
              <b>{{ link.title }}</b>
    

    Refresh the steelrumours site on your browser and check if all the links work correctly.

CRUD - Update and Delete

  1. The remaining two views for Update and Delete are straight forward and we can add them together. We are going to reuse the LinkForm, so let’s start with views.

    Add these view classes to links/views.py:

        from django.core.urlresolvers import reverse_lazy
        from django.views.generic.edit import UpdateView
        from django.views.generic.edit import DeleteView
        ...
    
        class LinkUpdateView(UpdateView):
            model = Link
            form_class = LinkForm
    
        class LinkDeleteView(DeleteView):
            model = Link
            success_url = reverse_lazy("home")
    
  2. Copy link_confirm_delete.html from goodies to steelrumors/templates/links/link_confirm_delete.html:

        cp /tmp/sr-goodies-master/templates/links/link_confirm_delete.html \
           ~/proj/steelrumors/templates/links/
    
  3. Add these views in steelrumours/urls.py:

        from links.views import LinkUpdateView
        from links.views import LinkDeleteView
    
            url(r'^link/update/(?P<pk>\d+)/$', auth(LinkUpdateView.as_view()),
                name='link_update'),
            url(r'^link/delete/(?P<pk>\d+)/$', auth(LinkDeleteView.as_view()),
                name='link_delete'),
    
  4. Finally, for convenience, add these lines (with + sign) to steelrumors/templates/links/link_detail.html:

        <h2><a href="{{ object.link }}">{{ object.title }}</a></h2>
        + {% if object.submitter == user %}
        +  <a href="{% url 'link_update' pk=object.pk %}">Edit</a> | 
        +  <a href="{% url 'link_delete' pk=object.pk %}">Delete</a>
        + {% endif %}
    

    Now, you can create, read, update and delete Link objects. Try it!

Enabling Comments

  1. We are going to add comments to the link detail pages using the built-in Django comments framework. First add this applications in steelrumors/settings.py:

        INSTALLED_APPS = (
    
            'django.contrib.admin',
        +    'django.contrib.comments',
    

    Run syndb to create the tables required by the comments app:

        ./manage.py syncdb
    
  2. Copy the new link_detail.html page from the goodies pack:

        cp /tmp/sr-goodies-master/templates/links/link_detail2.html \
           ~/proj/steelrumors/templates/links/link_detail.html
    
  3. We need to show comment counts in the front page itself. So add the following lines to steelrumors/templates/links/link_list.html at the beginning and middle of the template:

        {% extends "base.html" %}
        + {% load comments %}
        ...
    
        <a href="{% url 'link_detail' pk=link.pk %}">
        <b>{{ link.title }}</b>
        +  {% get_comment_count for link as comment_count %}
        +  {{ comment_count }} comment{{ comment_count|pluralize }}
        </a>
    
  4. Add this line to steelrumours/urls.py for wiring up the comments app:

            url(r'^comments/', include('django.contrib.comments.urls')),
    

    Now, open any link detail page and have fun writing comments!

Fun with Random Gossip

Add a mixin class called RandomGossipMixin in links/views.py before LinkListView class:

from django.contrib.comments.models import Comment
...

class RandomGossipMixin(object):
    def get_context_data(self, **kwargs):
        context = super(RandomGossipMixin, self).get_context_data(**kwargs)
        context[u"randomquip"] = Comment.objects.order_by('?')[0]
        return context

Change the class declaration of LinkListView to include this mixin as a base class:

class LinkListView(RandomGossipMixin, ListView):

Add the following lines to steelrumors/templates/links/link_list.html before the endblock line:

<blockquote style="background-color: #ddd; padding: 4px; border-radius: 10px; margin: 10px 0; color: #666; font-size: smaller; text-shadow: rgba(255,255,255,0.8) 1px 1px 0;">
{{ randomquip.comment|truncatechars:140 }}
</blockquote>

Now refresh the home page and enjoy a random comment appear at the bottom of the page.

Final Comments

We have a lot more feature-complete social news site at this point. Users can actually submit links and comment about them. In the next and concluding part, we will cover writing mixins and ranking algorithms in Django. With this users will be able to vote and influence the ranking of links.

That concludes Part 3. Follow me on Twitter at @arocks to get updates about upcoming parts.

EDIT: Check out Part 4!

Resources


Arun Ravindran profile pic

Arun Ravindran

Arun is the author of "Django Design Patterns and Best Practices". Works as a Product Manager at Google. Avid open source enthusiast. Keen on Python. Loves to help people learn technology. Find out more about Arun on the about page.

Don't miss any future posts!

Comments →

Next: ▶   Building a Hacker News clone in Django - Part 4 (AJAX and Mixin)

Prev: ◀   Building a Hacker News clone in Django - Part 2 (User Profiles and Registrations)

Up: ▲   Blog

Featured Posts

Frequent Tags

banner ad for Django book

Comments

powered by Disqus