Prudent Devs

A Simple 'Twitter' clone in Django

create a twitter clone in Django for internal use

#code , #python , #django

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.

{% codeblock %}
<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>

{% endcodeblock %}

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 github 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 Amazon Books:

Links are affiliate links