diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/__init__.py | 0 | ||||
| -rw-r--r-- | src/initial_data.json | 20 | ||||
| -rwxr-xr-x | src/manage.py | 11 | ||||
| -rw-r--r-- | src/rest/__init__.py | 0 | ||||
| -rw-r--r-- | src/rest/emitters.py | 39 | ||||
| -rw-r--r-- | src/rest/parsers.py | 18 | ||||
| -rw-r--r-- | src/rest/resource.py | 113 | ||||
| -rw-r--r-- | src/settings.py | 97 | ||||
| -rw-r--r-- | src/testarchive/__init__.py | 0 | ||||
| -rw-r--r-- | src/testarchive/models.py | 3 | ||||
| -rw-r--r-- | src/testarchive/templates/emitter.html | 11 | ||||
| -rw-r--r-- | src/testarchive/templates/emitter.txt | 4 | ||||
| -rw-r--r-- | src/testarchive/templates/emitter.xhtml | 3 | ||||
| -rw-r--r-- | src/testarchive/tests.py | 23 | ||||
| -rw-r--r-- | src/testarchive/urls.py | 7 | ||||
| -rw-r--r-- | src/testarchive/views.py | 8 | ||||
| -rw-r--r-- | src/urls.py | 15 |
17 files changed, 372 insertions, 0 deletions
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)), +) |
