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

6 min read · Posted on: Jun 6, 2013 · Print this page

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

It has been more than a week since the first part of this tutorial series was posted and I’ve been getting positive feedback from several channels. Ironically, it was extremely popular on Hacker News. It was even translated into Chinese.

To be sure, the objective was not to create a full featured clone of any website. The real objective is to learn Django using a medium-sized project that utilises it to the fullest. Many tutorials fall short of bringing together various parts of Django. Compared to a microframework like Flask (which is also great, btw), Django comes with a lot of batteries included. If you are short on time, this makes it ideal for completing an ambitious project like this.

This tutorial would show you how to implement social features like supporting user registration and profile pages. We will leverage Django’s class based views for building CRUD functionality. There is a lot of ground to be covered this time.

As before, there is a text description of the steps if you do not prefer to watch the entire video. There is also a goodies pack with templates and other assets included, which would be required if you are following this tutorial.

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 1, we showed you how to create a private beta-like site to publish rumors about “Man of Steel”.

The outline of Part 2 of the screencast is:

  • Better branding and templates
  • Custom login/logout
  • Sociopath to actually social - django-registrations
  • Simple registration
  • User Profiles

Open the goodies pack

So far, the appearance of the website looks a bit bland. Let’s use some assets which are pre-designed for the tutorial.

  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

  2. Copy the entire static directory from the extracted files to steelrumors/steelrumors. Also, overwrite the extracted sr-goodies-master/templates/base.html template into steelrumors/steelrumors/templates/

        cp -R /tmp/sr-goodies-master/static ~/proj/steelrumors/steelrumors/
        cp /tmp/sr-goodies-master/templates/base.html ~/proj/steelrumors/steelrumors/templates/
    

Custom Login page

  1. Add to steelrumors/urls.py:

        url(r'^login/$', 'django.contrib.auth.views.login', {
            'template_name': 'login.html'}, name="login"),
        url(r'^logout/$', 'django.contrib.auth.views.logout_then_login',
            name="logout"),
    
  2. Add the login/logout URL locations to steelrumors/settings.py:

        from django.core.urlresolvers import reverse_lazy
    
        LOGIN_URL=reverse_lazy('login')
        LOGIN_REDIRECT_URL = reverse_lazy('home')
        LOGOUT_URL=reverse_lazy('logout')
    
  3. Now copy login.html from the goodies pack to the templates directory:

        cp /tmp/sr-goodies-master/templates/login.html ~/proj/steelrumors/steelrumors/templates/
    

    Refresh your browser to view the newly styled pages.

Using django-registrations

We will be using the “simple” backend of django-registrations since it is easy to use and understand.

  1. Currently, the version of django registration on the the Python Package Index doesn’t work well with Django 1.5. So we will use my forked version using pip:

        pip install git+git://github.com/arocks/django-registration-1.5.git
    

    Or if you don’t have git installed, use:

        pip install https://github.com/arocks/django-registration-1.5/tarball/master
    
  2. Edit settings.py to add registration app to the end of INSTALLED_APPS

        'registration',
        )
    
  3. Run syncdb to create the registration models:

        ./manage.py syncdb
    
  4. We need to use the registration form template from goodies pack. Since, this is for the registration app we need to create a registration directory under templates:

        mkdir ~/proj/steelrumors/steelrumors/templates/registration/
        cp /tmp/sr-goodies-master/templates/registration/registration_form.html
           ~/proj/steelrumors/steelrumors/templates/registration/
    
  5. Add to urls.py:

        url(r'^accounts/', include('registration.backends.simple.urls')),
    

    Visit http://127.0.0.1:8000/accounts/register/ and create a new user. This will throw a “Page not found” error after a user is created.

