aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortom christie tom@tomchristie.com2010-12-29 19:37:57 +0000
committertom christie tom@tomchristie.com2010-12-29 19:37:57 +0000
commit650111dc8c0800e5b7d4c878c1d454657b68efca (patch)
treeb2cb8a07eb60abe20012b352d856bb1dcb13f440
downloaddjango-rest-framework-650111dc8c0800e5b7d4c878c1d454657b68efca.tar.bz2
Initial commit of all files to the repository.
-rw-r--r--.hgignore5
-rw-r--r--requirements.txt3
-rw-r--r--src/__init__.py0
-rw-r--r--src/initial_data.json20
-rwxr-xr-xsrc/manage.py11
-rw-r--r--src/rest/__init__.py0
-rw-r--r--src/rest/emitters.py39
-rw-r--r--src/rest/parsers.py18
-rw-r--r--src/rest/resource.py113
-rw-r--r--src/settings.py97
-rw-r--r--src/testarchive/__init__.py0
-rw-r--r--src/testarchive/models.py3
-rw-r--r--src/testarchive/templates/emitter.html11
-rw-r--r--src/testarchive/templates/emitter.txt4
-rw-r--r--src/testarchive/templates/emitter.xhtml3
-rw-r--r--src/testarchive/tests.py23
-rw-r--r--src/testarchive/urls.py7
-rw-r--r--src/testarchive/views.py8
-rw-r--r--src/urls.py15
19 files changed, 380 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000..f72e6586
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,5 @@
+syntax: glob
+
+*.pyc
+*.db
+env
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..8144d828
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+Django==1.2.4
+distribute==0.6.14
+wsgiref==0.1.2
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/__init__.py
diff --git a/src/initial_data.json b/src/initial_data.json
new file mode 100644
index 00000000..62103cf9
--- /dev/null
+++ b/src/initial_data.json
@@ -0,0 +1,20 @@
+[
+ {
+ "pk": 1,
+ "model": "auth.user",
+ "fields": {
+ "username": "admin",
+ "first_name": "",
+ "last_name": "",
+ "is_active": true,
+ "is_superuser": true,
+ "is_staff": true,
+ "last_login": "2010-01-01 00:00:00",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6cbce$e4e808893d586a3301ac3c14da6c84855999f1d8",
+ "email": "test@example.com",
+ "date_joined": "2010-01-01 00:00:00"
+ }
+ }
+] \ No newline at end of file
diff --git a/src/manage.py b/src/manage.py
new file mode 100755
index 00000000..5e78ea97
--- /dev/null
+++ b/src/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import 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.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/src/rest/__init__.py b/src/rest/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/rest/__init__.py
diff --git a/src/rest/emitters.py b/src/rest/emitters.py
new file mode 100644
index 00000000..fa6a2ffd
--- /dev/null
+++ b/src/rest/emitters.py
@@ -0,0 +1,39 @@
+from django.template import Context, loader
+import json
+
+class BaseEmitter(object):
+ def __init__(self, resource, status, headers):
+ self.resource = resource
+ self.status = status
+ self.headers = headers
+
+ def emit(self, output):
+ return output
+
+class TemplatedEmitter(BaseEmitter):
+ def emit(self, output):
+ content = json.dumps(output, indent=4)
+ template = loader.get_template(self.template)
+ context = Context({
+ 'content': content,
+ 'status': self.status,
+ 'headers': self.headers,
+ 'resource_name': self.resource.__class__.__name__,
+ 'resource_doc': self.resource.__doc__
+ })
+ return template.render(context)
+
+class JSONEmitter(BaseEmitter):
+ def emit(self, output):
+ return json.dumps(output)
+
+class XMLEmitter(BaseEmitter):
+ pass
+
+class HTMLEmitter(TemplatedEmitter):
+ template = 'emitter.html'
+
+class TextEmitter(TemplatedEmitter):
+ template = 'emitter.txt'
+
+
diff --git a/src/rest/parsers.py b/src/rest/parsers.py
new file mode 100644
index 00000000..038065f0
--- /dev/null
+++ b/src/rest/parsers.py
@@ -0,0 +1,18 @@
+class BaseParser(object):
+ def __init__(self, resource, request):
+ self.resource = resource
+ self.request = request
+
+ def parse(self, input):
+ return {}
+
+
+class JSONParser(BaseParser):
+ pass
+
+class XMLParser(BaseParser):
+ pass
+
+class FormParser(BaseParser):
+ pass
+
diff --git a/src/rest/resource.py b/src/rest/resource.py
new file mode 100644
index 00000000..7e4a6cd5
--- /dev/null
+++ b/src/rest/resource.py
@@ -0,0 +1,113 @@
+from django.http import HttpResponse
+from decimal import Decimal
+from rest import emitters, parsers
+
+class Resource(object):
+
+ callmap = { 'GET': 'read', 'POST': 'create',
+ 'PUT': 'update', 'DELETE': 'delete' }
+
+ emitters = [ ('application/json', emitters.JSONEmitter),
+ ('text/html', emitters.HTMLEmitter),
+ ('application/xhtml+xml', emitters.HTMLEmitter),
+ ('text/plain', emitters.TextEmitter),
+ ('application/xml', emitters.XMLEmitter), ]
+
+ parsers = { 'application/json': parsers.JSONParser,
+ 'application/xml': parsers.XMLParser,
+ 'application/x-www-form-urlencoded': parsers.FormParser }
+
+ def __new__(cls, request, *args, **kwargs):
+ self = object.__new__(cls)
+ self.__init__()
+ return self._handle_request(request, *args, **kwargs)
+
+ def __init__(self):
+ pass
+
+ def _determine_parser(self, request):
+ """Return the appropriate parser for the input, given the client's 'Content-Type' header,
+ and the content types that this Resource knows how to parse."""
+ print request.META
+ return self.parsers.values()[0]
+
+ # TODO: Raise 415 Unsupported media type
+
+ def _determine_emitter(self, request):
+ """Return the appropriate emitter for the output, given the client's 'Accept' header,
+ and the content types that this Resource knows how to serve.
+
+ See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ """
+
+ default = self.emitters[0]
+
+ if not request.META.has_key('HTTP_ACCEPT'):
+ return default
+
+ # Parse the accept header into a dict of {Priority: List of Mimetypes}
+ accept_list = [item.split(';') for item in request.META["HTTP_ACCEPT"].split(',')]
+ accept_dict = {}
+ for item in accept_list:
+ mimetype = item[0].strip()
+ qvalue = Decimal('1.0')
+
+ if len(item) > 1:
+ # Parse items that have a qvalue eg text/html;q=0.9
+ try:
+ (q, num) = item[1].split('=')
+ if q == 'q':
+ qvalue = Decimal(num)
+ except:
+ # Skip malformed entries
+ continue
+
+ if accept_dict.has_key(qvalue):
+ accept_dict[qvalue].append(mimetype)
+ else:
+ accept_dict[qvalue] = [mimetype]
+
+ # Go through all accepted mimetypes in priority order and return our first match
+ qvalues = accept_dict.keys()
+ qvalues.sort(reverse=True)
+
+ for qvalue in qvalues:
+ for (mimetype, emitter) in self.emitters:
+ for accept_mimetype in accept_dict[qvalue]:
+ if ((accept_mimetype == '*/*') or
+ (accept_mimetype.endswith('/*') and mimetype.startswith(accept_mimetype[:-1])) or
+ (accept_mimetype == mimetype)):
+ return (mimetype, emitter)
+
+ # TODO: Raise 406, Not Acceptable
+
+ def _handle_request(self, request, *args, **kwargs):
+ meth = request.method
+
+ # Parse the HTTP Request content
+ if meth in ('PUT', 'POST'):
+ parser = self._determine_parser(request)
+ data = parser(self, request).parse(request.raw_post_data)
+
+ if meth == "POST":
+ (status, ret, headers) = self.handle_post(data, request.META, *args, **kwargs)
+ else:
+ (status, ret, headers) = self.handle_get(request.META, *args, **kwargs)
+
+ # Serialize the HTTP Response content
+ mimetype, emitter = self._determine_emitter(request)
+ content = emitter(self, status, headers).emit(ret)
+ print mimetype, emitter, content
+
+ # Build the HTTP Response
+ resp = HttpResponse(content, mimetype=mimetype, status=status)
+ for (key, val) in headers.items():
+ resp[key] = val
+
+ return resp
+
+ def handle_get(self):
+ raise NotImplementedError(self.handle_get)
+
+ def handle_post(self):
+ raise NotImplementedError(self.handle_post) \ No newline at end of file
diff --git a/src/settings.py b/src/settings.py
new file mode 100644
index 00000000..2a55c76a
--- /dev/null
+++ b/src/settings.py
@@ -0,0 +1,97 @@
+# Django settings for src project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'sqlite3.db', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ '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.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/London'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-uk'
+
+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
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# 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 = ''
+
+# 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/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'src.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ # Uncomment the next line to enable the admin:
+ 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ # 'django.contrib.admindocs',
+ 'testarchive',
+)
diff --git a/src/testarchive/__init__.py b/src/testarchive/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/testarchive/__init__.py
diff --git a/src/testarchive/models.py b/src/testarchive/models.py
new file mode 100644
index 00000000..71a83623
--- /dev/null
+++ b/src/testarchive/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/src/testarchive/templates/emitter.html b/src/testarchive/templates/emitter.html
new file mode 100644
index 00000000..4c843aa3
--- /dev/null
+++ b/src/testarchive/templates/emitter.html
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <h1>{{ resource_name }}</h1>
+ <p>{{ resource_doc }}</p>
+ <pre>
+{% include 'emitter.txt' %} </pre>
+ </body>
+</html> \ No newline at end of file
diff --git a/src/testarchive/templates/emitter.txt b/src/testarchive/templates/emitter.txt
new file mode 100644
index 00000000..3bf094c6
--- /dev/null
+++ b/src/testarchive/templates/emitter.txt
@@ -0,0 +1,4 @@
+{% autoescape off %}HTTP Status {{ status }}
+{% for key, val in headers.items %}{{ key }}: {{ val }}
+{% endfor %}
+{{ content }}{% endautoescape %} \ No newline at end of file
diff --git a/src/testarchive/templates/emitter.xhtml b/src/testarchive/templates/emitter.xhtml
new file mode 100644
index 00000000..d9fb3ce9
--- /dev/null
+++ b/src/testarchive/templates/emitter.xhtml
@@ -0,0 +1,3 @@
+HTML:
+
+{{ content }} \ No newline at end of file
diff --git a/src/testarchive/tests.py b/src/testarchive/tests.py
new file mode 100644
index 00000000..2aaf1bab
--- /dev/null
+++ b/src/testarchive/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 3)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/src/testarchive/urls.py b/src/testarchive/urls.py
new file mode 100644
index 00000000..81aa0fa8
--- /dev/null
+++ b/src/testarchive/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns
+from testarchive.views import RootResource
+
+
+urlpatterns = patterns('',
+ (r'^$', RootResource),
+)
diff --git a/src/testarchive/views.py b/src/testarchive/views.py
new file mode 100644
index 00000000..a7812dc3
--- /dev/null
+++ b/src/testarchive/views.py
@@ -0,0 +1,8 @@
+from rest.resource import Resource
+
+class RootResource(Resource):
+ """This is my docstring
+ """
+
+ def handle_get(self, headers={}, *args, **kwargs):
+ return (200, {'Name': 'Test', 'Value': 1}, {'Location': 'BLAH'})
diff --git a/src/urls.py b/src/urls.py
new file mode 100644
index 00000000..f6c598ce
--- /dev/null
+++ b/src/urls.py
@@ -0,0 +1,15 @@
+from django.conf.urls.defaults import patterns, include
+from django.contrib import admin
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ # Example:
+ (r'^testarchive/', include('testarchive.urls')),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ (r'^admin/', include(admin.site.urls)),
+)