Django 1.0 Web Site Development

Django 1.0 Web Site Development

Django is a high-level Python web application framework designed to support the development of dynamic web sites, web applications, and web services. It is designed to promote rapid development and clean, pragmatic design. Therefore, it lets you build high-performing and elegant web applications quickly.

also read:

In this book you will learn about employing this MVC web framework, which is written in Python—a powerful and popular programming language. The book emphasizes utilizing Django and Python to create a Web 2.0 bookmark-sharing application, with many common features found in the Web 2.0 sites these days. The book follows a tutorial style to introduce concepts and explain solutions to problems. It is not meant to be a reference manual for Python or Django. Django will be explained as we build features throughout the chapters, until we realize our goal of having a working Web 2.0
application for storing and sharing bookmarks.

I sincerely hope that you will enjoy reading the book as much as I enjoyed writing it. And I am sure that by its end, you will appreciate the benefits of using Python and Django for your next project. Both Python and Django are powerful and simple, and provide a robust environment for rapid development of your dynamic web applications.

What This Book Covers

Chapter 1 gives you an introduction to MVC web development frameworks, and explains why Python and Django are the best tools to achieve the aim of this book.
Chapter 2 provides a step-by-step guide to installing Python, Django, and an appropriate database system so that you can create an empty project and set up the development server.
Chapter 3 creates the main page so that we have an initial view and a URL. You will learn how to create templates for both the main page and the user page. You will also write a basic set of data models to store your application’s data.
Chapter 4 is where the application really starts to take shape, as user management is implemented. Learn how to log users in and out, create a registration form, and allow users to manage their own accounts by changing email or password details.
Chapter 5 explores how to manage your growing bank of content. Create tags, tag clouds, and a bookmark submission form, all of which interact with your database. Security features also come into play as you learn how to restrict access to certain pages and protect them against malicious input.
Chapter 6 enables you to enhance your application with AJAX and jQuery, since users can now edit entries in place and do live searching. Data entry is also made easier with the introduction of auto-completion.
Chapter 7 shows you how to enable users to vote and comment on their bookmark entries. You will also build a popular bookmarks page.
Chapter 8 focuses on the administration interface. You will learn how to create and customize the interface, which allows you to manage content and set permissions for users and groups.
Chapter 9 will give your application a much more professional feel through the implementation of RSS feeds and pagination.
Chapter 10 tackles social networks providing the “social” element of your application. Users will be able to build a friend network, browse the bookmarks of their friends, and invite their friends to join the web site.
Chapter 11 covers extending and deploying your application. You will also learn about advanced features, including offering the site in multiple languages, managing the site during high traffic, and configuring the site for a production environment.
Chapter 12 takes a brief look at the additional Django features that have not been covered elsewhere in the book. You will gain the knowledge required to further develop your application and build on the basic skills that you have learned throughout the book.

Building User Networks

Our application is about “social” bookmarking. Running a social web application means having a community of users who have common interests, and who use the application to share their interests and findings with each other. We will want to enhance the social experience of our users. In this chapter we will introduce two features that will enable us to do this. We will let our users maintain lists of friends, see what their friends are bookmarking, and invite new friends to try out our application. We will also utilize a Django API to make our application more user-friendly and responsive by displaying feedback messages to users. So let’s get started!

In this chapter you will learn how to:

  • Build a friend network feature
  • Let users browse bookmarks of friends
  • Enable users to invite friends to your web site
  • Improve the interface with status messages

Building friend networks

An important aspect of socializing in our application is letting users to maintain their friend lists and browse through the bookmarks of their friends. So, in this section we will build a data model to maintain user relationships, and then program two views to enable users to manage their friends and browse their friends’ bookmarks.

Creating the friendship data model

Let’s start with the data model for the friends feature. When a user adds another user as a friend, we need to maintain both users in one object. Therefore, the Friendship data model will consist of two references to the User objects involved in the friendship. Create this model by opening the bookmarks/models.py file and inserting the following code in it:

 
	class Friendship(models.Model):
		from_friend = models.ForeignKey(
			User, related_name='friend_set'
		)
		to_friend = models.ForeignKey(
			User, related_name='to_friend_set'
		)
		def __unicode__(self):
			return u'%s, %s' % (
			self.from_friend.username,
			self.to_friend.username
		)
		class Meta:
			unique_together = (('to_friend', 'from_friend'), )

