Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Building Ribbit in Django

by

After implementing our Twitter-clone, Ribbit, in plain PHP and Rails, it's time to introduce the next walk-through: Python! In this tutorial, we'll rebuild Ribbit using Django. Without further delay, let's get started!

Home Page

Step 0 - Bootstrapping

As of the time of this writing, Django 1.4 supports Python 2.5 to 2.7.3. Before proceeding, make sure that you have the apt version by executing python -v in the terminal. Note that Python 2.7.3 is preferred. All throughout this tutorial, we'll use pip as our package manager and virtualenv to set up the Virtual Environments. To install these, fire up the terminal and execute the following commands as root

curl http://python-distribute.org/distribute_setup.py | sudo python
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | sudo python
sudo pip install virtualenv

To set up our Django development evironment, we'll start off by creating a Virtual Environment. Execute the following commands in the terminal (preferrably inside your development directory)

virtualenv --no-site-packages ribbit_env
source ribbit_env/bin/activate

With our Virtual Environment, set up and activated (your command prompt should be changed to reflect the environmen's name), let's move on to installing the dependencies for the project. Apart from Django, we'll be using South to handle the database migrations. We'll use pip to install both of them by executing. Do note that from here on, we'll be doing everything inside the virtualenv. As such, ensure that it's activated before proceeding.

pip install Django South

With all of the dependencies set up, we're ready to move on to creating a new Django Project.


Step 1 - Creating the Project and the Ribbit App

We'll begin by creating a new Django project and our app. cd into your preferred directory and run:

django-admin.py startproject ribbit
cd ribbit
django-admin.py startapp ribbit_app

Next, we'll initialize our git repository and create a .gitignore file inside the Ribbit project that we just created. To do so, run:

git init
echo "*.pyc" >> .gitignore
git add .
git commit -m 'Initial Commit'

Let's move on to editing ribbit/settings.py and configure our project. First, we'll define some constants. Add the following to the top of the file:

import os

PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))  
LOGIN_URL = '/'

PROJECT_PATH will store the location of the directory in which settings.py is stored. This will allow us to use relative paths for future constants. LOGIN_URL, as the name suggests, designates that the root of our site will be the URL to Login.

Moving on, let's configure the database. For the development evironment, sqlite3 is an ideal choice. To do so, edit the DATABASES constant with the following values:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': os.path.join(PROJECT_PATH, 'database.db'),                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Django finds the static files from the directory mentioned in the STATIC_ROOT constant and routes requests to the files to the path specified in STATIC_URL. Configure them so that they reflect the following:

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')

# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'

Do note the use of os.path.join(). The function allows us to relatively specify the path using the PROJECT_PATH constant we defined before.

Next, we need to specify the location that Django needs to look to find the template files. We'll edit the TEMPLATE_DIRS constant to specify the path.

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    os.path.join(PROJECT_PATH, 'templates')
)

Finally, let's add South and ribbit_app to the list of INSTALLED_APPS.

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'south',
    'ribbit_app',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

Next, let's create the directories we defined in the settings and create the database:

mkdir ribbit/static ribbit/templates ribbit_app/static
python manage.py syncdb
python manage.py schemamigration ribbit_app --initial
python manage.py migrate ribbit_app

The syncdb command will create the required tables and create the superuser account. Since we're using South for migrations, we make the initial migration for the app using the syntax schemamigration <app_name> --initial and apply it with python manage.py migrate ribbit_app

Let's start our development server to ensure everything is working correctly.

python manage.py runserver

If everything's fine indeed, you should be greeted with the following page when you visit http://localhost:8000

Landing Page

Further, the project tree should look like:

ribbit
|-- manage.py
|-- ribbit
|   |-- database.db
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- settings.py
|   |-- settings.pyc
|   |-- static
|   |-- templates
|   |-- urls.py
|   |-- urls.pyc
|   |-- wsgi.py
|   `-- wsgi.pyc
`-- ribbit_app
    |-- __init__.py
    |-- __init__.pyc
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0001_initial.pyc
    |   |-- __init__.py
    |   `-- __init__.pyc
    |-- models.py
    |-- models.pyc
    |-- static
    |-- tests.py
    `-- views.py

