finally{}

all will be well finally

A simple ‘Twitter’ clone in Django

with 9 comments

It started when my boss challenged me to write a twitter/orkut for our department. Having already decided to learn web development with Django (read about it here), I jumped into action over the weekend.

As it was my first application and it was for internal use, I limited myself to the below feature list:

  • multi-user system with login/logoff
  • micro-blog (<= 150 characters per blog)
  • groups (something similar to threads; a provision for related conversation)
  • Active Directory authentication (so there was no need for user registration)

I decided to take up AD authentication later. I understood from Django forum that it was possible to plug-in that feature.

I looked for a ‘cool’ name for the application. Being a micro-blog, I wanted a simple, single syllable word that represents word or conversation or talk. I settled with ‘SOL’ – it is a Tamil word representing what I expected.

Typical Django applications consists of models (classes), views (logic) and templates (presentation). URLs bind all of these components together.

Models

It seemed logical to start with models – one for SOLs and another one for group. There is nothing complex here; pretty simple models. I also built form models for each of these. So here is models.py:

class group(models.Model):
	desc = models.TextField(max_length=25)

	def __unicode__(self):
		return unicode(self.id)

	def get_absolute_url(self):
		return "/g/%s" % unicode(self.id)

	class Admin:
		list_display = ('id','desc')
	class Meta:
		ordering = ['-id']

class sol(models.Model):
	body = models.TextField(max_length=150)
	author = models.ForeignKey(auth.User)
	date = models.DateTimeField('Date')
	group = models.ForeignKey(group)

	def __unicode__(self):
		return unicode(self.id)

	def get_absolute_url(self):
		return "/sol/%s/" % unicode(self.id)

	def get_author_url(self):
		return "/u/%s/p/0" % (self.author)

	class Admin:
		list_display = ('author', 'body', 'date')
		date_hierarchy = 'date'
	class Meta:
		ordering = ['-date']

class solForm(ModelForm):

	body = forms.CharField(max_length=150, widget=forms.Textarea(attrs={'rows':2, 'cols': 40}),label= 'Your Sol:')
	author = forms.CharField(widget=forms.HiddenInput)
	date = forms.CharField(widget=forms.HiddenInput)
	group = forms.CharField(widget=forms.HiddenInput)

	class Meta:
		model = sol

class grpForm(ModelForm):

	desc = forms.CharField(max_length=25, label= 'Create Your Group:')

	class Meta:
		model = group

Views

There are only three functions (or views):

  • create a group
  • create a SOL
  • display list of SOLs (for groups, users, and all)

It is so simple to display list of SOLs (for that matter any objects). Django provides generic views for that. However I used my own views. All that is needed is the template name and a dictionary of values to be passed to the template.

Object (user or group or a default home page), object id, template name, and page number are passed to the view function; each variable also have default values. Using ObjectPaginator, these objects are paginated. Here is my views.py:

#for pagination: http://www.slideshare.net/simon/the-django-web-application-framework/#

#generic view function for homepage (solhome), userhome and group home
#object is one of the param; object="u" -> user; g->group; s->sol, which is default home page
#object id : for u & g the respective ids; for sol nothing; defaults to sol(0)
#pagenum : for pagination; default is 0 otherwise the pagenumber to be displayed in the input

def home(request, model="s",objectId="0",page_num=0, template='home.html'):
	paginate_by = settings.PAGINATE_BY
	#what we get as parameter is always a string
	page_num = int(page_num)
	if model == 'u': #for user we need to filter for the user
		info_list = ObjectPaginator(sol.objects.all().filter(author__username=objectId),paginate_by)
	elif model== 's': #for sol; home page
		info_list = ObjectPaginator(sol.objects.all(),paginate_by)
	elif model =='g': #for group
		if objectId == '0':
			info_list = ObjectPaginator(group.objects.all(),paginate_by)
		else:
			info_list = ObjectPaginator(sol.objects.all().filter(group=group.objects.get(id=objectId)),paginate_by)

	has_previous = info_list.has_previous_page(page_num)
	has_next = info_list.has_next_page(page_num)

	info_dict = {
		'query_list' : info_list.get_page(page_num),
		'has_previous' : has_previous,
		'previous_page' : page_num - 1,
		'has_next' : has_next,
		'next_page' : page_num + 1,
		'site_name' : 'sol',
		'user' : request.user,
	}

	if model == 's':
		form = solForm()
		#this is how you append to a dict
		info_dict['solForm'] = form

	if model == 'u':
		#this is how you append to a dict
		info_dict['u_id'] = objectId
		info_dict['nickname'] = userprofile.objects.get(user__username=objectId).nickname

	if model == 'g':
		info_dict['grpForm'] = grpForm()
		if objectId == '0':
			info_dict['solForm'] = solForm()
		else:
			info_dict['solForm'] = solForm(initial={'group': group.objects.get(id=objectId)})
			info_dict['grpName'] = group.objects.get(id=objectId).desc

	return render_to_response(template, info_dict)

def logout(request):
		auth.logout(request)
		return HttpResponseRedirect('/')

def createsol(request):
	#if there is nothing in the text field, do nothing
	if request.POST['body'] == "":
		return HttpResponseRedirect('/')

	newsol = sol()
	newsol.author = request.user
	newsol.date = datetime.datetime.today()
	newsol.body = request.POST['body']
	if request.POST['group'] <> '':
		newsol.group = group.objects.get(id=request.POST['group'])
	newsol.save()
	return HttpResponseRedirect('/')

def creategroup(request):
	if request.POST['desc'] == "":
		return HttpResponseRedirect('/groups/')

	newgrp = group()
	newgrp.desc = request.POST['desc']
	newgrp.save()
	return HttpResponseRedirect('/groups/')