The Friendship data model starts with defining two fields that are User objects: from_friend and to_friend. from_friend is the user who added to_friend as a friend. As you can see, we passed a keyword argument called related_name to both the fields. The reason for this is that both fields are foreign keys that refer back to the User data model. This will cause Django to try to create two attributes called friendship_set in each User object, which would result in a name confl ict.
To avoid this problem, we provide a specific name for each attribute. Consequently,each User object will contain two new attributes: user.friend_set, which contains the friends of this user and user.to_friend_set, which contains the users who added this user as a friend. Throughout this chapter, we will only use the friend_set attribute, but the other one is there in case you need it.

Next, we defined a __unicode__ method in our data model. As you already know, this method is useful for debugging.
Finally, we defined a class called Meta. This class may be used to specify various options related to the data model. Some of the commonly used options are:

  • db_table: This is the name of the table to use for the model. This is useful when the table name generated by Django is a reserved keyword in SQL, or when you want to avoid confl icts if a table with the same name already exists in the database.
  • ordering: This is a list of field names. It declares how objects are ordered when retrieving a list of objects. A column name may be preceded by a minus sign to change the sorting order from ascending to descending.
  • permissions: This lets you declare custom permissions for the data model in addition to add, change, and delete permissions that we learned about in Chapter 7. Permissions should be a list of two-tuples, where each two-tuple should consist of a permission codename and a human-readable name for that permission. For example, you can define a new permission for listing friend bookmarks by using the following Meta class:
     
    	class Meta:
    		permissions = (
    			('can_list_friend_bookmarks',
    			'Can list friend bookmarks'),
    		)
    
  • unique_together: A list of field names that must be unique together.

We used the unique_together option here to ensure that a Friendship object is added only once for a particular relationship. There cannot be two Friendship objects with equal to_friend and from_friend fields. This is equivalent to the following SQL declaration:

 
	UNIQUE ("from_friend", "to_friend")

If you check the SQL generated by Django for this model, you will find something similar to this in the code.
After entering the data model code into the bookmarks/models.py file, run the following command to create its corresponding table in the database:

$ python manage.py syncdb

Now let’s experiment with the new model and see how to store and retrieve relations of friendship. Run the interactive console using the following command:

$ python manage.py shell

Next, retrieve some User objects and build relationships between them (but make sure that you have at least three users in the database):

>>> from bookmarks.models import *

>>> from django.contrib.auth.models import User

>>> user1 = User.objects.get(id=1)

>>> user2 = User.objects.get(id=2)

>>> user3 = User.objects.get(id=3)

>>> friendship1 = Friendship(from_friend=user1, to_friend=user2)

>>> friendship1.save()

>>> friendship2 = Friendship(from_friend=user1, to_friend=user3)

>>> friendship2.save()

Now, user2 and user3 are both friends of user1. To retrieve the list of Friendship objects associated with user1, use:

>>> user1.friend_set.all()

[<Friendship: user1, user2>, <Friendship: user1, user3>]

(The actual usernames in output were replaced with user1, user2, and user3 for clarity.)
As you may have already noticed, the attribute is named friend_set because we called it so using the related_name option when we created the Friendship model.

Next, let’s see one way to retrieve the User objects of user1’s friends:

 
>>> [friendship.to_friend for friendship in
	user1.friend_set.all()]
	[<User: user2>, <User: user3>]

The last line of code uses a Python feature called “list” comprehension to build the list of User objects. This feature allows us to build a list by iterating through another list. Here, we built the User list by iterating over a list of Friendship objects. If this syntax looks unfamiliar, please refer to the List Comprehension section in the Python tutorial.

Notice that user1 has user2 as a friend, but the opposite is not true.

>>> user2.friend_set.all()
	[]

In other words, the Friendship model works only in one direction. To add user1 as a friend of user2, we need to construct another Friendship object.

>>> friendship3 = Friendship(from_friend=user2, to_friend=user1)

>>> friendship3.save()

>>> user2.friend_set.all()

[<Friendship: user2, user1>]

By reversing the arguments passed to the Friendship constructor, we built a relationship in the other way. Now user1 is a friend of user2 and vice-versa.

Experiment more with the model to make sure that you understand how it works. Once you feel comfortable with it, move to the next section, where we will write views to utilize the data model. Things will only get more exciting from now on!

Writing views to manage friends

Now that we are able to store and retrieve user relationships, it’s time to create views for these features. In this section we will build two views: one for adding a friend, and another for listing friends and their bookmarks.