Before moving on to the next step, let's commit our changes to the repo.

git add .
git commit -m 'Created app and configured settings'

Step 2 - Base Template and Static Files

Following on from the interface tutorial, download the assets and place them within the ribbit_app/static directory. We need to make some edits to style.less for this tutorial. Let's begin with adding some styles for the flash.

.flash {
    padding: 10px;
    margin: 20px 0;
    &.error {
        background: #ffefef;
        color: #4c1717;
        border: 1px solid #4c1717;
    }
    &.warning {
        background: #ffe4c1;
        color: #79420d;
        border: 1px solid #79420d;
    }
    &.notice {
        background: #efffd7;
        color: #8ba015;
        border: 1px solid #8ba015;
    }
}

Next, let's update the width of the input elements and add the error class for the same. Note that the code below contains only the styles that are required to be added or updated. The remaining code remains untouched.

input {
    width: 179px;
    &.error {
        background: #ffefef;
        color: #4c1717;
        border: 1px solid #4c1717;
    }
}

We also need to increase the height of #content.wrapper.panel.right.

height: 433px;

Finally, let's add a right margin to the footer images.

footer {
    div.wrapper {
        img {
            margin-right: 5px;
        }
    }
}

Before moving on, let's commit the changes:

git add .
git commit -m 'Added static files'

Next, let's create the base template, which will be inherited by all the other templates. Django uses it's own templating engine (like ERB or Jade). Define the template in ribbit/templates/base.html with the following content:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet/less" href="{{ STATIC_URL }}style.less">
    <script src="{{ STATIC_URL }}less.js"></script>
</head>
<body>
    <header>
        <div class="wrapper">
            <img src="{{ STATIC_URL }}gfx/logo.png">
            <span>Twitter Clone</span>
            {% block login %}
            <a href="/">Home</a>
            <a href="/users/">Public Profiles</a>
            <a href="/users/{{ username }}">My Profile</a>
            <a href="/ribbits">Public Ribbits</a>
            <form action="/logout">
                <input type="submit" id="btnLogOut" value="Log Out">
            </form>
            {% endblock %}
        </div>
    </header>
    <div id="content">
        <div class="wrapper">
            {% block flash %}
            {% if auth_form.non_field_errors or user_form.non_field_errors or ribbit_form.errors %}
            <div class="flash error">
                {{ auth_form.non_field_errors }}
                {{ user_form.non_field_errors }}
                {{ ribbit_form.content.errors }}
            </div>
            {% endif %}
            {% if notice %}
            <div class="flash notice">
                {{ notice }}
            </div>
            {% endif %}
            {% endblock %}

            {% block content %}

            {% endblock %}
        </div>
    </div>
    <footer>
        <div class="wrapper">
            Ribbit - A Twitter Clone Tutorial
            <a href="http://net.tutsplus.com">
                <img src="{{ STATIC_URL }}gfx/logo-nettuts.png">
            </a>
            <a href="http://www.djangoproject.com/">
                <img src="https://www.djangoproject.com/m/img/badges/djangomade124x25.gif" border="0" alt="Made with Django." title="Made with Django." />
            </a>
        </div>
    </footer>
</body>
</html>

In the above markup, {{ STATIC_URL }} prints the path for the static url defined in settings.py. Another feature is the use of blocks. All the content of the blocks is inherited to the sub-classes of the base template, and will not be overwritten unless the block is explicitly redefined in them. This provides us with some flexibility to place the navigation links at the header and replace it with the login form if the user isn't signed in. We're also using a block with an if condition to check if any of the flash variables are not empty and render the messages appropriately.

Alright! Time to make another commit:

git add .
git commit -m 'Created base template'

Step 3 - Creating the Models

One of the best things about Django is that it includes quite a few models and forms, which can be overridden to suite many purposes. For our application, we'll use the User model and add a few properties to it by creating a UserProfile Model. Further, to manage the ribbits, we'll create a Ribbit Model as well. The User model provided by Django includes fields to store the username, password, first and last names and email address (with validation) along with many others. I suggest you to have a look at the API to know about the all fields supported by default. Add the following code for models in ribbit_app/models.py.