Create a user’s profile page

  1. Add UserProfile class and its signals to models.py:

        class UserProfile(models.Model):
            user = models.OneToOneField(User, unique=True)
            bio = models.TextField(null=True)
    
            def __unicode__(self):
                return "%s's profile" % self.user
    
        def create_profile(sender, instance, created, **kwargs):
            if created:
                profile, created = UserProfile.objects.get_or_create(user=instance)
    
        # Signal while saving user
        from django.db.models.signals import post_save
        post_save.connect(create_profile, sender=User)
    
  2. Run syncdb again to create the user profile model:

        ./manage.py syncdb
    
  3. Add these to admin.py of links app to replace/extend the default admin for User:

    
        from django.contrib.auth.admin import UserAdmin
        from django.contrib.auth import get_user_model
        ...
        class UserProfileInline(admin.StackedInline):
            model = UserProfile
            can_delete = False
    
        class UserProfileAdmin(UserAdmin):
            inlines=(UserProfileInline, )
    
        admin.site.unregister(get_user_model())
        admin.site.register(get_user_model(), UserProfileAdmin)
    
    

    Visit http://127.0.0.1:8000/admin/ and open any user’s details. The bio field should appear in the bottom.

  4. Add to views.py of links apps:

        from django.views.generic import ListView, DetailView
        from django.contrib.auth import get_user_model
        from .models import UserProfile
        ....
        class UserProfileDetailView(DetailView):
            model = get_user_model()
            slug_field = "username"
            template_name = "user_detail.html"
    
            def get_object(self, queryset=None):
                user = super(UserProfileDetailView, self).get_object(queryset)
                UserProfile.objects.get_or_create(user=user)
                return user
    
  5. Copy user_detail.html from goodies to steelrumors/templates/:

        cp /tmp/sr-goodies-master/templates/user_detail.html \
           ~/proj/steelrumors/steelrumors/templates/
    
  6. Let’s add the urls which failed last time when we tried to create a user. Add to urls.py:

        from links.views import UserProfileDetailView
        ...
        url(r'^users/(?P<slug>\w+)/$', UserProfileDetailView.as_view(), name="profile"),
    

    Now try to create a user and it should work. You should also see the profile page for the newly created user, as well as other users.

  7. You probably don’t want to enter these links by hand each time. So let’s edit base.html by adding the lines with a plus sign ‘+’ (omitting the plus sign) below:

          {% if user.is_authenticated %}
            <a href="{% url 'logout' %}">Logout</a> |
          +  <a href="{% url 'profile' slug=user.username %}"><b>{{ user.username }}</b></a> 
          {% else %}
          +  <a href="{% url 'registration_register' %}">Register</a> | 
    

    Refresh the browser to see the changes.

Edit your profile details

  1. Add UserProfileEditView class to views.py in links app:

        from django.views.generic.edit import UpdateView
        from .models import UserProfile
        from .forms import UserProfileForm
        from django.core.urlresolvers import reverse
    
        class UserProfileEditView(UpdateView):
            model = UserProfile
            form_class = UserProfileForm
            template_name = "edit_profile.html"
    
            def get_object(self, queryset=None):
                return UserProfile.objects.get_or_create(user=self.request.user)[0]
    
            def get_success_url(self):
                return reverse("profile", kwargs={'slug': self.request.user})
    
  2. Create links/forms.py:

        from django import forms
        from .models import UserProfile
    
        class UserProfileForm(forms.ModelForm):
            class Meta:
                model = UserProfile
                exclude = ("user")
    
  3. Add the profile edit view to urls.py. Protect it with an auth decorator to prevent unlogged users from seeing this view.

        from django.contrib.auth.decorators import login_required as auth
        from links.views import UserProfileEditView
        ...
        url(r'^edit_profile/$', auth(UserProfileEditView.as_view()), name="edit_profile"),
    
  4. Copy edit_profile.html from goodies to steelrumors/templates/:

        cp /tmp/sr-goodies-master/templates/edit_profile.html \
           ~/proj/steelrumors/steelrumors
    
  5. Add the following lines to templates/user_detail before the final endblock:

        {% if object.username == user.username %}
        <p><a href='{% url "edit_profile" %}'>Edit my profile</a></p>
        {% endif %}
    

    Now, visit your profile page and try to edit it.

Easter Egg Fun

Add the following lines to user_detail.html before the endblock line:

{% if "zodcat" in object.userprofile.bio %}
<style>
html {
  background: #EEE url("/static/img/zod.jpg");
}
</style>
{% endif %}

Now mention “zodcat” to your bio. You have your easter egg!

Final Comments

We have a much better looking site at the end of this tutorial. While anyone could register to the site, they will not be part of the staff. Hence, you cannot submit links through the admin interface. This will be addressed in the next part.

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

EDIT: Check out Part 3!

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 3 (Comments and CRUD)

Prev: ◀   Building a Hacker News clone in Django - Part 1

Up: ▲   Blog

Featured Posts

Frequent Tags

banner ad for Django book

Comments

powered by Disqus