We will use the following URL scheme for friend-related views:

  • If the view is for managing friends (adding a friend, removing a friend, and so on), its URL should start with /friend/. For example, the URL of the view that adds a friend will be /friend/add/.
  • If the view is for viewing friends and their bookmarks, its URL should start with /friends/. For example, /friends/username/ will be used to display the friends of username.

This convention is necessary to avoid confl icts. If we use the prefix /friend/ for all views, what happens if a user registers the username add? The Friends page for this user will be /friend/add/, just like the view to add a friend. The first URL mapping in the URL table will always be used, and the second will become inaccessible, which is obviously a bug.

Now that we have a URL scheme in mind, let’s start with writing the friends list view.

The friends list view

This view will receive a username in the URL, and will display this user’s friends and their bookmarks. To create the view, open the bookmarks/views.py file and add the following code to it:

 
	def friends_page(request, username):
		user = get_object_or_404(User, username=username)
		friends = [friendship.to_friend
					for friendship in user.friend_set.all()]
		friend_bookmarks = Bookmark.objects.filter(
			user__in=friends
		).order_by('-id')
		variables = RequestContext(request, {
			'username': username,
			'friends': friends,
			'bookmarks': friend_bookmarks[:10],
			'show_tags': True,
			'show_user': True
		})
	return render_to_response('friends_page.html', variables)

This view is pretty simple. It receives a username and operates upon it as follows:

  • The User object that corresponds to the username is retrieved using the shortcut method get_object_or_404.
  • The friends of this user are retrieved using the list comprehension syntax mentioned in the previous section.
  • After that, the bookmarks of the user’s friends are retrieved using the filter method. The user__in keyword argument is passed to filter in order to retrieve all the bookmarks of the user who exists in the friends list. order_by is chained to filter for the purpose of sorting bookmarks
    by id in a descending order.
  • Finally, the variables are put into a RequestContext object and are sent to a template named friends_page.html. We used the index syntax with friend_bookmarks to get only the latest ten bookmarks.

Let’s write the view’s template next. Create a file called friends_page.html in the templates folder with the following code in it:

 
	{% extends "base.html" %}
	
	{% block title %}Friends for {{ username }}{% endblock %}
	{% block head %}Friends for {{ username }}{% endblock %}
	
	{% block content %}
		<h2>Friend List</h2>
		{% if friends %}
			<ul class="friends">
				{% for friend in friends %}
					<li><a href="/user/{{ friend.username }}/">
						{{ friend.username }}</a></li>
				{% endfor %}
			</ul>
		{% else %}
			<p>No friends found.</p>
		{% endif %}
		<h2>Latest Friend Bookmarks</h2>
		{% include "bookmark_list.html" %}
	{% endblock %}

The template should be self-explanatory; there is nothing new in it. We iterate over the friends list and create a link for each friend. Next, we create a list of friend bookmarks by including the bookmark_list.html template.
Finally, we will add a URL entry for the view. Open the urls.py file and insert the following mapping into the urlpatterns list:

 
	urlpatterns = patterns('',
		[...]
		# Friends
		(r'^friends/(\w+)/$', friends_page),
	)

This URL entry captures the username portion in the URL using a regular expression, exactly the way we did in the user_page view.

Although we haven’t created a view for adding friends yet, you can still see this view by manually adding some friends to your account (if you haven’t done so already). Use the interactive console to make sure that your account has friends, and then start the development server and point your browser to http://127.0.0.1:8000/ friends/your_username/ (replacing your_username with your actual username). The resulting page will look something similar to the following screenshot:

So, we now have a functional Friends page. It displays a list of friends along with their latest bookmarks. In the next section, we are going to create a view that allows users to add friends to this page.

Creating the add friend view

So far, we have been adding friends using the interactive console. The next step in building the friends feature is offering a way to add friends from within our web application.
The friend_add view works like this: It receives the username of the friend in GET, and creates a Friendship object accordingly. Open the bookmarks/views.py file and add the following view:

 
	@login_required
	def friend_add(request):
		if 'username' in request.GET:
			friend = get_object_or_404(
				User, username=request.GET['username']
			)
			friendship = Friendship(
				from_friend=request.user,
				to_friend=friend
			)
			friendship.save()
			return HttpResponseRedirect(
				'/friends/%s/' % request.user.username
			)
		else:
			raise Http404

Let’s go through the view line by line:

  • We apply the login_required decorator to the view. Anonymous users must log in before they can add friends.
  • We check whether a GET variable called username exists. If it does, we continue with creating a relationship. Otherwise, we raise a 404 page not found error.
  • We retrieve the user to be added as a friend using get_object_or_404.
  • We create a Friendship object with the currently logged-in user as the from_friend argument, and the requested username as the to_friend argument.
  • Finally, we redirect the user to their Friends page.

