Skip to content

Commit

Permalink
upgrade to django 1.3; add ability to have custom URLs; add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tehranian committed Jun 4, 2012
1 parent 44fc470 commit 4db38eb
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 419 deletions.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Copyright (c) 2009 Nilesh Kapadia
Copyright (c) 2012 Dan Tehranian

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
Expand Down
7 changes: 1 addition & 6 deletions README
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
url-shortener
django-url-shortener
=============

This is URL shortening application using the Django framework
Expand All @@ -10,11 +10,6 @@ main page shows the 10 most recent and 10 most popular URLs.
Prerequisites
=============

Download Blueprint from: http://www.blueprintcss.org/
Copy the "blueprint" folder into static/css/ (which you may need to create)

Note that in a production installation, you'll want to have your web
server serve the "static" folder instead of letting Django serve it.

Settings
========
Expand Down
20 changes: 0 additions & 20 deletions deploy/deploy.wsgi

This file was deleted.

7 changes: 5 additions & 2 deletions manage.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#!/usr/bin/env python
from django.core.management import execute_manager
import imp
try:
import settings # Assumed to be in the same directory.
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
sys.exit(1)

import settings

if __name__ == "__main__":
execute_manager(settings)
76 changes: 34 additions & 42 deletions settings.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
# Django settings for urlweb project.
import os, logging
#from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
import os
import sys

logging.basicConfig(
level = logging.DEBUG,
format = '%(asctime)s %(levelname)s %(message)s',
)

logging.debug("Reading settings...")

PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
SITE_ROOT = os.path.abspath(os.path.dirname(__file__))

DEBUG = True
TEMPLATE_DEBUG = DEBUG
Expand All @@ -20,84 +12,84 @@

MANAGERS = ADMINS

DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
#DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_NAME = os.path.join(PROJECT_PATH, 'database.sqlite')
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'db.sqlite' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'
TIME_ZONE = 'America/Los_Angeles'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
STATIC_URL = '/static/'

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
MEDIA_ROOT = os.path.join(SITE_ROOT, 'media')

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
MEDIA_URL = '/media/'

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

# Make this unique, and don't share it with anybody.
SECRET_KEY = '#### CHANGE_ME ####'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.middleware.transaction.TransactionMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware',
)

ROOT_URLCONF = 'urlweb.urls'
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.debug',
'django.core.context_processors.media',
'django.core.context_processors.request',
'django.core.context_processors.static',
)

ROOT_URLCONF = 'urls'

TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'templates')
os.path.join(SITE_ROOT, 'templates')
)

INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'urlweb.shortener',
'shortener',
)

STATIC_DOC_ROOT = os.path.join(PROJECT_PATH, 'static')
LOGIN_REDIRECT_URL = '/'

#TEMPLATE_CONTEXT_PROCESSORS += (
# 'django.core.context_processors.request',
# )

SITE_NAME = 'localhost:8000'
SITE_BASE_URL = 'http://' + SITE_NAME + '/'
REQUIRE_LOGIN = True
5 changes: 2 additions & 3 deletions shortener/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from django.contrib import admin

from urlweb.shortener.models import Link
from shortener.models import Link

class LinkAdmin(admin.ModelAdmin):
model = Link
extra = 3
pass