from django.db import models
from django.contrib.auth.models import User
import hashlib


class Ribbit(models.Model):
    content = models.CharField(max_length=140)
    user = models.ForeignKey(User)
    creation_date = models.DateTimeField(auto_now=True, blank=True)


class UserProfile(models.Model):
    user = models.OneToOneField(User)
    follows = models.ManyToManyField('self', related_name='followed_by', symmetrical=False)

    def gravatar_url(self):
        return "http://www.gravatar.com/avatar/%s?s=50" % hashlib.md5(self.user.email).hexdigest()


User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

Let's start with the Ribbit Model. The attributes include a CharField with maximum length of 140 characters to store the content, a ForeignKey to the User model (so that we have a relation between the two models) and a DateTimeField which is automatically populated with the time when the instance of the model is saved.

Moving on to the UserProfile Model, we've a OneToOne field that defines a One to One relation with the User Model and a ManyToMany field to implement the follows/followed_by relation. The related_name parameter allows us to use the relation backwards using a name of our choice. We've also set symmetrical to False to ensure that if User A follows B then User B doesn't automatically follow A. We've also defined a function to get the link to the gravatar image based upon the user's url and a property to get (if the UserProfile exists for the user) or create one when we use the syntax <user_object>.profile. This allows us to fetch the properties of UserProfile quite easily. Here's an example of how you might use the ORM to get the users that a given User follows and is followed by:

superUser = User.object.get(id=1)
superUser.profile.follows.all() # Will return an iterator of UserProfile instances of all users that superUser follows
superUse.profile.followed_by.all() # Will return an iterator of UserProfile instances of all users that follow superUser

Now that our models are defined, let's generate the migrations and apply them:

python manage.py schemamigration ribbit_app --auto
python manage.py migrate ribbit_app

Before moving on, let's commit the changes

git add .
git commit -m 'Created Models'

Step 4 - Creating Forms

Django allows us to create forms so that we can easily validate the data accepted by the user for irregularities. We'll create a custom form for the Ribbit Model andcreate a form that inherits UserCreationForm provided by default to manage the registration. For managing the authentication, we'll extend the AuthenticationForm provided by default in Django. Let's create a new file ribbit_app/forms.py and add the imports.

from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth.models import User
from django import forms
from django.utils.html import strip_tags
from ribbit_app.models import Ribbit

Let's begin with creating the registration form. We'll name it UserCreateForm and it's code is given below:

class UserCreateForm(UserCreationForm):
    email = forms.EmailField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'Email'}))
    first_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'First Name'}))
    last_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'Last Name'}))
    username = forms.CharField(widget=forms.widgets.TextInput(attrs={'placeholder': 'Username'}))
    password1 = forms.CharField(widget=forms.widgets.PasswordInput(attrs={'placeholder': 'Password'}))
    password2 = forms.CharField(widget=forms.widgets.PasswordInput(attrs={'placeholder': 'Password Confirmation'}))

    def is_valid(self):
        form = super(UserCreateForm, self).is_valid()
        for f, error in self.errors.iteritems():
            if f != '__all_':
                self.fields[f].widget.attrs.update({'class': 'error', 'value': strip_tags(error)})
        return form

    class Meta:
        fields = ['email', 'username', 'first_name', 'last_name', 'password1',
                  'password2']
        model = User

In the form above, we've explicitly set some of the fields as mandatory by passing in required=True. Further, I've added the placeholder attribute to the different widgets used by the forms. A class named error is also added to the fields that contain errors. This is done in the is_valid() function. Finally, in the Meta class, we can specify the order in which we want our form fields to render and set the model against which the form should be validated.

Next, let's write the form for the authentication:

class AuthenticateForm(AuthenticationForm):
    username = forms.CharField(widget=forms.widgets.TextInput(attrs={'placeholder': 'Username'}))
    password = forms.CharField(widget=forms.widgets.PasswordInput(attrs={'placeholder': 'Password'}))

    def is_valid(self):
        form = super(AuthenticateForm, self).is_valid()
        for f, error in self.errors.iteritems():
            if f != '__all__':
                self.fields[f].widget.attrs.update({'class': 'error', 'value': strip_tags(error)})
        return form

As in the UserCreateForm, the AuthenticateForm adds some placeholders and error classes.

Finally, let's finish up form, to accept a new Ribbit.

class RibbitForm(forms.ModelForm):
    content = forms.CharField(required=True, widget=forms.widgets.Textarea(attrs={'class': 'ribbitText'}))

    def is_valid(self):
        form = super(RibbitForm, self).is_valid()
        for f in self.errors.iterkeys():
            if f != '__all__':
                self.fields[f].widget.attrs.update({'class': 'error ribbitText'})
        return form

    class Meta:
        model = Ribbit
        exclude = ('user',)

The exclude option in the Meta class above prevents the user field from being rendered. We don't need it since the Ribbit's user will be decided by using sessions.

Let's commit the changes we've made so far

git add .
git commit -m 'Created Forms'

Step 5 - Implementing Sign Up and Login

Django offers great flexibility when it comes to routing. Let's begin by defining some routes in ribbit/urls.py.

urlpatterns = patterns('',
    # Examples:
    url(r'^$', 'ribbit_app.views.index'), # root
    url(r'^login$', 'ribbit_app.views.login_view'), # login
    url(r'^logout$', 'ribbit_app.views.logout_view'), # logout
    url(r'^signup$', 'ribbit_app.views.signup'), # signup
)

Next, let's make use of the models and forms we've just made and write the corresponding views for each route we've just defined.. Let's start by adding the imports in ribbit_app/views.py.

from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.models import User
from ribbit_app.forms import AuthenticateForm, UserCreateForm, RibbitForm
from ribbit_app.models import Ribbit

Followed by the index view:

def index(request, auth_form=None, user_form=None):
    # User is logged in
    if request.user.is_authenticated():
        ribbit_form = RibbitForm()
        user = request.user
        ribbits_self = Ribbit.objects.filter(user=user.id)
        ribbits_buddies = Ribbit.objects.filter(user__userprofile__in=user.profile.follows.all)
        ribbits = ribbits_self | ribbits_buddies

        return render(request,
                      'buddies.html',
                      {'ribbit_form': ribbit_form, 'user': user,
                       'ribbits': ribbits,
                       'next_url': '/', })
    else:
        # User is not logged in
        auth_form = auth_form or AuthenticateForm()
        user_form = user_form or UserCreateForm()

        return render(request,
                      'home.html',
                      {'auth_form': auth_form, 'user_form': user_form, })

For the index view, we first check if the user is logged in or not and render the templates, accordingly. The querysets ribbits_self and ribbits_buddies are merged with the | operator in the above code. Also, we check if an instance of a form has been passed to the method (in the function definition) and if not we create a new one. This allows us to pass around form instances to the appropriate templates and render the errors.

Let's proceed with editing the 'home.html' template which will be used to show the index page for anonymous users. In the ribbit/templates/home.html file, add the following code.

{% extends "base.html" %}

{% block login %}
    <form action="/login" method="post">{% csrf_token %}
        {% for field in auth_form %}
        {{ field }}
        {% endfor %}
        <input type="submit" id="btnLogIn" value="Log In">
    </form>
{% endblock %}

{% block content %}
{% if auth_form.non_field_errors or user_form.non_field_errors %}
<div class="flash error">
    {{ auth_form.non_field_errors }}
    {{ user_form.non_field_errors }}
</div>
{% endif %}
<img src="{{ STATIC_URL}}gfx/frog.jpg">
<div class="panel right">
    <h1>New to Ribbit?</h1>
    <p>
        <form action="/signup" method="post">{% csrf_token %}
            {% for field in user_form %}
            {{ field }}
            {% endfor %}
            <input type="submit" value="Create Account">
        </form>
    </p>
</div>
{% endblock %}