After creating the view, we will add a URL entry for it. Open the urls.py file and add the highlighted line to it:

 
	urlpatterns = patterns('',
		[...]
		# Friends
		(r'^friends/(\w+)/$', friends_page),
		<b>(r'^friend/add/$', friend_add),</b>
	)

The “add friend” view is now functional. However, there are no links to use it anywhere in our application, so let’s add these links. We will modify the user_page view to display a link for adding the current user as a friend, and a link for viewing the user’s friends. Of course, we will need to handle special cases; you don’t want an “add friend” link when you are viewing your own page, or when you are viewing the page of one of your friends.

Adding these links will be done in the user_page.html template. But before doing so, we need to pass a Boolean fl ag from the user_page view to the template indicating whether the owner of the user page is a friend of the currently logged-in user or not. So open the bookmarks/views.py file and add the highlighted lines into the user_page view:

 
	def user_page(request, username):
		user = get_object_or_404(User, username=username)
		query_set = user.bookmark_set.order_by('-id')
		paginator = Paginator(query_set, ITEMS_PER_PAGE)
		<b>if request.user.is_authenticated():
			is_friend = Friendship.objects.filter(
				from_friend=request.user,
				to_friend=user
			)
		else:
			is_friend = False</b>
		try:
			page_number = int(request.GET['page'])
		except (KeyError, ValueError):
			page_number = 1
		try:
			page = paginator.page(page_number)
		except InvalidPage:
			raise Http404
		bookmarks = page.object_list
		variables = RequestContext(request, {
			'username': username,
			'bookmarks': bookmarks,
			'show_tags': True,
			'show_edit': username == request.user.username,
			'show_paginator': paginator.num_pages > 1,
			'has_prev': page.has_previous(),
			'has_next': page.has_next(),
			'page': page_number,
			'pages': paginator.num_pages,
			'next_page': page_number + 1,
			'prev_page': page_number - 1,
			<b>'is_friend': is_friend,</b>
		})
		return render_to_response('user_page.html', variables)

Next, open the templates/user_page.html file and add the following highlighted lines to it:

 
	[...]
	{% block content %}
		<b>{% ifequal user.username username %}
			<a href="/friends/{{ username }}/">view your friends</a>
		{% else %}
			{% if is_friend %}
				<a href="/friends/{{ user.username }}/">
				{{ username }} is a friend of yours</a>
			{% else %}
				<a href="/friend/add/?username={{ username }}">
				add {{ username }} to your friends</a>
			{% endif %}
			- <a href="/friends/{{ username }}/">
			view {{username }}'s friends</a>
		{% endifequal %}</b>
		{% include "bookmark_list.html" %}
	{% endblock %}

Let’s go through each conditional branch in the highlighted code:

  1. We check whether the user is viewing his or her page. This is done using a template tag called ifequal, which takes two variables to compare for equality. If the user is indeed viewing his or her page, we simply display a link to it.
  2. We check whether the user is viewing the page of one of their friends. If this is the case, we display a link to the current user’s Friends page instead of an “add friend” link. Otherwise, we construct an “add friend” link by passing the username as a GET variable.
  3. We display a link to the Friends page of the user page’s owner being viewed.

And that’s it. Browse some user pages to see how the links at the top change, depending on your relationship with the owner of the user page. Try to add new friends to see your Friends page grow.

Implementing the friends feature wasn’t that hard, was it? You wrote one data model and two views, and the feature became functional. Interestingly, the more Django experience you gain, the more easy and fast its implementation becomes.

Our users are now able to add each other as friends and monitor their friends’ bookmarks, but what about friends who are not members of our site? In the next section we will implement an ” Invite a friend ” feature that will allow users to invite their friends to join our site via email.

Inviting friends via email

Enabling our users to invite their friends carries many benefits. People are more likely to join our site if their friends are already using it. After they join, they will also invite their friends, and so on, which means an increasing number of users for our application. Therefore, it is a good idea to offer an ” Invite a friend ” feature. This is actually a common functionality found in many Web 2.0 applications.

Building this feature requires the following components:

  • An Invitation data model to store invitations in the database
  • A form in which users can type the emails of their friends and send invitations
  • An invitation email with an activation link
  • A mechanism for processing activation links sent in email

