Joseph Jude
A Simple 'Twitter' clone in 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.)