In the template, we inherit the base template defined before, render the authentication and sign up forms by overriding the login block. A neat thing about Django is that it makes CSRF Protection quite easy! All you need to do is add a csrf_token in every form you use in the template.

Let's move on to the buddies.html template, which will show the Buddies' Ribbit page. Edit ribbit/templates/buddies.html and add the following code:

{% extends "base.html" %}
{% block login %}
    {% with user.username as username %}
        {{ block.super }}
    {% endwith %}
{% endblock %}

{% block content %}
    <div class="panel right">
        <h1>Create a Ribbit</h1>
        <p>
            <form action="/submit" method="post">
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type="hidden" value="{{ next_url }}" name="next_url">
            <input type="submit" value="Ribbit!">
            </form>
        </p>
    </div>
    <div class="panel left">
        <h1>Buddies' Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class="ribbitWrapper">
            <a href="/users/{{ ribbit.user.username }}">
                <img class="avatar" src="{{ ribbit.user.profile.gravatar_url }}">
                <span class="name">{{ ribbit.user.first_name }}</span>
            </a>
            @{{ ribbit.user.username }}
            <p>
                {{ ribbit.content }}
            </p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

In this template, we provide the parent template i.e. base.html with the value of username so that the navigation link for the logged in User's profile renders correctly. We're also using an instance of RibbitForm to accept new Ribbits and looping over and showing the current ribbits by our buddies.

With the templates finished, let's move on and write the code to log in/out the user. Add the following code to ribbit_app/views.py

def login_view(request):
    if request.method == 'POST':
        form = AuthenticateForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            # Success
            return redirect('/')
        else:
            # Failure
            return index(request, auth_form=form)
    return redirect('/')


def logout_view(request):
    logout(request)
    return redirect('/')