Throughout this section, we will implement each component. But because this section involves sending emails, we first need to configure Django to send emails by adding some options to the settings.py file. So, open the settings.py file and add the following lines to it:

 
	SITE_HOST = '127.0.0.1:8000'
	DEFAULT_FROM_EMAIL = \
		'Django Bookmarks <django.bookmarks@example.com>'
	EMAIL_HOST = 'mail.yourisp.com'
	EMAIL_PORT = ''
	EMAIL_HOST_USER = 'username'
	EMAIL_HOST_PASSWORD = 'password'

Let’s see what each variable does.

  • SITE_HOST: This is the host name of your server. Leave it as 127.0.0.1:8000 for now. When we deploy our server in the next chapter, we will change this.
  • DEFAULT_FROM_EMAIL: This is the email address that appears in the From field of emails sent by Django.
  • EMAIL_HOST: This is the host name of your email server. If you are using a development machine that doesn’t run a mail server (which is most likely the case), then you need to put your ISP’s outgoing email server here.
    Contact your ISP for more information.
  • EMAIL_PORT: This refers to the port number of the outgoing email server. If you leave it empty, the default value (25) will be used. You also need to obtain this from your ISP.
  • EMAIL_HOST_USER and EMAIL_HOST_PASSWORD : This refers to the username and password for the outgoing email server. For the host username, input your username and your email server (as shown in the previous code). Leave the fields empty if your ISP does not require them.

To verify that your settings are correct, launch the interactive shell and enter
the following:

 
	>>> from django.core.mail import send_mail
	>>> send_mail('Subject', 'Body of the message.',
			'from@example.com',
			['your_email@example.com'])

Replace your_email@example.com with your actual email address. If the above callto send_mail does not raise an exception and you receive the email, then all is set. Otherwise, you need to verify your settings with your ISP and try again.

Once the settings are correct, sending an email in Django is a piece of cake! We will use send_mail to send the invitation email. But first, let’s create a data model for storing invitations.

The invitation data model

An invitation consists of the following information:

  • Recipient name
  • Recipient email
  • The User object of the sender

We also need to store an activation code for the invitation. This code will be sent in the invitation email. The code will serve two purposes:

  • Before accepting the invitation, we can use the code to verify that the invitation actually exists in the database
  • After accepting the invitation, we can use the code to retrieve the invitation information from the database and create friendship relationships between the sender and the recipient

With this in mind, let’s create the Invitation data model. Open the bookmarks/models.py file and append the following code to it:

 
	class Invitation(models.Model):
		name = models.CharField(max_length=50)
		email = models.EmailField()
		code = models.CharField(max_length=20)
		sender = models.ForeignKey(User)

		def __unicode__(self):
			return u'%s, %s' % (self.sender.username, self.email)

There shouldn’t be anything new or difficult to understand in this model. We simply defined fields for the recipient name, recipient email, activation code, and the sender of the invitation. We also created a __unicode__ method for debugging, and enabled the model in the administration interface. Do not forget to run manage.py syncdb to create the new model’s table in the database.
Next, we will add a method for sending the invitation email. The method will use classes and methods from several packages. So, put the following import statements at the beginning of the bookmarks/models.py file, and append the send method to the Invitation data model in the same file:

 
	from django.core.mail import send_mail
	from django.template.loader import get_template
	from django.template import Context
	from django.conf import settings
	class Invitation(models.Model):
		[...]
		def send(self):
			subject = u'Invitation to join Django Bookmarks'
			link = 'http://%s/friend/accept/%s/' % (
				settings.SITE_HOST,
				self.code
			)
			template = get_template('invitation_email.txt')
			context = Context({
				'name': self.name,
				'link': link,
				'sender': self.sender.username,
			})
			message = template.render(context)
			send_mail(
				subject, message,
				settings.DEFAULT_FROM_EMAIL, [self.email]
			)

The method works by loading a template called invitation_email.txt and passing the following variables to it: the name of the recipient, the activation link, and the sender username. The template is then used to render the body of the invitation email. After that, we used send_mail to send the email as we did during the interactive session in the previous section.
There are several observations to make here:

  • The format of the activation link is http://SITE_HOST/friend/accept/CODE/. We will write a view to handle such URLs later in this section.
  • This is the first time we use a template to render something other than a web page. As you can see, the template system is quite fl exible and allows us to build emails as well as web pages, or any other text.
  • We used the get_template and render methods to build the message body as opposed to the usual render_to_response call. If you remember, this is how we rendered templates early in the book. We are doing this here because we are not rendering a web page.
  • The last parameter of send_mail is a list of recipient emails. Here we are passing only one email address. But if you want to send the same email to multiple users, you can pass all of the email addresses in one list to send_mail.