admin.site.register(Link, LinkAdmin)
41 changes: 29 additions & 12 deletions shortener/baseconv.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,37 @@
>>> base20.to_decimal('31e')
1234
"""
import numbers
import string


class EncodingError(ValueError):
pass


class DecodingError(ValueError):
pass


class BaseConverter(object):
decimal_digits = "0123456789"
decimal_digits = string.digits

def __init__(self, digits):
self.digits = digits

def from_decimal(self, i):
if not isinstance(i, numbers.Real):
raise EncodingError('%s is not an int()' % i)
return self.convert(i, self.decimal_digits, self.digits)

def to_decimal(self, s):
if not isinstance(s, basestring):
raise DecodingError('%s is not a basestring()' % s)
for char in s:
if char not in self.digits:
raise EncodingError('Invalid character for encoding: %s' % digit)
return int(self.convert(s, self.digits, self.decimal_digits))

def convert(number, fromdigits, todigits):
# Based on http://code.activestate.com/recipes/111286/
if str(number)[0] == '-':
Expand All @@ -35,13 +53,13 @@ def convert(number, fromdigits, todigits):
# make an integer out of the number
x = 0
for digit in str(number):
x = x * len(fromdigits) + fromdigits.index(digit)
x = x * len(fromdigits) + fromdigits.index(digit)

# create the result in base 'len(todigits)'
if x == 0:
res = todigits[0]
else:
res = ""
res = ''
while x > 0:
digit = x % len(todigits)
res = todigits[digit] + res
Expand All @@ -51,8 +69,7 @@ def convert(number, fromdigits, todigits):
return res
convert = staticmethod(convert)


bin = BaseConverter('01')
hexconv = BaseConverter('0123456789ABCDEF')
base62 = BaseConverter(
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'
)
hexconv = BaseConverter(string.hexdigits)
base62 = BaseConverter(string.digits + string.letters)
32 changes: 32 additions & 0 deletions shortener/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django import forms

from shortener.baseconv import base62
from shortener.models import Link

class LinkSubmitForm(forms.Form):
url = forms.URLField(
label='URL to be shortened',)
custom = forms.CharField(
label='Custom shortened name',
required=False,)

def clean_custom(self):
custom = self.cleaned_data['custom']
if not custom:
return

# test for characters in the requested custom alias that are not
# available in our base62 enconding
for char in custom:
if char not in base62.digits:
raise forms.ValidationError('Invalid character: "%s"' % char)
# make sure this custom alias is not alrady taken
id = base62.to_decimal(custom)
try:
if Link.objects.filter(id=id).exists():
raise forms.ValidationError('"%s" is already taken' % custom)
except OverflowError:
raise forms.ValidationError(
"Your custom name is too long. Are you sure you wanted a "
"shortening service? :)")
return custom
48 changes: 7 additions & 41 deletions shortener/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,25 @@

from django.db import models
from django.conf import settings
#from django.contrib.auth.models import User
from django import forms

from urlweb.shortener.baseconv import base62
from shortener.baseconv import base62

class Link(models.Model):
"""
Model that represents a shortened URL
# Initialize by deleting all Link objects
>>> Link.objects.all().delete()
# Create some Link objects
>>> link1 = Link.objects.create(url="http://www.google.com/")
>>> link2 = Link.objects.create(url="http://www.nileshk.com/")
# Get base 62 representation of id
>>> link1.to_base62()
'B'
>>> link2.to_base62()
'C'
# Set SITE_BASE_URL to something specific
>>> settings.SITE_BASE_URL = 'http://uu4.us/'
# Get short URL's
>>> link1.short_url()
'http://uu4.us/B'
>>> link2.short_url()
'http://uu4.us/C'
# Test usage_count
>>> link1.usage_count
0
>>> link1.usage_count += 1
>>> link1.usage_count
1
"""
url = models.URLField(verify_exists=True, unique=True)
url = models.URLField()
date_submitted = models.DateTimeField(auto_now_add=True)
usage_count = models.IntegerField(default=0)
usage_count = models.PositiveIntegerField(default=0)

def to_base62(self):
return base62.from_decimal(self.id)

def short_url(self):
return settings.SITE_BASE_URL + self.to_base62()

def __unicode__(self):
return self.to_base62() + ' : ' + self.url
return '%s : %s' % (self.to_base62(), self.url)

class LinkSubmitForm(forms.Form):
u = forms.URLField(verify_exists=True,
label='URL to be shortened:',
)
class Meta:
get_latest_by = 'date_submitted'
Loading

0 comments on commit 4db38eb

Please sign in to comment.