2009
Updated! This post is now old and out-of-date, I’ve updated the sourcecode and it can be seen at my-implementation-of-a-twitter-clone-in-django-take-2
This is my first ever django app so any feedback would be greatly appreciated. I worked on something simple like this so I can learn the ins and outs of django nice and quickly while still producing something that is of some use in the actual site itself.
As athe.la will be very community-based, I thought it would only be appropriate to have my own kind of twitter so people can post their thoughts and reply to each other.
Further Reading:
A simple ‘Twitter’ clone in Django
s-o-l sourcecode
Specifications
- On the homepage people can see the posts written by their friends (including their own).
- Another page should allow the user to see posts written by their friends’ friends.
- Friendships are one-way. [a] can be a friend of [b] but the vice-versa isn’t necessary.
- Posts will have a required content and an optional title.
- People can reply to posts. (The posts are called Chirps, the replies Whistles)
- These whistles are not themselves chirps and are only seen when the chirp is viewed in detail.
Implementation
I decided to call the app Chirps (in a motion for the user to recognise it’ll be like ‘tweeting’).
models.py
from django.contrib.auth.models import User
from django.db import models
from django.forms import ModelForm
from django import forms
import datetime
class Chirp(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length=200)
pub_date = models.DateTimeField('date chirped')
content = models.CharField(max_length=2000)
def __unicode__(self):
return self.title
class Admin:
list_dispay = ('author', 'title', 'pub_date')
date_hierarchy = 'pub_date'
class Meta:
ordering = ['-pub_date']
class Whistle(models.Model):
author = models.ForeignKey(User)
chirp = models.ForeignKey(Chirp)
pub_date = models.DateTimeField('date whistled')
content = models.CharField(max_length=2000)
def __unicode__(self):
return self.content
class Meta:
ordering = ['pub_date']
class ChirpForm(ModelForm):
content = forms.CharField(max_length=2000, widget=forms.Textarea(attrs={'rows':2, 'cols': 40}),label= 'Content:')
class Meta:
model = Chirp
fields = ('title', 'content')
class WhistleForm(ModelForm):
content = forms.CharField(max_length=2000, widget=forms.Textarea(attrs={'rows':2, 'cols': 40}),label= '')
class Meta:
model = Whistle
fields = ('content')
class Friendship(models.Model):
user = models.ForeignKey(User, primary_key=True)
friends = models.ManyToManyField('self', symmetrical=False,)
def __unicode__(self):
return '%s' % self.user.username
Hopefully this is all pretty straight forward. The Friendship model is something different though for the reason that the ManyToManyField is joining on to itself. Instead of joining more User objects, the model joins the other Friendship objects of that user’s friends. This makes it easier to list friends’ friends.
views.py
from django.http import Http404
from.django.shortcuts import render_to_response, get_object_or_404
from chirps.models import Chirp, ChirpForm, WhistleForm, Friendship
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from django import forms
def index(request):
friendships = get_object_or_404(Friendship,user=request.user)
friends = [friendship.user for friendship in friendships.friends.all()]
info_dict = {
'chirp_list' : Chirp.objects.filter(author__in=friends).order_by('-pub_date')[:20],
'username' : request.user.username,
}
form = ChirpForm()
info_dict['ChirpForm'] = form
return render_to_response('chirps/chirp_list.html', info_dict)
def chirpview(request, chirp_id):
c = get_object_or_404(Chirp, pk=chirp_id)
form = WhistleForm()
return render_to_response('chirps/chirp_detail.html', {'chirp': c, 'WhistleForm' : form,})
def authorview(request, author):
a = get_object_or_404(User, username=author)
return render_to_response('chirps/chirp_author.html', {'author': a,})
def reply(request, chirp_id):
c = get_object_or_404(Chirp, pk=chirp_id)
c.whistle_set.create(content=request.POST['content'], pub_date=datetime.datetime.now(),author=request.user)
return HttpResponseRedirect('../')
def new(request):
c = Chirp(title=request.POST['title'], pub_date=datetime.datetime.now(), content=request.POST['content'], author=request.user)
c.save()
return HttpResponseRedirect('/chirps/')
def addfriend(request, author):
user = Friendship(user=request.user)
friend = Friendship(user=User.objects.get(username=author))
user.friends.add(friend)
user.save()
return HttpResponseRedirect('/chirps/%s/' % author)
def removefriend(request, author):
user = Friendship(user=request.user)
friend = Friendship(user=User.objects.get(username=author))
user.friends.remove(friend)
user.save()
return HttpResponseRedirect('/chirps/%s/' % author)
Note: This code doesn’t yet let you see friends of friends. I’m leaving that until later.
So far so good. Users can now add friends, delete friends, add chirps, view friend’s chirps, reply to chirps, view all chirps of a named author.
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('chirps.views',
(r'^$', 'index'),
(r'^(?P<chirp_id>\d+)/$', 'chirpview'),
(r'^new/$', 'new'),
(r'^(?P<author>\w+)/$', 'authorview'),
(r'^(?P<author>\w+)/add$', 'addfriend'),
(r'^(?P<author>\w+)/remove$', 'removefriend'),
(r'^(?P<chirp_id>\d+)/reply/$', 'reply'),
)</pre>
<pre>
Here I’ve set up some urls so that if someone visits /chirps/[username]/ they’ll be taken to a page that shows all of that author’s chirps- however if they go to /chirps/[integer]/ they’ll be taken directly to that specific chirp. A put a restriction on usernames so that they must always begin with a letter and not a number.
chirp_list.html
{% if chirp_list %}
<ul>
{% for object in chirp_list %}
<li><b>{{ object.title }}</b> - {{object.content}}
<br /><a href="{{ object.id }}">{{ object.whistle_set.count }} whistle{{object.whistle_set.count|pluralize}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No chirps are available.</p>
{% endif %}
chirp_author.html
{% if author.chirp_set.all %}
<ul>
{% for chirp in author.chirp_set.all %}
<li><b>{{ chirp.title }}</b> - {{chirp.content}}
<br /><a href="../{{ chirp.id }}">{{ chirp.whistle_set.count }} whistle{{chirp.whistle_set.count|pluralize}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No chirps are available.</p>
{% endif %}
chirp_detail.html
<h1>{{ chirp.title }}</h1>
<p><a href="/chirps/{{ chirp.author.username }}">{{ chirp.author.username }}</a> wrote:</p>
<p>{{ chirp.content }}</p>
</div>
{% if chirp.whistle_set.all %}
<div style="background:#337;width:500px;">
<h4>Replies</h4>
{% for whistle in chirp.whistle_set.all %}
<div style="background:#454580;margin:9px 5px;padding:5px;border:solid 1px #224;">{{ whistle.content }}<br /><span class="repliedwhen" style="font-size:x-small; color:#88A">published by <a href="/chirps/{{ whistle.author.username }}/">{{ whistle.author.username }}</a> on {{ whistle.pub_date }}</span></div>
{% endfor %}
</div>
{% endif %}
<form action="/chirps/{{ chirp.id }}/reply/" method="post">
{{ WhistleForm }}
<input type="submit" value="Reply" />
Reflection
There’s no effective paging at the moment but as this is a my first app I’m pretty happy with it the way it is, for now. It works well and I like the friendships model. I also went and made my own registration process as I didn’t know how to get django-registration to not send an activation email out and to not allow duplicate email addresses in the database. It was good that I went and did this as I’ve made adjustments to the default registration so that as soon as a person registers, they are a friend of themselves. This means that they can see their own posts on their Chirps homepage.
My Registration Model Addition
def clean_email(self): """ Validate that the email is not already in use. """ try: user = User.objects.get(email__iexact=self.cleaned_data['email']) except User.DoesNotExist: return self.cleaned_data['email'] raise forms.ValidationError(_(u'This email is already taken. Please choose another.')) def save(self): """ save new user object """ new_user = User.objects.create_user(username=self.cleaned_data['username'], password=self.cleaned_data['password1'], email=self.cleaned_data['email']) new_user.is_active = True new_user.save() friend1 = Friendship(user=new_user) friend1.save() friend2 = Friendship(user=new_user) friend1.friends.add(friend2) friend1.save() return new_user
Related posts at athe.la:
- My implementation of a twitter clone in django (Take 2) Ok, so I promised this a long time ago but...
- TimeToggle django custom filter It’s always good to keep web pages clean and free...
- wordbreak filter I wrote my own wordbreak filter for django I realised...
- Logo v0.1 A quick update on my progress, I’ve updated the Chirp...
- What's next? Adding the buzz counter to chirps so I can get...
[...] What’s next? 29 03 2009 Adding the buzz counter to chirps so I can get a list of current top chirps. [...]
[...] quick update on my progress, I’ve updated the Chirp app quite a lot and so the list of things I need to do has [...]
This makes no sense to me. I have no clue on how to do my own twitter. Can someone help!!!!
ac, since I wrote this, the code has been updated a lot. stay tuned for the updated version of the code. I’ll pack it up in a zip so you can download it and play with it yourself.
Thanks for the reply. I hope that I will be able to do this.
[...] my posts on Sub-Pixel Rendering (Hinting) In Chrome Is Inferior To That Of Firefox and my long gone implementation of a twitter clone in django. So far so good, people were using Google to find information and my blog was quite high in the [...]