The idea for this site came when some co-workers and I were collecting our W2’s and found letters attached with some HR information. One of them had a ridiculously long URL at the bottom of it (…Enroll%20in%20UCF%20Retirement%20Plans…). Before seeing that URL I hadn’t thought of the convenience of services like
TinyURL outside of the internet. We realized it would be simple enough to write one that is specific to UCF.
We decided on a few simple features and jumped into
pair programming the site with
django. Since
Jason was new to Django, he obvserved while I drove. It took less than the rest of the day to finish the site.
Features
Only allows domains that we specify
We created a custom Form Field to accomplish this and made it able to accept a tuple of allowed domains. If anyone needs this on their site they can use the following code.
from django import forms
class URLDomainField(forms.URLField):
domain = ''
def __init__(self, *args, **kwargs):
# set domain to passed value or default
self.domain = kwargs.get('domain', ('gregallard.com', 'isthemarketdown.com', 'codespatter.com'))
# remove from list if exists
try:
del kwargs['domain']
except:
pass
# call parent init
super(URLDomainField, self).__init__(*args, **kwargs)
def clean(self, value):
# call parent clean
value = super(URLDomainField, self).clean(value)
from urlparse import urlparse
o = urlparse(value)
# endswith accepts tuples and will try them all and will return false if none match
if not o.hostname.endswith(self.domain):
raise forms.ValidationError('%s is not a valid url! %s domains only.' % (value, self.domain))
return value |
from django import forms
class URLDomainField(forms.URLField):
domain = ''
def __init__(self, *args, **kwargs):
# set domain to passed value or default
self.domain = kwargs.get('domain', ('gregallard.com', 'isthemarketdown.com', 'codespatter.com'))
# remove from list if exists
try:
del kwargs['domain']
except:
pass
# call parent init
super(URLDomainField, self).__init__(*args, **kwargs)
def clean(self, value):
# call parent clean
value = super(URLDomainField, self).clean(value)
from urlparse import urlparse
o = urlparse(value)
# endswith accepts tuples and will try them all and will return false if none match
if not o.hostname.endswith(self.domain):
raise forms.ValidationError('%s is not a valid url! %s domains only.' % (value, self.domain))
return value
The code to use this would look like this:
class LinkForm(forms.Form):
url = URLDomainField(domain=('ivylees.com', 'ucf.edu')) |
class LinkForm(forms.Form):
url = URLDomainField(domain=('ivylees.com', 'ucf.edu'))
Automatically creates 5 character alphanumeric string
This method in the model creates a string and makes sure it isn’t in use yet:
def make_short(self):
from random import Random
import string
cool = False
while not cool:
self.short = ''.join( Random().sample(string.letters+string.digits, 5) )
try:
r = Link.objects.get(short=self.short)
except Link.DoesNotExist:
if self.short != "admin" and self.short != "thank":
cool = True |
def make_short(self):
from random import Random
import string
cool = False
while not cool:
self.short = ''.join( Random().sample(string.letters+string.digits, 5) )
try:
r = Link.objects.get(short=self.short)
except Link.DoesNotExist:
if self.short != "admin" and self.short != "thank":
cool = True
Allows for custom strings
By default it will create a 5 character alphanumeric string to go at the end of the URL, however we added a form field to allow users to specify their own string so that the URL might have more meaning. To strip non alphanumeric characters, we created a simple clean method in the model:
def clean_short(self):
import re
# ^ as first character inside [] negates the set
# find everything that isn't alphanumeric or a -
self.short = re.sub('[^\w|\-]', '_', self.short) |
def clean_short(self):
import re
# ^ as first character inside [] negates the set
# find everything that isn't alphanumeric or a -
self.short = re.sub('[^\w|\-]', '_', self.short)
Won’t create more short links
If a URL has been submitted before, the site will not create an extra URL for it, instead it will return the existing one to the user. To do this, we added some functionality to the save method:
def save(self, **kwargs):
link = Link.objects.filter(url=self.url)[:1]
# if one exists, return it, otherwise save it
if link:
# there should be a better way to do this
# but self = link doesn't work
self.url = link[0].url
self.short = link[0].short
self.created = link[0].created
self.id = link[0].id
else:
if self.short == '':
self.make_short()
else:
self.clean_short()
super(Link, self).save(**kwargs) |
def save(self, **kwargs):
link = Link.objects.filter(url=self.url)[:1]
# if one exists, return it, otherwise save it
if link:
# there should be a better way to do this
# but self = link doesn't work
self.url = link[0].url
self.short = link[0].short
self.created = link[0].created
self.id = link[0].id
else:
if self.short == '':
self.make_short()
else:
self.clean_short()
super(Link, self).save(**kwargs)
Just a Prototype
We just wanted to create something simple as a prototype so that hopefully some of the higher-ups will like the idea and we can put it into production.
Pair Programming
This was the first time I had any experience with pair programming and I definitely think it’s a great idea. Jason learned a lot about django, caught my mistakes, and pointed out other things. I solidified my knowledge by showing him what I knew and we both learned some valuable things. For example: using print foo will be displayed in the command window when you are using the django development server. I foresee more pair programming in my future.
Update 2009-01-28
Tim recommended that I remove the chance of profanity to be automatically generated for the url and suggested removing all vowels so that no words will be there. This is the line I added to achieve that.
letters = re.sub('a|e|i|o|u|A|E|I|O|U', '', string.letters) |
letters = re.sub('a|e|i|o|u|A|E|I|O|U', '', string.letters)