def help(request):
	return render_to_response('help.html')

SOL URLs:

URLs are the routing engines connecting both models and views. The URLs for SOL are:

  • homepage (of sol) – listing all SOLs
  • user’s homepage – SOLs of a particular user
  • group homepage – SOLs belonging to a particular group
  • creation of new sol
  • creation of new group

With support for pagination and admin, url.py is like:

urlpatterns = patterns('cool.sol.views',
     #default page number is 0;
     #url like: http://sol.com/
     (r'^$', 'home'),

     #homepage for sol with pagination enabled
     #url like: http://sol.com/p/0
     (r'^p/(?P<page_num>d+)/$', 'home',{'template':'home.html', 'model':'s'}),

     #user homepage - employees with employee ids as numbers;
     #url like: http://sol.com/u/1029/p/0
     (r'^u/(?P<objectId>d+)/p/(?P<page_num>d+)/$', 'home',{'template':'user_home.html', 'model':'u'}),

     #groups homepage
     #url like: http://sol.com/groups/ or http://sol.com/groups/p/0 
     (r'^groups/$', 'home',{'template':'groups_home.html', 'model':'g'}),
     (r'^groups/p/(?P<page_num>d+)/$', 'home',{'template':'groups_home.html', 'model':'g'}),

     #homepage for a particular group
     #url like: http://sol.com/g/100/p/0
     (r'^g/(?P<objectId>d+)/$', 'home',{'template':'group_home.html', 'model':'g'}),
     (r'^g/(?P<objectId>d+)/p/(?P<page_num>d+)/$', 'home',{'template':'group_home.html', 'model':'g'}),

     #logoff
     (r'^logout/$', 'logout'),

     #create a sol; called by form action
     (r'^createsol/$', 'createsol'),

     #create a sol; called by form action
     (r'^creategroup/$', 'creategroup'),

     #help
     (r'^help/$', 'help'),
)

#these are derived from admin
urlpatterns += patterns('',
        #login page
        (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),

        #admin page
        (r'^admin/', include('django.contrib.admin.urls')),

)

Templates

Only thing left are templates. Django templates can be inherited. Hence first you define the basic template with placeholder blocks – for html headers, page headers like navigation menus and footer – then build upon that template until the final template. It is okay to fill-in only few blocks.

Here is a snippet from base.html.

            <div id="content">
                <div id="left">
                    {% block content %}
                        {% block newgroup %} {% endblock %}
                        {% block newsol %} {% endblock %}
                        {% block entries%} {% endblock %}
                        {% block paginate %} {% endblock %}
                    {% endblock %}
                </div>
                <div id="footer">
                    {% block footer %}{% endblock %}
                </div>
            </div>

Now in home.html, I derive from base.html and define what is needed:

{# Template for home page for sol; it lists all the sols #}
{% extends "base.html" %}

{% block newsol %}
{% if user.is_authenticated %}
<div class="form">
	<form action="/createsol/" method="post" enctype="multipart/form-data">
	  <table>
		{{ solForm }}
	  </table>
	  <input type="submit" value="Update" />
	</form>
</div>
{% endif %}
{% endblock newsol%}

Once you get these concepts (model, view, template and urls), it is extremely easy to put together a website.

The complete code can be downloaded from google code page.

(Note: There might be a better way to do all of this. As I read more and write more of Django, I hope to figure them out. However, if there is a better way, there is no harm in leaving a comment about the same.)

Related Posts:

Written by Joseph Jude

April 5th, 2008 at 11:26 pm

Posted in Programming

Tagged with

9 Responses to 'A simple ‘Twitter’ clone in Django'

Subscribe to comments with RSS or TrackBack to 'A simple ‘Twitter’ clone in Django'.

  1. I thought you might want to know about monologista, a twitter clone out of Japan – so most of the info is in Japanese, but you can go through the code if you like.
    * http://monologista.jp
    * http://trac.monologista.jp/

    Rob Hawkins

    6 Apr 08 at 5:26 pm

  2. Nice work. Always glad to see more people discovering Django!

    Tim

    17 Apr 08 at 8:57 pm

  3. Great work! But i can’t test it…i’m on Ubuntu linux and your Google Code release is a .exe executable. I was trying to find models.py but is not on the .zip file… It would be great if you release those files!

    Alex

    19 Jul 08 at 10:33 pm

  4. Alex: Thank you for your appreciation.

    You can browse the sources in google code: http://code.google.com/p/s-o-l/source/browse. You can also checkout the sources using a svn client.

    Joseph Jude

    20 Jul 08 at 7:45 am

  5. Can you provide the source code in tgz format?

    Dr Zen

    11 Jan 09 at 9:56 am

  6. Source code is provided as a .gz file. Please check out.

    Joseph Jude

    12 Jan 09 at 1:47 pm

  7. Hi,

    I’ve been thinking about getting something twitter for use with my own team. I was wondering if you mind, if I used ur source, and maybe, make some changes.

    Also, I will be willing to submit the changes back to you, for review.

    let me know what you think.
    Btw, sol is such an apt name! terse, and really conveys the meaning!

    Shiva

    28 Jan 09 at 11:32 am

  8. Shiv: Glad to hear that you find SOL useful. Go ahead with the usage. If you could post how you used it and also the reasoning behind the changes, that will help.

    Joseph Jude

    28 Jan 09 at 11:44 am

  9. [...] Reading: A simple ‘Twitter’ clone in Django s-o-l [...]

    My implementation of a twitter clone in django « Athe.la Development Blog

    30 Mar 09 at 8:11 pm

Leave a Reply