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.
<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.)
2 Responses to “A simple ‘Twitter’ clone in Django”
By Rob Hawkins on Apr 6, 2008 | Reply
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/
By Tim on Apr 17, 2008 | Reply
Nice work. Always glad to see more people discovering Django!