Joseph Jude

Technology, Psychology, and Story Telling

A Simple 'Twitter' clone in Django

Posted: Tags: 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.)



Like the post? Retweet it. Got comments? Reply.

Comments

comments powered by Disqus