finally{}

all will be well finally

Archive for April 5th, 2008

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.)

Written by Joseph Jude

April 5th, 2008 at 11:26 pm

Posted in Programming

Tagged with