Since the send method loads a template called invitation_email.txt, create a file with this name in the templates folder and insert the following content into it:

 
	Hi {{ name }},
	{{ sender }} invited you to join Django Bookmarks,
		a website where you can post and share your bookmarks with friends!
	To accept the invitation, please click the link below:
	{{ link }}
		-- Django Bookmarks Team

Once we write the send method, our Invitation data model is ready. Next, we will create a form that allows users to send invitations.

The Invite A Friend form and view

The next step in implementing the ” Invite a friend ” feature is providing users with a form to enter their friends’ details and invite them. We will create this form now. The task will be quite similar to compiling the forms that we have built throughout
this book.

First, let’s create a Form class that represents our form. Open the bookmarks/forms.py file and add this class to it:

 
	class FriendInviteForm(forms.Form):
		name = forms.CharField(label=u'Friend\'s Name')
		email = forms.EmailField(label=u'Friend\'s Email')

This form is simple. We only ask the user to enter the friend’s name and email. Let’s create a view to display and handle this form. Open the bookmarks/views.py file and append the following code to it:

 
	@login_required
	def friend_invite(request):
		if request.method == 'POST':
			form = FriendInviteForm(request.POST)
			if form.is_valid():
				invitation = Invitation(
					name=form.cleaned_data['name'],
					email=form.cleaned_data['email'],
					code=User.objects.make_random_password(20),
					sender=request.user
				)
				invitation.save()
				invitation.send()
				return HttpResponseRedirect('/friend/invite/')
			else:
				form = FriendInviteForm()

			variables = RequestContext(request, {
				'form': form
			})
			return render_to_response('friend_invite.html', variables)

Again, the view is similar to the other form processing views in our application. If a valid form is submitted, it creates an Invitation object and sends it. We used a method called make_random_password in User.objects to generate an activation code for the invitation. This method can be used to create random passwords.It takes the length of the password as a parameter and returns a random alphanumeric password.

After this, we will add a template for the view. Create a file called friend_invite.html in the templates folder with the following code:

 
	{% extends "base.html" %}
	{% block title %}Invite A Friend{% endblock %}
	{% block head %}Invite A Friend{% endblock %}
	{% block content %}
	Enter your friend name and email below,
	and click "send invite" to invite your friend to join the site:
	<form method="post" action=".">
		{{ form.as_p }}
		<input type="submit" value="send invite" />
	</form>
	{% endblock %}

As you can see, the template displays a help message and the form below it. Finally, we will add a URL entry for this view, so open the urls.py file and add the highlighted line to it:

 
	urlpatterns = patterns('',
		[...]
		# Friends
		(r'^friends/(\w+)/$', friends_page),
		(r'^friend/add/$', friend_add),
		(r'^friend/invite/$', friend_invite),
	)

The Invite A Friend view is now ready. Open http://127.0.0.1:8000/friend/ invite/ in your browser, and you will see a form similar to the following screenshot:

Try to send an invitation to your email address. If everything is working correctly, you will receive an invitation with an activation link similar to the following screenshot:

We are half-way through implementing the ” Invite a friend ” feature. At the moment, clicking the activation link produces a 404 page not found error. So we will now write a view to handle it.

Handling activation links

We have made good progress; users are now able to send invitations to their friends via email. The next step is building a mechanism for handling activation links in invitations. Here is an outline of what we are going to do:

  • We will build a view that handles activation links. This view verifies that the invitation code actually exists in the database, stores the invitation ID in the user’s session, and redirects to the registration page.
  • When the user registers an account, we check to see if they have an invitation ID in their session. If this is the case, we retrieve the Invitation object for this ID, and build friendship relationships between the user and the sender of the invitation.

Let’s start by writing a URL entry for the view. Open the urls.py file and add the highlighted line from the following code to it:

 
	urlpatterns = patterns('',
		[...]
		# Friends
		(r'^friends/(\w+)/$', friends_page),
		(r'^friend/add/$', friend_add),
		(r'^friend/invite/$', friend_invite),
		(r'^friend/accept/(\w+)/$', friend_accept),
	)

As you can see, the view follows the URL format sent in invitation emails. The activation code is captured from the URL using a regular expression, and then it will be passed to the view as a parameter. Next, we will write the view. Open the bookmarks/views.py file and create the following view in it:

 
	def friend_accept(request, code):
		invitation = get_object_or_404(Invitation, code__exact=code)
		request.session['invitation'] = invitation.id
		return HttpResponseRedirect('/register/')