The views for login expect a HTTP POST request for the login (since the form's method is POST). It validates the form and, if successful, logins the user using the login() method which starts the session and then redirects to the root url. If the validation fails, we pass the instance of the auth_form received from the user to the index function and list the errors, whereas, if the request isn't POST then the user is redirected to the root url.

The logout view is relatively simpler. It utilizes the logout() function in Django which deletes the session and logs the user out followed by redirecting to the root url.

We now need to write the view to sign up and register a user. Append the following code to ribbit_app/views.py.

def signup(request):
    user_form = UserCreateForm(data=request.POST)
    if request.method == 'POST':
        if user_form.is_valid():
            username = user_form.clean_username()
            password = user_form.clean_password2()
            user_form.save()
            user = authenticate(username=username, password=password)
            login(request, user)
            return redirect('/')
        else:
            return index(request, user_form=user_form)
    return redirect('/')

Similar to the login view, the signup view expects a POST request as well and redirects to the root url if the check fails. If the Sign Up form is valid, the user is saved to the database, authenticated, logged in and then redirected to the home page. Otherwise, we call the index function and pass in the instance of user_form submitted by the user to list out the errors.

Let's check our progress by starting the server and testing the views we've written so far manually.

python manage.py runserver

Let's visit our Development Server and try registering a new user. If all goes well, you should be presented with the Buddies' Ribbits page. We can logout and reauthenticate the newly created user to check if the sign in functions work as expected.

Buddies

Time to commit the changes!

git add .
git commit -m 'Implemented User Login and Sign Up'

Step 6 - Accepting new Ribbits and Listing Public Ribbits

In the buddies.html template we created before, the ribbit_form was submitted to /submit. Let's edit ribbit/urls.py and add the route for the form and the page to list all the public ribbits.

urlpatterns = patterns('',
    # Examples:
    url(r'^$', 'ribbit_app.views.index'), # root
    url(r'^login$', 'ribbit_app.views.login_view'), # login
    url(r'^logout$', 'ribbit_app.views.logout_view'), # logout
    url(r'^ribbits$', 'ribbit_app.views.public'), # public ribbits
    url(r'^submit$', 'ribbit_app.views.submit'), # submit new ribbit
)

Let's write a view to validate and store the ribbits submitted. Open up ribbit_app/views.py and append the following code:

from django.contrib.auth.decorators import login_required

@login_required
def submit(request):
    if request.method == "POST":
        ribbit_form = RibbitForm(data=request.POST)
        next_url = request.POST.get("next_url", "/")
        if ribbit_form.is_valid():
            ribbit = ribbit_form.save(commit=False)
            ribbit.user = request.user
            ribbit.save()
            return redirect(next_url)
        else:
            return public(request, ribbit_form)
    return redirect('/')

The view uses the @login_required decorator, which executes the function only if the user is authenticated; else, the user is redirected to the path specified in LOGIN_URL constant in the settings. If the form validation is successful, we manually set the user to the one contained in the session and then save the records. After the database commit, the user is redirected to the path specified in next_url field which is a hidden form field we manually entered in the template for this purpose. The value of next_url is passed along in the views that render the Ribbit Form.

Moving on, let's write the view to list the last 10 Public Ribbits. Append the following in ribbit_app/views.py

@login_required
def public(request, ribbit_form=None):
    ribbit_form = ribbit_form or RibbitForm()
    ribbits = Ribbit.objects.reverse()[:10]
    return render(request,
                  'public.html',
                  {'ribbit_form': ribbit_form, 'next_url': '/ribbits',
                   'ribbits': ribbits, 'username': request.user.username})

In the public view, we query the database for the last 10 ribbits by slicing the queryset to the last 10 elements. The form along with the ribbits are then rendered to the template. Let's create ribbit/templates/public.html for this view

{% extends "base.html" %}

{% block content %}
    <div class="panel right">
        <h1>Create a Ribbit</h1>
        <p>
            <form action="/submit" method="post">
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type="hidden" value="{{ next_url }}" name="next_url">
            <input type="submit" value="Ribbit!">
            </form>
        </p>
    </div>
    <div class="panel left">
        <h1>Public Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class="ribbitWrapper">
            <img class="avatar" src="{{ ribbit.user.profile.gravatar_url }}">
            <span class="name">{{ ribbit.user.first_name }}</span>@{{ ribbit.user.username }}
            <span class="time">{{ ribbit.creation_date|timesince }}</span>
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

While looping over the ribbit objects, the template uses the timesince template tag to automatically determine the time difference between the ribbit creation date and current time, and prints it in a Twitter-like way.

Ensuring that the development server is running, create a new ribbit and have a look at the Public Ribbits page to ensure everything is fine.

First Ribbit

Let's commit the changes before proceeding:

git add .
git commit -m 'Implemented Ribbit Submission and Public Ribbits Views'

Step 7 - User Profiles and Following Users

A twitter clone, User Profiles and Following Users go hand in hand. Let's write the routes for implementing this functionality. Update ribbit/urls.py with the following code:

urlpatterns = patterns('',
    # Examples:
    url(r'^$', 'ribbit_app.views.index'), # root
    url(r'^login$', 'ribbit_app.views.login_view'), # login
    url(r'^logout$', 'ribbit_app.views.logout_view'), # logout
    url(r'^ribbits$', 'ribbit_app.views.public'), # public ribbits
    url(r'^submit$', 'ribbit_app.views.submit'), # submit new ribbit
    url(r'^users/$', 'ribbit_app.views.users'),
    url(r'^users/(?P<username>\w{0,30})/$', 'ribbit_app.views.users'),
    url(r'^follow$', 'ribbit_app.views.follow'),
)

An interesting route above is the one to direct to a specific user's profile. <?P<username> captures the username queried via GET, and \w{0,30} asserts that the maximum length for a username is 30 characters:

Next, we'll proceed with writing the view to render User Profiles. Append the following in `ribbit_app/views.py'.

from django.db.models import Count
from django.http import Http404


def get_latest(user):
    try:
        return user.ribbit_set.order_by('-id')[0]
    except IndexError:
        return ""


@login_required
def users(request, username="", ribbit_form=None):
    if username:
        # Show a profile
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise Http404
        ribbits = Ribbit.objects.filter(user=user.id)
        if username == request.user.username or request.user.profile.follows.filter(user__username=username):
            # Self Profile or buddies' profile
            return render(request, 'user.html', {'user': user, 'ribbits': ribbits, })
        return render(request, 'user.html', {'user': user, 'ribbits': ribbits, 'follow': True, })
    users = User.objects.all().annotate(ribbit_count=Count('ribbit'))
    ribbits = map(get_latest, users)
    obj = zip(users, ribbits)
    ribbit_form = ribbit_form or RibbitForm()
    return render(request,
                  'profiles.html',
                  {'obj': obj, 'next_url': '/users/',
                   'ribbit_form': ribbit_form,
                   'username': request.user.username, })

This view is perhaps the most interesting of all that we've covered so far. We start by ensuring that only logged in users are able to view profiles. In the routes we defined in ribbit/urls.py, we wrote one to capture the username. This captured parameter is automatically called along with the request object in the users view. We come across two options for this view:

  • A username is passed to the url to render a specific user's profile
  • No username is passed which implies the user wants to view all profiles

We begin by checking if a username is passed and is not empty. Then, we try to fetch a User object for the corresponding username. Note the use of a try catch block in the code above. We simply raise a Http404 exception provided by Django to redirect to the default 404 template. Next, we need to check if the profile requested is of the logged in user or one of his buddies. If so, we don't need to render a follow link in the profile since the relation is already established. Otherwise, we pass along the follow parameter in the view to print the Follow link.

Moving on to the second point, if no username is given in the url, we fetch a list of all the users and use the annotate() function to add a ribbit_count attribute to all objects, which stores the number of Ribbits made by each user in the queryset. This allows us to use something along the lines of <user_object>.ribbit_count to fetch the Ribbit Count of the user.

Getting the latest Ribbit by each user is a bit tricky. We use Python's built in map() function for this and call get_latest() to all the elements of users queryset. The get_latest() function is defined in the code above and makes use of a backward relation on the User<-->Ribbit relation. For instance user.ribbit_set.all() would return all the ribbits by the user. We order the ribbits by id in descending order and slice the first element. The code is enclosed in a try catch block to catch the exception if no ribbits are created by the user. We're then making use of Python's zip() function to link up each element of both iterators (users and ribbits) so that we have a tuple with User Object and Latest Ribbit pair. We then pass along this zipped object along with the forms to the template. Let's write our last view that will accept the request to follow a user

from django.core.exceptions import ObjectDoesNotExist

@login_required
def follow(request):
    if request.method == "POST":
        follow_id = request.POST.get('follow', False)
        if follow_id:
            try:
                user = User.objects.get(id=follow_id)
                request.user.profile.follows.add(user.profile)
            except ObjectDoesNotExist:
                return redirect('/users/')
    return redirect('/users/')

In the view above, we get the value of the follow parameter, passed by POST. If the id is set we check if a user exists and add the relation, else, we catch an ObjectDoesNotExist exception and redirect the user to the page that lists all User Profiles.

We're done with the views here. Let's write the remaining two templates required to render the user profiles. Open your text editor and edit ribbit/templates/profiles.html.

{% extends "base.html" %}

{% block content %}
    <div class="panel right">
        <h1>Create a Ribbit</h1>
        <p>
            <form action="/submit" method="post">
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type="hidden" value="{{ next_url }}" name="next_url">
            <input type="submit" value="Ribbit!">
            </form>
        </p>
    </div>
    <div class="panel left">
        <h1>Public Profiles</h1>
        {% for user, ribbit in obj %}
        <div class="ribbitWrapper">
            <a href="/users/{{ user.username }}">
                <img class="avatar" src="{{ user.profile.gravatar_url }}">
                <span class="name">{{ user.first_name }}</span>
            </a>
            @{{ user.username }}
            <p>
                {{ user.ribbit_count}} Ribbits
                <span class="spacing">{{ user.profile.followed_by.count }} Followers</span>
                <span class="spacing">{{ user.profile.follows.count }} Following</span>
            </p>
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

To list the user along with his last ribbit, we use a common python construct to iterate over a list of tuples: for user, ribbit in obj. Inside the loop we print the information about the user along with his ribbit stats. Notice the use of a backward relation in {{ user.profile.followed_by.count }}. followed_by is the related name we set up while defining the ManyToManyField attribute for the UserProfile Model.

Finally, let's write the template to list a specific user's profile. Write the following code in ribbit/templates/user.html.

{% extends "base.html" %}
{% block login %}
    {% with user.username as username %}
        {{ block.super }}
    {% endwith %}
{% endblock %}

{% block content %}
    <div class="panel left">
        <h1>{{ user.first_name }}'s Profile</h1>
        <div class="ribbitWrapper">
            <a href="/users/{{ user.username }}">
                <img class="avatar" src="{{ user.profile.gravatar_url }}">
                <span class="name">{{ user.first_name }}</span>
            </a>
                @{{ user.username }}
            <p>
                {{ ribbits.count }} Ribbits
                <span class="spacing">{{ user.profile.follows.all.count }} Following</span>
                <span class="spacing">{{ user.profile.followed_by.all.count }} Followers</span>
            </p>
            {% if follow %}
            <form action="/follow" method="post">
                {% csrf_token %}
                <input type="hidden" name="follow" value="{{ user.id }}">
                <input type="submit" value="Follow">
            </form>
            {% endif %}
        </div>
    </div>

    <div class="panel left">
        <h1>{{ user.first_name }}'s Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class="ribbitWrapper">
            <a href="/users/{{ user.username }}">
                <img class="avatar" src="{{ user.profile.gravatar_url }}">
                <span class="name">{{ ribbit.user.first_name }}</span>
            </a>
            @{{ ribbit.user.username }}
            <span class="time">{{ ribbit.creation_date|timesince }}</span>
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

As in the buddies.html template, we pass the username of the logged in template to the parent template using the {% with %} construct. Next, we display the user's profile and display the follow link only if the follow variable is set to True. After that, we list all the ribbits created by the user by iterating over the ribbits variable.

We're finally done with all the views and templates. Browse around all the links and try following dummy users and explore the Twitter-clone you've just created.

Let's commit our changes:

git add .
git commit -m 'Implemented User Profiles and Following Relation on frontend'

Step 8 - Deployment to Heroku

I prefer to keep my development and production branches separate; git makes branching a breeze. Fire up the terminal and execute the following:

git branch -b production

This will create the production branch and switch to it immediately. Let's update our .gitignore file and add the database to it. It's contents should be

*.pyc
ribbit/database.db

Let's install some packages to our virtualenv that are required for deployment.

pip install psycopg2 dj-database-url gunicorn

We'll also create a file with all dependencies of the project so that they're picked up by Heroku.

pip freeze > requirements.txt

Next, let's edit ribbit/settings.py. The database settings should be changed like.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'ribbit',                      # Or path to database file if using sqlite3.
        'USER': 'username',                      # Not used with sqlite3.
        'PASSWORD': 'password',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

At the end of the file, append the following so that Heroku can figure out the database settings.

import dj_database_url

DATABASES['default'] = dj_database_url.config()

Let's create a Procfile to start the process on Heroku's server. Place the following in a file, named Procfile.

web: gunicorn ribbit.wsgi

Also, if your application isn't large scale, you can skip using storage services by appending the route for static files in ribbit/urls.py

urlpatterns += patterns('django.contrib.staticfiles.views',
        url(r'^static/(?P<path>.*)$', 'serve'),
    )

We're all set! Let's commit the changes:

git commit -a -m 'Configured for Production'

Finally, we'll create a Heroku app and push our repo to the remote repository:

heroku create
git push heroku production:master

Once the files are transferred, run syncdb and apply the migrations to the database.

heroku run python manage.py syncdb
heroku run python manage.py migrate ribbit_app

Finally, open the deployed app with:

heroku open

Conclusion

We're finally done with Ribbit in Django! This tutorial was quite long, indeed, and the application still has much more potential that can be implemented. I hope that, if you are new to Django, this article has encouraged you to dig more deeply.

Feel free to play along with my deployment at Heroku. If you have any questions, I'd be glad to answer and all questions.

Advertisement