Inviting Friends via Email on Social Web Application with Django 1.0

Exclusive offer: get 50% off this eBook here
Django 1.0 Website Development

Django 1.0 Website Development — Save 50%

Build powerful web applications, quickly and cleanly, with the Django application framework

$23.99    $12.00
by Ayman Hourieh | March 2009 | Open Source Web Development

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 the previous article we built a friend network feature and let users browse bookmarks of friends.

In this article by Ayman Hourieh, we will learn how to:

  • Enable users to invite friends to your web site
  • Improve the interface with status messages

Django is an open source web framework that enables you to build clean and feature-rich web applications with minimal time and effort. Django is written in Python, a general purpose language that is well-suited for developing web applications. Social bookmarking is one such  application.

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 article, we will implement each component. But because this article 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. We will change this, when we deploy our server.
  • 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 enterthe 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 call to 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.

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 article.
  • 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 flexible 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.

Django 1.0 Website Development Build powerful web applications, quickly and cleanly, with the Django application framework
Published: March 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

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.

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:

Django 1.0 Website Development

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:

Django 1.0 Website Development

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']
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 article, 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.

Django 1.0 Website Development

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

In this article we developed an important feature for our project. We allowed our users to invite new friends to try out our application. We have also utilized a Django API to make our application more user-friendly and responsive by displaying feedback messages to users.

Django 1.0 Website Development Build powerful web applications, quickly and cleanly, with the Django application framework
Published: March 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Ayman Hourieh

Ayman Hourieh holds a bachelor degree in Computer Science. He joined the engineering team at Google in January 2008. Prior to that, he worked with web application development for more than two years. In addition, he has been contributing to several Open Source projects such as Mozilla Firefox. Ayman also worked as a teaching assistant in Computer Science courses for one year. Even after working with a variety of technologies, Python remains Ayman's favorite programming language. He found Django to be a powerful and flexible Python framework that helps developers to produce high-quality web applications in a short time.

Books From Packt

 

Django 1.0 Template Development
Django 1.0 Template Development

Drupal 6 Social Networking
Drupal 6 Social Networking

Expert Python Programming
Expert Python Programming

Drupal 6 Site Builder Solutions
Drupal 6 Site Builder Solutions

Spring 2.5 Aspect Oriented Programming
Spring 2.5 Aspect Oriented Programming

Learning jQuery 1.3
Learning jQuery 1.3

jQuery UI 1.6: The User Interface Library for jQuery
jQuery UI 1.6: The User Interface Library for jQuery

Practical Plone 3: A Beginner's Guide to Building Powerful Websites
Practical Plone 3: A Beginner's Guide to Building Powerful Websites

 

 

Your rating: None Average: 4 (4 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Z
v
r
g
N
f
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software