The view is short and concise. It tries to retrieve the Invitation object that corresponds to the requested code (generating a 404 error if the code does not exist). After that, it stores the ID of the object in the user’s session. Lastly, it redirects to the registration page.

This is the first time that we use sessions in our application. Django provides an easy-to-use session framework to store and retrieve data for each visitor. Data is stored on the server and can be accessed in views by using a dictionary-like object available at request.session.

The session framework is enabled by default in the settings.py file. You can verify this by looking for ‘django.contrib.sessions’ in the INSTALLED_APPS variable.

You can use request.session to do the following:

  • Store a key-value pair: request.session[key] = value
  • Retrieve a value by providing its key: value = request.session[key]. This raises KeyError if the key does not exist.
  • Check whether the session contains a particular key:
    if key in request.session:

Each user has its own session dictionary. Sessions are useful for maintaining data across requests, especially for anonymous users. Unlike cookies, sessions are stored on the server side so that they cannot be tampered with.

All of these properties make sessions ideal for passing the invitation ID to the register_page view. After this quick overview of the session framework, let’s get back to our current task. Now that the friend_accept view is ready, we will modify the register_page view a little to make use of the invitation ID in the user’s session. If the ID exists, we will create friendship relations between the user and the sender, and delete the invitation to prevent reusing it. Open the bookmarks/views.py file and add the highlighted lines from the following code:

 
	def register_page(request):
		if request.method == 'POST':
			form = RegistrationForm(request.POST)
			if form.is_valid():
				user = User.objects.create_user(
					username=form.cleaned_data['username'],
					password=form.cleaned_data['password1'],
					email=form.cleaned_data['email']
				)
				if 'invitation' in request.session:
					# Retrieve the invitation object.
					invitation = Invitation.objects.get(
						id=request.session['invitation']
					)
					# Create friendship from user to sender.
					friendship = Friendship(
						from_friend=user,
						to_friend=invitation.sender
					)
					friendship.save()
					# Create friendship from sender to user.
					friendship = Friendship (
						from_friend=invitation.sender,
						to_friend=user
					)
					friendship.save()
					# Delete the invitation from the database and session.
					invitation.delete()
					del request.session['invitation']</b>
				return HttpResponseRedirect('/register/success/')
			else:
				form = RegistrationForm()
			variables = RequestContext(request, {
				'form': form
			})
			return render_to_response('registration/register.html', variables)

The highlighted code should be easy to understand. It starts by checking for an invitation ID in the user’s session. If there is one, it creates the relation of friendship in both directions between the sender of the invitation and the current user. After that, it deletes the invitation and removes its ID from the session.

Feel free to create a link to the Invite A Friend page. The Friends list page is a good place to do so. Open the templates/friends_page.html file and add the highlighted line from the following code:

 
	{% extends "base.html" %}
	{% block title %}Friends for {{ username }}{% endblock %}
	{% block head %}Friends for {{ username }}{% endblock %}
	{% block content %}
		<h2>Friend List</h2>
		{% if friends %}
			<ul class="friends">
				{% for friend in friends %}
					<li><a href="/user/{{ friend.username }}/">
						{{ friend.username }}</a></li>
				{% endfor %}
			</ul>
		{% else %}
			<p>No friends found.</p>
		{% endif %}
		<a href="/friend/invite/">Invite a friend!</a>
		<h2>Latest Friend Bookmarks</h2>
		{% include 'bookmark_list.html' %}
	{% endblock %}

This should be all that we need to do to implement the ” Invite a friend ” feature. It was a bit long, but we were able to put various areas of our Django knowledge to good use while implementing it. You can now click on the invitation link that you received via email to see what happens—you will be redirected to the registration page. Create a new account there, log in, and notice how the new account and your original one have become friends with each other.

Improving the interface with messages

Although our implementation of user networks is working correctly, there is something missing. The interface does not tell the user whether an operation succeeded or failed. After sending an invitation, for example, the user is redirected back to the invitation form, with no feedback on whether the operation was successful or not. In this section, we are going to improve our interface by providing status messages to the user after performing certain actions.

Displaying messages to users is done using the message API, which is part of the authentication system. The API is simple. To create a message, you can use the following call:

 
	request.user.message_set.create(
		message=u'Message text goes here.'
	)

This call will create a message and store it in the database. Available messages are accessible from within templates through the variable messages. The following code iterates over messages and displays them in a list:

 
	{% if messages %}
	<ul>
		{% for message in messages %}
			<li>{{ message }}</li>
		{% endfor %}
	</ul>
	{% endif %}

This information covers all that we need to utilize the message framework in our project. Let’s start by placing the above template code in the base template of our application. Open the templates/base.html file and add the highlighted section of the following code:

 
	<body>
		<div id="nav">
			[...]
		</div>
		<h1>{% block head %}{% endblock %}</h1>
		{% if messages %}
			<ul class="messages">
				{% for message in messages %}
					<li>{{ message }}</li>
				{% endfor %}
			</ul>
		{% endif %}
		{% block content %}{% endblock %}
	</body>
	</html>

We placed the code below the heading of the page. To give messages a distinctive look, add the following CSS code to the site_media/style.css file:

 
	ul.messages {
		border: 1px dashed #000;
		margin: 1em 4em;
		padding: 1em;
	}

And that’s about it. We can now create messages and they will be displayed automatically. Let’s start with sending invitations. Open the bookmarks/views.py files and modify the friend_invite view as follows:

 
	import smtplib
	@login_required
	def friend_invite(request):
		if request.method == 'POST':
			form = FriendInviteForm(request.POST)
			if form.is_valid():
				invitation = Invitation(
					name=form.cleaned_data['name'],
					email=form.cleaned_data['email'],
					code=User.objects.make_random_password(20),
					sender=request.user
				)
				invitation.save()
				try:
					invitation.send()
					request.user.message_set.create(
						message=u'An invitation was sent to %s.' %
						invitation.email
					)
				except smtplib.SMTPException:
					request.user.message_set.create(
						message=u'An error happened when '
						u'sending the invitation.'
					)
				return HttpResponseRedirect('/friend/invite/')
			else:
				form = FriendInviteForm()
				variables = RequestContext(request, {
					'form': form
				})
			return render_to_response('friend_invite.html', variables)

The highlighted code works as follows: send_mail raises an exception if it fails, so we wrap the call to invitation.send in a try/except block. The reader is then notified accordingly.

You can try the new message system now. First, send an invitation and notice how a message appears confirming the success of the operation. Next, change the EMAIL_HOST option in the settings.py file to an invalid value and try sending an invitation again. You should see a message indicating failure this time. Our interface is more responsive now. Users know exactly what’s going on.

You can do the same for the friend_add view. Open the bookmarks/views.py file and modify the view like this:

 
	@login_required
	def friend_add(request):
		if 'username' in request.GET:
			friend = get_object_or_404(
				User, username=request.GET['username']
			)
			friendship = Friendship(
				from_friend=request.user,
				to_friend=friend
			)
			try:
				friendship.save()
				request.user.message_set.create(
					message=u'%s was added to your friend list.' %
					friend.username
				)
			except:
				request.user.message_set.create(
					message=u'%s is already a friend of yours.' %
					friend.username
				)
			return HttpResponseRedirect(
				'/friends/%s/' % request.user.username
			)
		else:
			raise Http404

The highlighted code displays a success message if the call to friendship.save was successful. If an exception is thrown by the call, it means that the unique_together condition was violated and that the requested username is already a friend of the current user. An error message that says so is displayed.

The message API is simple, yet effective. You can use it for all sorts of things, such as displaying status messages, errors, notifications, and so on. Try to utilize it in other parts of the application if you want, such as after adding or editing a bookmark.

Summary

We developed an important set of features for our project in this chapter. Friend networks are very important in helping users to socialize and share interests together. These features are common in Web 2.0 applications, and now you are able to incorporate them into any Django web site.

Here is a quick summary of the Django features covered in this chapter:

  • To manually specify a name for the related attribute in a data model, pass a keyword argument called related_name to the field that creates the relationship between models.
  • You can specify several options for data models by defining a class called Meta inside the data model. Some of the possible attributes in this class are: db_table, ordering, permissions, and unique_together.
  • To send an email in Django, use the send_mail function. It’s available from the django.core.mail package.
  • The Django session framework provides a convenient way to store and retrieve user data across requests. The request.session object provides a dictionary-like interface to interact with session data.
  • To create a message, use the following method call:
    request.user.message_set.create.
  • To display messages in a template, use the template variable messages.

also read:

In the next chapter we will learn about improving various aspects of our application— mainly, performance and localization. We will also learn how to deploy our project on a production server. The next chapter comes with a lot of useful information, so keep reading!

Comments

comments

Speak Your Mind

*