aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortom christie tom@tomchristie.com2011-01-23 23:08:44 +0000
committertom christie tom@tomchristie.com2011-01-23 23:08:44 +0000
commite95198a1c0b206bd3b565bb62d167ada71595099 (patch)
tree65dc7f469b28f09783b732862ab9822b8528f10d
parent4100242fa2395bef8db0c5ffbab6f5d0cf95301d (diff)
downloaddjango-rest-framework-e95198a1c0b206bd3b565bb62d167ada71595099.tar.bz2
Sphinx docs, examples, lots of refactoring
-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.py59
-rw-r--r--src/rest/modelresource.py394
-rw-r--r--src/rest/parsers.py63
-rw-r--r--src/rest/resource.py382
-rw-r--r--src/rest/status.py50
-rw-r--r--src/rest/templates/emitter.html93
-rw-r--r--src/rest/templates/emitter.txt8
-rw-r--r--src/rest/templates/emitter.xhtml3
-rw-r--r--src/rest/templatetags/__init__.py0
-rw-r--r--src/rest/templatetags/__init__.pycbin163 -> 0 bytes
-rw-r--r--src/rest/templatetags/add_query_param.py17
-rw-r--r--src/rest/templatetags/urlize_quoted_links.py100
-rw-r--r--src/rest/templatetags/urlize_quoted_links.pycbin4547 -> 0 bytes
-rw-r--r--src/rest/utils.py170
-rw-r--r--src/settings.py98
-rw-r--r--src/testapp/__init__.py0
-rw-r--r--src/testapp/forms.py7
-rw-r--r--src/testapp/models.py94
-rw-r--r--src/testapp/tests.py162
-rw-r--r--src/testapp/urls.py19
-rw-r--r--src/testapp/views.py118
-rw-r--r--src/urls.py15
26 files changed, 0 insertions, 1883 deletions
diff --git a/src/__init__.py b/src/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/__init__.py
+++ /dev/null
diff --git a/src/initial_data.json b/src/initial_data.json
deleted file mode 100644
index 62103cf9..00000000
--- a/src/initial_data.json
+++ /dev/null
@@ -1,20 +0,0 @@
-[
- {
- "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
deleted file mode 100755
index 5e78ea97..00000000
--- a/src/manage.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/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
deleted file mode 100644
index e69de29b..00000000
--- a/src/rest/__init__.py
+++ /dev/null
diff --git a/src/rest/emitters.py b/src/rest/emitters.py
deleted file mode 100644
index c1ff5cc2..00000000
--- a/src/rest/emitters.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from django.template import RequestContext, loader
-import json
-from utils import dict2xml
-
-class BaseEmitter(object):
- uses_forms = False
-
- def __init__(self, resource):
- self.resource = resource
-
- def emit(self, output):
- return output
-
-class TemplatedEmitter(BaseEmitter):
- template = None
-
- def emit(self, output):
- if output is None:
- content = ''
- else:
- content = json.dumps(output, indent=4, sort_keys=True)
-
- template = loader.get_template(self.template)
- context = RequestContext(self.resource.request, {
- 'content': content,
- 'resource': self.resource,
- })
-
- ret = template.render(context)
-
- # Munge DELETE Response code to allow us to return content
- # (Do this *after* we've rendered the template so that we include the normal deletion response code in the output)
- if self.resource.resp_status == 204:
- self.resource.resp_status = 200
-
- return ret
-
-class JSONEmitter(BaseEmitter):
- def emit(self, output):
- if output is None:
- # Treat None as no message body, rather than serializing
- return ''
- return json.dumps(output)
-
-class XMLEmitter(BaseEmitter):
- def emit(self, output):
- if output is None:
- # Treat None as no message body, rather than serializing
- return ''
- return dict2xml(output)
-
-class HTMLEmitter(TemplatedEmitter):
- template = 'emitter.html'
- uses_forms = True
-
-class TextEmitter(TemplatedEmitter):
- template = 'emitter.txt'
-
-
diff --git a/src/rest/modelresource.py b/src/rest/modelresource.py
deleted file mode 100644
index 6719a9ed..00000000
--- a/src/rest/modelresource.py
+++ /dev/null
@@ -1,394 +0,0 @@
-"""TODO: docs
-"""
-from django.forms import ModelForm
-from django.db.models.query import QuerySet
-from django.db.models import Model
-
-from rest.resource import Resource
-
-import decimal
-import inspect
-import re
-
-
-class ModelResource(Resource):
- """A specialized type of Resource, for RESTful resources that map directly to a Django Model.
- Useful things this provides:
-
- 0. Default input validation based on ModelForms.
- 1. Nice serialization of returned Models and QuerySets.
- 2. A default set of create/read/update/delete operations."""
-
- # The model attribute refers to the Django Model which this Resource maps to.
- # (The Model's class, rather than an instance of the Model)
- model = None
-
- # By default the set of returned fields will be the set of:
- #
- # 0. All the fields on the model, excluding 'id'.
- # 1. All the properties on the model.
- # 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
- #
- # If you wish to override this behaviour,
- # you should explicitly set the fields attribute on your class.
- fields = None
-
- # By default the form used with be a ModelForm for self.model
- # If you wish to override this behaviour or provide a sub-classed ModelForm
- # you should explicitly set the form attribute on your class.
- form = None
-
- # By default the set of input fields will be the same as the set of output fields
- # If you wish to override this behaviour you should explicitly set the
- # form_fields attribute on your class.
- form_fields = None
-
-
- def get_bound_form(self, data=None, is_response=False):
- """Return a form that may be used in validation and/or rendering an html emitter"""
- if self.form:
- return super(self.__class__, self).get_bound_form(data, is_response=is_response)
-
- elif self.model:
- class NewModelForm(ModelForm):
- class Meta:
- model = self.model
- fields = self.form_fields if self.form_fields else None
-
- if data and not is_response:
- return NewModelForm(data)
- elif data and is_response:
- return NewModelForm(instance=data)
- else:
- return NewModelForm()
-
- else:
- return None
-
-
- def cleanup_request(self, data, form_instance):
- """Override cleanup_request to drop read-only fields from the input prior to validation.
- This ensures that we don't error out with 'non-existent field' when these fields are supplied,
- and allows for a pragmatic approach to resources which include read-only elements.
-
- I would actually like to be strict and verify the value of correctness of the values in these fields,
- although that gets tricky as it involves validating at the point that we get the model instance.
-
- See here for another example of this approach:
- http://fedoraproject.org/wiki/Cloud_APIs_REST_Style_Guide
- https://www.redhat.com/archives/rest-practices/2010-April/thread.html#00041"""
- read_only_fields = set(self.fields) - set(self.form_instance.fields)
- input_fields = set(data.keys())
-
- clean_data = {}
- for key in input_fields - read_only_fields:
- clean_data[key] = data[key]
-
- return super(ModelResource, self).cleanup_request(clean_data, form_instance)
-
-
- def cleanup_response(self, data):
- """A munging of Piston's pre-serialization. Returns a dict"""
-
- def _any(thing, fields=()):
- """
- Dispatch, all types are routed through here.
- """
- ret = None
-
- if isinstance(thing, QuerySet):
- ret = _qs(thing, fields=fields)
- elif isinstance(thing, (tuple, list)):
- ret = _list(thing)
- elif isinstance(thing, dict):
- ret = _dict(thing)
- elif isinstance(thing, int):
- ret = thing
- elif isinstance(thing, bool):
- ret = thing
- elif isinstance(thing, type(None)):
- ret = thing
- elif isinstance(thing, decimal.Decimal):
- ret = str(thing)
- elif isinstance(thing, Model):
- ret = _model(thing, fields=fields)
- #elif isinstance(thing, HttpResponse): TRC
- # raise HttpStatusCode(thing)
- elif inspect.isfunction(thing):
- if not inspect.getargspec(thing)[0]:
- ret = _any(thing())
- elif hasattr(thing, '__emittable__'):
- f = thing.__emittable__
- if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1:
- ret = _any(f())
- else:
- ret = str(thing) # TRC TODO: Change this back!
-
- return ret
-
- def _fk(data, field):
- """
- Foreign keys.
- """
- return _any(getattr(data, field.name))
-
- def _related(data, fields=()):
- """
- Foreign keys.
- """
- return [ _model(m, fields) for m in data.iterator() ]
-
- def _m2m(data, field, fields=()):
- """
- Many to many (re-route to `_model`.)
- """
- return [ _model(m, fields) for m in getattr(data, field.name).iterator() ]
-
-
- def _method_fields(data, fields):
- if not data:
- return { }
-
- has = dir(data)
- ret = dict()
-
- for field in fields:
- if field in has:
- ret[field] = getattr(data, field)
-
- return ret
-
- def _model(data, fields=()):
- """
- Models. Will respect the `fields` and/or
- `exclude` on the handler (see `typemapper`.)
- """
- ret = { }
- #handler = self.in_typemapper(type(data), self.anonymous) # TRC
- handler = None # TRC
- get_absolute_url = False
-
- if handler or fields:
- v = lambda f: getattr(data, f.attname)
-
- if not fields:
- """
- Fields was not specified, try to find teh correct
- version in the typemapper we were sent.
- """
- mapped = self.in_typemapper(type(data), self.anonymous)
- get_fields = set(mapped.fields)
- exclude_fields = set(mapped.exclude).difference(get_fields)
-
- if not get_fields:
- get_fields = set([ f.attname.replace("_id", "", 1)
- for f in data._meta.fields ])
-
- # sets can be negated.
- for exclude in exclude_fields:
- if isinstance(exclude, basestring):
- get_fields.discard(exclude)
-
- elif isinstance(exclude, re._pattern_type):
- for field in get_fields.copy():
- if exclude.match(field):
- get_fields.discard(field)
-
- get_absolute_url = True
-
- else:
- get_fields = set(fields)
- if 'absolute_url' in get_fields: # MOVED (TRC)
- get_absolute_url = True
-
- met_fields = _method_fields(handler, get_fields) # TRC
-
- for f in data._meta.local_fields:
- if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]):
- if not f.rel:
- if f.attname in get_fields:
- ret[f.attname] = _any(v(f))
- get_fields.remove(f.attname)
- else:
- if f.attname[:-3] in get_fields:
- ret[f.name] = _fk(data, f)
- get_fields.remove(f.name)
-
- for mf in data._meta.many_to_many:
- if mf.serialize and mf.attname not in met_fields:
- if mf.attname in get_fields:
- ret[mf.name] = _m2m(data, mf)
- get_fields.remove(mf.name)
-
- # try to get the remainder of fields
- for maybe_field in get_fields:
-
- if isinstance(maybe_field, (list, tuple)):
- model, fields = maybe_field
- inst = getattr(data, model, None)
-
- if inst:
- if hasattr(inst, 'all'):
- ret[model] = _related(inst, fields)
- elif callable(inst):
- if len(inspect.getargspec(inst)[0]) == 1:
- ret[model] = _any(inst(), fields)
- else:
- ret[model] = _model(inst, fields)
-
- elif maybe_field in met_fields:
- # Overriding normal field which has a "resource method"
- # so you can alter the contents of certain fields without
- # using different names.
- ret[maybe_field] = _any(met_fields[maybe_field](data))
-
- else:
- maybe = getattr(data, maybe_field, None)
- if maybe:
- if callable(maybe):
- if len(inspect.getargspec(maybe)[0]) == 1:
- ret[maybe_field] = _any(maybe())
- else:
- ret[maybe_field] = _any(maybe)
- else:
- pass # TRC
- #handler_f = getattr(handler or self.handler, maybe_field, None)
- #
- #if handler_f:
- # ret[maybe_field] = _any(handler_f(data))
-
- else:
- # Add absolute_url if it exists
- get_absolute_url = True
-
- # Add all the fields
- for f in data._meta.fields:
- if f.attname != 'id':
- ret[f.attname] = _any(getattr(data, f.attname))
-
- # Add all the propertiess
- klass = data.__class__
- for attr in dir(klass):
- if not attr.startswith('_') and not attr in ('pk','id') and isinstance(getattr(klass, attr, None), property):
- #if attr.endswith('_url') or attr.endswith('_uri'):
- # ret[attr] = self.make_absolute(_any(getattr(data, attr)))
- #else:
- ret[attr] = _any(getattr(data, attr))
- #fields = dir(data.__class__) + ret.keys()
- #add_ons = [k for k in dir(data) if k not in fields and not k.startswith('_')]
- #print add_ons
- ###print dir(data.__class__)
- #from django.db.models import Model
- #model_fields = dir(Model)
-
- #for attr in dir(data):
- ## #if attr.startswith('_'):
- ## # continue
- # if (attr in fields) and not (attr in model_fields) and not attr.startswith('_'):
- # print attr, type(getattr(data, attr, None)), attr in fields, attr in model_fields
-
- #for k in add_ons:
- # ret[k] = _any(getattr(data, k))
-
- # TRC
- # resouce uri
- #if self.in_typemapper(type(data), self.anonymous):
- # handler = self.in_typemapper(type(data), self.anonymous)
- # if hasattr(handler, 'resource_uri'):
- # url_id, fields = handler.resource_uri()
- # ret['resource_uri'] = permalink( lambda: (url_id,
- # (getattr(data, f) for f in fields) ) )()
-
- # TRC
- #if hasattr(data, 'get_api_url') and 'resource_uri' not in ret:
- # try: ret['resource_uri'] = data.get_api_url()
- # except: pass
-
- # absolute uri
- if hasattr(data, 'get_absolute_url') and get_absolute_url:
- try: ret['absolute_url'] = data.get_absolute_url()
- except: pass
-
- for key, val in ret.items():
- if key.endswith('_url') or key.endswith('_uri'):
- ret[key] = self.add_domain(val)
-
- return ret
-
- def _qs(data, fields=()):
- """
- Querysets.
- """
- return [ _any(v, fields) for v in data ]
-
- def _list(data):
- """
- Lists.
- """
- return [ _any(v) for v in data ]
-
- def _dict(data):
- """
- Dictionaries.
- """
- return dict([ (k, _any(v)) for k, v in data.iteritems() ])
-
- # Kickstart the seralizin'.
- return _any(data, self.fields)
-
-
- def create(self, data, headers={}, *args, **kwargs):
- # TODO: test creation on a non-existing resource url
- all_kw_args = dict(data.items() + kwargs.items())
- instance = self.model(**all_kw_args)
- instance.save()
- headers = {}
- if hasattr(instance, 'get_absolute_url'):
- headers['Location'] = self.add_domain(instance.get_absolute_url())
- return (201, instance, headers)
-
- def read(self, headers={}, *args, **kwargs):
- try:
- instance = self.model.objects.get(**kwargs)
- except self.model.DoesNotExist:
- return (404, None, {})
-
- return (200, instance, {})
-
- def update(self, data, headers={}, *args, **kwargs):
- # TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url
- try:
- instance = self.model.objects.get(**kwargs)
- for (key, val) in data.items():
- setattr(instance, key, val)
- except self.model.DoesNotExist:
- instance = self.model(**data)
- instance.save()
-
- instance.save()
- return (200, instance, {})
-
- def delete(self, headers={}, *args, **kwargs):
- try:
- instance = self.model.objects.get(**kwargs)
- except self.model.DoesNotExist:
- return (404, None, {})
-
- instance.delete()
- return (204, None, {})
-
-
-
-class QueryModelResource(ModelResource):
- allowed_methods = ('read',)
- queryset = None
-
- def get_bound_form(self, data=None, is_response=False):
- return None
-
- def read(self, headers={}, *args, **kwargs):
- if self.queryset:
- return (200, self.queryset, {})
- queryset = self.model.objects.all()
- return (200, queryset, {})
-
diff --git a/src/rest/parsers.py b/src/rest/parsers.py
deleted file mode 100644
index ac449e49..00000000
--- a/src/rest/parsers.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import json
-from rest.status import ResourceException, Status
-
-class BaseParser(object):
- def __init__(self, resource):
- self.resource = resource
-
- def parse(self, input):
- return {}
-
-
-class JSONParser(BaseParser):
- def parse(self, input):
- try:
- return json.loads(input)
- except ValueError, exc:
- raise ResourceException(Status.HTTP_400_BAD_REQUEST, {'detail': 'JSON parse error - %s' % str(exc)})
-
-class XMLParser(BaseParser):
- pass
-
-class FormParser(BaseParser):
- """The default parser for form data.
- Return a dict containing a single value for each non-reserved parameter
- """
- def __init__(self, resource):
-
- if resource.request.method == 'PUT':
- # Fix from piston to force Django to give PUT requests the same
- # form processing that POST requests get...
- #
- # Bug fix: if _load_post_and_files has already been called, for
- # example by middleware accessing request.POST, the below code to
- # pretend the request is a POST instead of a PUT will be too late
- # to make a difference. Also calling _load_post_and_files will result
- # in the following exception:
- # AttributeError: You cannot set the upload handlers after the upload has been processed.
- # The fix is to check for the presence of the _post field which is set
- # the first time _load_post_and_files is called (both by wsgi.py and
- # modpython.py). If it's set, the request has to be 'reset' to redo
- # the query value parsing in POST mode.
- if hasattr(resource.request, '_post'):
- del request._post
- del request._files
-
- try:
- resource.request.method = "POST"
- resource.request._load_post_and_files()
- resource.request.method = "PUT"
- except AttributeError:
- resource.request.META['REQUEST_METHOD'] = 'POST'
- resource.request._load_post_and_files()
- resource.request.META['REQUEST_METHOD'] = 'PUT'
-
- #
- self.data = {}
- for (key, val) in resource.request.POST.items():
- if key not in resource.RESERVED_PARAMS:
- self.data[key] = val
-
- def parse(self, input):
- return self.data
-
diff --git a/src/rest/resource.py b/src/rest/resource.py
deleted file mode 100644
index b94854f5..00000000
--- a/src/rest/resource.py
+++ /dev/null
@@ -1,382 +0,0 @@
-from django.contrib.sites.models import Site
-from django.core.urlresolvers import reverse
-from django.core.handlers.wsgi import STATUS_CODE_TEXT
-from django.http import HttpResponse
-from rest import emitters, parsers
-from rest.status import Status, ResourceException
-from decimal import Decimal
-import re
-
-# TODO: Authentication
-# TODO: Display user login in top panel: http://stackoverflow.com/questions/806835/django-redirect-to-previous-page-after-login
-# TODO: Return basic object, not tuple of status code, content, headers
-# TODO: Take request, not headers
-# TODO: Standard exception classes
-# TODO: Figure how out references and named urls need to work nicely
-# TODO: POST on existing 404 URL, PUT on existing 404 URL
-#
-# NEXT: Generic content form
-# NEXT: Remove self.blah munging (Add a ResponseContext object?)
-# NEXT: Caching cleverness
-# NEXT: Test non-existent fields on ModelResources
-#
-# FUTURE: Erroring on read-only fields
-
-# Documentation, Release
-
-
-
-class Resource(object):
- # List of RESTful operations which may be performed on this resource.
- allowed_operations = ('read',)
- anon_allowed_operations = ()
-
- # Optional form for input validation and presentation of HTML formatted responses.
- form = None
-
- # List of content-types the resource can respond with, ordered by preference
- emitters = ( ('application/json', emitters.JSONEmitter),
- ('text/html', emitters.HTMLEmitter),
- ('application/xhtml+xml', emitters.HTMLEmitter),
- ('text/plain', emitters.TextEmitter),
- ('application/xml', emitters.XMLEmitter), )
-
- # List of content-types the resource can read from
- parsers = { 'application/json': parsers.JSONParser,
- 'application/xml': parsers.XMLParser,
- 'application/x-www-form-urlencoded': parsers.FormParser,
- 'multipart/form-data': parsers.FormParser }
-
- # Map standard HTTP methods to RESTful operations
- CALLMAP = { 'GET': 'read', 'POST': 'create',
- 'PUT': 'update', 'DELETE': 'delete' }
-
- REVERSE_CALLMAP = dict([(val, key) for (key, val) in CALLMAP.items()])
-
- # Some reserved parameters to allow us to use standard HTML forms with our resource
- METHOD_PARAM = '_method' # Allow POST overloading
- ACCEPT_PARAM = '_accept' # Allow override of Accept header in GET requests
- CONTENTTYPE_PARAM = '_contenttype' # Allow override of Content-Type header (allows sending arbitrary content with standard forms)
- CONTENT_PARAM = '_content' # Allow override of body content (allows sending arbitrary content with standard forms)
- CSRF_PARAM = 'csrfmiddlewaretoken' # Django's CSRF token
-
- RESERVED_PARAMS = set((METHOD_PARAM, ACCEPT_PARAM, CONTENTTYPE_PARAM, CONTENT_PARAM, CSRF_PARAM))
-
-
- def __new__(cls, request, *args, **kwargs):
- """Make the class callable so it can be used as a Django view."""
- self = object.__new__(cls)
- self.__init__()
- return self._handle_request(request, *args, **kwargs)
-
-
- def __init__(self):
- pass
-
-
- def name(self):
- """Provide a name for the resource.
- By default this is the class name, with 'CamelCaseNames' converted to 'Camel Case Names'."""
- class_name = self.__class__.__name__
- return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).strip()
-
-
- def description(self):
- """Provide a description for the resource.
- By default this is the class's docstring with leading line spaces stripped."""
- return re.sub(re.compile('^ +', re.MULTILINE), '', self.__doc__)
-
-
- def available_content_types(self):
- """Return a list of strings of all the content-types that this resource can emit."""
- return [item[0] for item in self.emitters]
-
-
- def resp_status_text(self):
- """Return reason text corrosponding to our HTTP response status code.
- Provided for convienience."""
- return STATUS_CODE_TEXT.get(self.resp_status, '')
-
-
- def reverse(self, view, *args, **kwargs):
- """Return a fully qualified URI for a given view or resource.
- Add the domain using the Sites framework if possible, otherwise fallback to using the current request."""
- return self.add_domain(reverse(view, *args, **kwargs))
-
-
- def add_domain(self, path):
- """Given a path, return an fully qualified URI.
- Use the Sites framework if possible, otherwise fallback to using the domain from the current request."""
-
- # Note that out-of-the-box the Sites framework uses the reserved domain 'example.com'
- # See RFC 2606 - http://www.faqs.org/rfcs/rfc2606.html
- try:
- site = Site.objects.get_current()
- if site.domain and site.domain != 'example.com':
- return 'http://%s%s' % (site.domain, path)
- except:
- pass
-
- return self.request.build_absolute_uri(path)
-
-
- def read(self, headers={}, *args, **kwargs):
- """RESTful read on the resource, which must be subclassed to be implemented. Should be a safe operation."""
- self.not_implemented('read')
-
-
- def create(self, data=None, headers={}, *args, **kwargs):
- """RESTful create on the resource, which must be subclassed to be implemented."""
- self.not_implemented('create')
-
-
- def update(self, data=None, headers={}, *args, **kwargs):
- """RESTful update on the resource, which must be subclassed to be implemented. Should be an idempotent operation."""
- self.not_implemented('update')
-
-
- def delete(self, headers={}, *args, **kwargs):
- """RESTful delete on the resource, which must be subclassed to be implemented. Should be an idempotent operation."""
- self.not_implemented('delete')
-
-
- def not_implemented(self, operation):
- """Return an HTTP 500 server error if an operation is called which has been allowed by
- allowed_operations, but which has not been implemented."""
- raise ResourceException(Status.HTTP_500_INTERNAL_SERVER_ERROR,
- {'detail': '%s operation on this resource has not been implemented' % (operation, )})
-
-
- def determine_method(self, request):
- """Determine the HTTP method that this request should be treated as.
- Allow for PUT and DELETE tunneling via the _method parameter."""
- method = request.method.upper()
-
- if method == 'POST' and self.METHOD_PARAM and request.POST.has_key(self.METHOD_PARAM):
- method = request.POST[self.METHOD_PARAM].upper()
-
- return method
-
-
- def authenticate(self):
- """TODO"""
- # user = ...
- # if DEBUG and request is from localhost
- # if anon_user and not anon_allowed_operations raise PermissionDenied
- # return
-
-
- def check_method_allowed(self, method):
- """Ensure the request method is acceptable for this resource."""
- if not method in self.CALLMAP.keys():
- raise ResourceException(Status.HTTP_501_NOT_IMPLEMENTED,
- {'detail': 'Unknown or unsupported method \'%s\'' % method})
-
- if not self.CALLMAP[method] in self.allowed_operations:
- raise ResourceException(Status.HTTP_405_METHOD_NOT_ALLOWED,
- {'detail': 'Method \'%s\' not allowed on this resource.' % method})
-
-
- def get_bound_form(self, data=None, is_response=False):
- """Optionally return a Django Form instance, which may be used for validation
- and/or rendered by an HTML/XHTML emitter.
-
- If data is not None the form will be bound to data. is_response indicates if data should be
- treated as the input data (bind to client input) or the response data (bind to an existing object)."""
- if self.form:
- if data:
- return self.form(data)
- else:
- return self.form()
- return None
-
-
- def cleanup_request(self, data, form_instance):
- """Perform any resource-specific data deserialization and/or validation
- after the initial HTTP content-type deserialization has taken place.
-
- Returns a tuple containing the cleaned up data, and optionally a form bound to that data.
-
- By default this uses form validation to filter the basic input into the required types."""
- if form_instance is None:
- return data
-
- # Default form validation does not check for additional invalid fields
- non_existent_fields = []
- for key in set(data.keys()) - set(form_instance.fields.keys()):
- non_existent_fields.append(key)
-
- if not form_instance.is_valid() or non_existent_fields:
- if not form_instance.errors and not non_existent_fields:
- # If no data was supplied the errors property will be None
- details = 'No content was supplied'
-
- else:
- # Add standard field errors
- details = dict((key, map(unicode, val)) for (key, val) in form_instance.errors.iteritems())
-
- # Add any non-field errors
- if form_instance.non_field_errors():
- details['errors'] = self.form.non_field_errors()
-
- # Add any non-existent field errors
- for key in non_existent_fields:
- details[key] = ['This field does not exist']
-
- # Bail. Note that we will still serialize this response with the appropriate content type
- raise ResourceException(Status.HTTP_400_BAD_REQUEST, {'detail': details})
-
- return form_instance.cleaned_data
-
-
- def cleanup_response(self, data):
- """Perform any resource-specific data filtering prior to the standard HTTP
- content-type serialization.
-
- Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can."""
- return data
-
-
- 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."""
- content_type = request.META.get('CONTENT_TYPE', 'application/x-www-form-urlencoded')
- split = content_type.split(';', 1)
- if len(split) > 1:
- content_type = split[0]
- content_type = content_type.strip()
-
- try:
- return self.parsers[content_type]
- except KeyError:
- raise ResourceException(Status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
- {'detail': 'Unsupported media type \'%s\'' % content_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 self.ACCEPT_PARAM and request.GET.get(self.ACCEPT_PARAM, None):
- # Use _accept parameter override
- accept_list = [(request.GET.get(self.ACCEPT_PARAM),)]
- elif request.META.has_key('HTTP_ACCEPT'):
- # Use standard HTTP Accept negotiation
- accept_list = [item.split(';') for item in request.META["HTTP_ACCEPT"].split(',')]
- else:
- # No accept header specified
- return default
-
- # Parse the accept header into a dict of {Priority: List of Mimetypes}
- 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)
-
- raise ResourceException(Status.HTTP_406_NOT_ACCEPTABLE,
- {'detail': 'Could not statisfy the client\'s accepted content type',
- 'accepted_types': [item[0] for item in self.emitters]})
-
-
- def _handle_request(self, request, *args, **kwargs):
- """
- Broadly this consists of the following procedure:
-
- 0. ensure the operation is permitted
- 1. deserialize request content into request data, using standard HTTP content types (PUT/POST only)
- 2. cleanup and validate request data (PUT/POST only)
- 3. call the core method to get the response data
- 4. cleanup the response data
- 5. serialize response data into response content, using standard HTTP content negotiation
- """
- emitter = None
- method = self.determine_method(request)
-
- # We make these attributes to allow for a certain amount of munging,
- # eg The HTML emitter needs to render this information
- self.request = request
- self.form_instance = None
- self.resp_status = None
- self.resp_headers = {}
-
- try:
- # Before we attempt anything else determine what format to emit our response data with.
- mimetype, emitter = self.determine_emitter(request)
-
- # Ensure the requested operation is permitted on this resource
- self.check_method_allowed(method)
-
- # Get the appropriate create/read/update/delete function
- func = getattr(self, self.CALLMAP.get(method, ''))
-
- # Either generate the response data, deserializing and validating any request data
- if method in ('PUT', 'POST'):
- parser = self.determine_parser(request)
- data = parser(self).parse(request.raw_post_data)
- self.form_instance = self.get_bound_form(data)
- data = self.cleanup_request(data, self.form_instance)
- (self.resp_status, ret, self.resp_headers) = func(data, request.META, *args, **kwargs)
-
- else:
- (self.resp_status, ret, self.resp_headers) = func(request.META, *args, **kwargs)
- if emitter.uses_forms:
- self.form_instance = self.get_bound_form(ret, is_response=True)
-
-
- except ResourceException, exc:
- # On exceptions we still serialize the response appropriately
- (self.resp_status, ret, self.resp_headers) = (exc.status, exc.content, exc.headers)
-
- # Fall back to the default emitter if we failed to perform content negotiation
- if emitter is None:
- mimetype, emitter = self.emitters[0]
-
- # Provide an empty bound form if we do not have an existing form and if one is required
- if self.form_instance is None and emitter.uses_forms:
- self.form_instance = self.get_bound_form()
-
-
- # Always add the allow header
- self.resp_headers['Allow'] = ', '.join([self.REVERSE_CALLMAP[operation] for operation in self.allowed_operations])
-
- # Serialize the response content
- ret = self.cleanup_response(ret)
- content = emitter(self).emit(ret)
-
- # Build the HTTP Response
- resp = HttpResponse(content, mimetype=mimetype, status=self.resp_status)
- for (key, val) in self.resp_headers.items():
- resp[key] = val
-
- return resp
-
diff --git a/src/rest/status.py b/src/rest/status.py
deleted file mode 100644
index d1b49d69..00000000
--- a/src/rest/status.py
+++ /dev/null
@@ -1,50 +0,0 @@
-
-class Status(object):
- """Descriptive HTTP status codes, for code readability."""
- HTTP_200_OK = 200
- HTTP_201_CREATED = 201
- HTTP_202_ACCEPTED = 202
- HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
- HTTP_204_NO_CONTENT = 204
- HTTP_205_RESET_CONTENT = 205
- HTTP_206_PARTIAL_CONTENT = 206
- HTTP_400_BAD_REQUEST = 400
- HTTP_401_UNAUTHORIZED = 401
- HTTP_402_PAYMENT_REQUIRED = 402
- HTTP_403_FORBIDDEN = 403
- HTTP_404_NOT_FOUND = 404
- HTTP_405_METHOD_NOT_ALLOWED = 405
- HTTP_406_NOT_ACCEPTABLE = 406
- HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
- HTTP_408_REQUEST_TIMEOUT = 408
- HTTP_409_CONFLICT = 409
- HTTP_410_GONE = 410
- HTTP_411_LENGTH_REQUIRED = 411
- HTTP_412_PRECONDITION_FAILED = 412
- HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
- HTTP_414_REQUEST_URI_TOO_LONG = 414
- HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
- HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
- HTTP_417_EXPECTATION_FAILED = 417
- HTTP_100_CONTINUE = 100
- HTTP_101_SWITCHING_PROTOCOLS = 101
- HTTP_300_MULTIPLE_CHOICES = 300
- HTTP_301_MOVED_PERMANENTLY = 301
- HTTP_302_FOUND = 302
- HTTP_303_SEE_OTHER = 303
- HTTP_304_NOT_MODIFIED = 304
- HTTP_305_USE_PROXY = 305
- HTTP_306_RESERVED = 306
- HTTP_307_TEMPORARY_REDIRECT = 307
- HTTP_500_INTERNAL_SERVER_ERROR = 500
- HTTP_501_NOT_IMPLEMENTED = 501
- HTTP_502_BAD_GATEWAY = 502
- HTTP_503_SERVICE_UNAVAILABLE = 503
- HTTP_504_GATEWAY_TIMEOUT = 504
- HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
-
-class ResourceException(Exception):
- def __init__(self, status, content='', headers={}):
- self.status = status
- self.content = content
- self.headers = headers
diff --git a/src/rest/templates/emitter.html b/src/rest/templates/emitter.html
deleted file mode 100644
index 17d53b81..00000000
--- a/src/rest/templates/emitter.html
+++ /dev/null
@@ -1,93 +0,0 @@
-{% load urlize_quoted_links %}{% load add_query_param %}<?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">
- <head>
- <style>
- pre {border: 1px solid black; padding: 1em; background: #ffd}
- div.action {padding: 0.5em 1em; margin-bottom: 0.5em; background: #ddf}
- ul.accepttypes {float: right; list-style-type: none; margin: 0; padding: 0}
- ul.accepttypes li {display: inline;}
- form div {margin: 0.5em 0}
- form div * {vertical-align: top}
- form ul.errorlist {display: inline; margin: 0; padding: 0}
- form ul.errorlist li {display: inline; color: red;}
- .clearing {display: block; margin: 0; padding: 0; clear: both;}
- </style>
- <title>API - {{ resource.name }}</title>
- </head>
- <body>
- <h1>{{ resource.name }}</h1>
- <p>{{ resource.description|linebreaksbr }}</p>
- <pre><b>{{ resource.resp_status }} {{ resource.resp_status_text }}</b>{% autoescape off %}
-{% for key, val in resource.resp_headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
-{% endfor %}
-{{ content|urlize_quoted_links }} </pre>{% endautoescape %}
-
-{% if 'read' in resource.allowed_operations %}
- <div class='action'>
- <a href='{{ resource.request.path }}'>Read</a>
- <ul class="accepttypes">
- {% for content_type in resource.available_content_types %}
- {% with resource.ACCEPT_PARAM|add:"="|add:content_type as param %}
- <li>[<a href='{{ resource.request.path|add_query_param:param }}'>{{ content_type }}</a>]</li>
- {% endwith %}
- {% endfor %}
- </ul>
- <div class="clearing"></div>
- </div>
-{% endif %}
-
-{% if 'create' in resource.allowed_operations %}
- <div class='action'>
- <form action="{{ resource.request.path }}" method="post">
- {% csrf_token %}
- {% with resource.form_instance as form %}
- {% for field in form %}
- <div>
- {{ field.label_tag }}:
- {{ field }}
- {{ field.help_text }}
- {{ field.errors }}
- </div>
- {% endfor %}
- {% endwith %}
- <div class="clearing"></div>
- <input type="submit" value="Create" />
- </form>
- </div>
-{% endif %}
-
-{% if 'update' in resource.allowed_operations %}
- <div class='action'>
- <form action="{{ resource.request.path }}" method="post">
- <input type="hidden" name="{{ resource.METHOD_PARAM}}" value="PUT" />
- {% csrf_token %}
- {% with resource.form_instance as form %}
- {% for field in form %}
- <div>
- {{ field.label_tag }}:
- {{ field }}
- {{ field.help_text }}
- {{ field.errors }}
- </div>
- {% endfor %}
- {% endwith %}
- <div class="clearing"></div>
- <input type="submit" value="Update" />
- </form>
- </div>
-{% endif %}
-
-{% if 'delete' in resource.allowed_operations %}
- <div class='action'>
- <form action="{{ resource.request.path }}" method="post">
- {% csrf_token %}
- <input type="hidden" name="{{ resource.METHOD_PARAM}}" value="DELETE" />
- <input type="submit" value="Delete" />
- </form>
- </div>
-{% endif %}
-
- </body>
-</html> \ No newline at end of file
diff --git a/src/rest/templates/emitter.txt b/src/rest/templates/emitter.txt
deleted file mode 100644
index 925529bd..00000000
--- a/src/rest/templates/emitter.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{{ resource.name }}
-
-{{ resource.description }}
-
-{% autoescape off %}HTTP/1.0 {{ resource.resp_status }} {{ resource.resp_status_text }}
-{% for key, val in resource.resp_headers.items %}{{ key }}: {{ val }}
-{% endfor %}
-{{ content }}{% endautoescape %}
diff --git a/src/rest/templates/emitter.xhtml b/src/rest/templates/emitter.xhtml
deleted file mode 100644
index d9fb3ce9..00000000
--- a/src/rest/templates/emitter.xhtml
+++ /dev/null
@@ -1,3 +0,0 @@
-HTML:
-
-{{ content }} \ No newline at end of file
diff --git a/src/rest/templatetags/__init__.py b/src/rest/templatetags/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/rest/templatetags/__init__.py
+++ /dev/null
diff --git a/src/rest/templatetags/__init__.pyc b/src/rest/templatetags/__init__.pyc
deleted file mode 100644
index 69527f63..00000000
--- a/src/rest/templatetags/__init__.pyc
+++ /dev/null
Binary files differ
diff --git a/src/rest/templatetags/add_query_param.py b/src/rest/templatetags/add_query_param.py
deleted file mode 100644
index 91c1a312..00000000
--- a/src/rest/templatetags/add_query_param.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from django.template import Library
-from urlparse import urlparse, urlunparse
-from urllib import quote
-register = Library()
-
-def add_query_param(url, param):
- (key, val) = param.split('=')
- param = '%s=%s' % (key, quote(val))
- (scheme, netloc, path, params, query, fragment) = urlparse(url)
- if query:
- query += "&" + param
- else:
- query = param
- return urlunparse((scheme, netloc, path, params, query, fragment))
-
-
-register.filter('add_query_param', add_query_param)
diff --git a/src/rest/templatetags/urlize_quoted_links.py b/src/rest/templatetags/urlize_quoted_links.py
deleted file mode 100644
index 4e3ae6c8..00000000
--- a/src/rest/templatetags/urlize_quoted_links.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""Adds the custom filter 'urlize_quoted_links'
-
-This is identical to the built-in filter 'urlize' with the exception that
-single and double quotes are permitted as leading or trailing punctuation.
-"""
-
-# Almost all of this code is copied verbatim from django.utils.html
-# LEADING_PUNCTUATION and TRAILING_PUNCTUATION have been modified
-import re
-import string
-
-from django.utils.safestring import SafeData, mark_safe
-from django.utils.encoding import force_unicode
-from django.utils.http import urlquote
-from django.utils.html import escape
-from django import template
-
-# Configuration for urlize() function.
-LEADING_PUNCTUATION = ['(', '<', '&lt;', '"', "'"]
-TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;', '"', "'"]
-
-# List of possible strings used for bullets in bulleted lists.
-DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
-
-unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
-word_split_re = re.compile(r'(\s+)')
-punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
- ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
- '|'.join([re.escape(x) for x in TRAILING_PUNCTUATION])))
-simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
-link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+')
-html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
-hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
-trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
-
-def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=True):
- """
- Converts any URLs in text into clickable links.
-
- Works on http://, https://, www. links and links ending in .org, .net or
- .com. Links can have trailing punctuation (periods, commas, close-parens)
- and leading punctuation (opening parens) and it'll still do the right
- thing.
-
- If trim_url_limit is not None, the URLs in link text longer than this limit
- will truncated to trim_url_limit-3 characters and appended with an elipsis.
-
- If nofollow is True, the URLs in link text will get a rel="nofollow"
- attribute.
-
- If autoescape is True, the link text and URLs will get autoescaped.
- """
- trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
- safe_input = isinstance(text, SafeData)
- words = word_split_re.split(force_unicode(text))
- nofollow_attr = nofollow and ' rel="nofollow"' or ''
- for i, word in enumerate(words):
- match = None
- if '.' in word or '@' in word or ':' in word:
- match = punctuation_re.match(word)
- if match:
- lead, middle, trail = match.groups()
- # Make URL we want to point to.
- url = None
- if middle.startswith('http://') or middle.startswith('https://'):
- url = urlquote(middle, safe='/&=:;#?+*')
- elif middle.startswith('www.') or ('@' not in middle and \
- middle and middle[0] in string.ascii_letters + string.digits and \
- (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
- url = urlquote('http://%s' % middle, safe='/&=:;#?+*')
- elif '@' in middle and not ':' in middle and simple_email_re.match(middle):
- url = 'mailto:%s' % middle
- nofollow_attr = ''
- # Make link.
- if url:
- trimmed = trim_url(middle)
- if autoescape and not safe_input:
- lead, trail = escape(lead), escape(trail)
- url, trimmed = escape(url), escape(trimmed)
- middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
- words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
- else:
- if safe_input:
- words[i] = mark_safe(word)
- elif autoescape:
- words[i] = escape(word)
- elif safe_input:
- words[i] = mark_safe(word)
- elif autoescape:
- words[i] = escape(word)
- return u''.join(words)
-
-
-#urlize_quoted_links.needs_autoescape = True
-urlize_quoted_links.is_safe = True
-
-# Register urlize_quoted_links as a custom filter
-# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
-register = template.Library()
-register.filter(urlize_quoted_links) \ No newline at end of file
diff --git a/src/rest/templatetags/urlize_quoted_links.pyc b/src/rest/templatetags/urlize_quoted_links.pyc
deleted file mode 100644
index cc77f97f..00000000
--- a/src/rest/templatetags/urlize_quoted_links.pyc
+++ /dev/null
Binary files differ
diff --git a/src/rest/utils.py b/src/rest/utils.py
deleted file mode 100644
index 98d8e1ae..00000000
--- a/src/rest/utils.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import re
-import xml.etree.ElementTree as ET
-from django.utils.encoding import smart_unicode
-from django.utils.xmlutils import SimplerXMLGenerator
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
-# From piston
-def coerce_put_post(request):
- """
- Django doesn't particularly understand REST.
- In case we send data over PUT, Django won't
- actually look at the data and load it. We need
- to twist its arm here.
-
- The try/except abominiation here is due to a bug
- in mod_python. This should fix it.
- """
- if request.method != 'PUT':
- return
-
- # Bug fix: if _load_post_and_files has already been called, for
- # example by middleware accessing request.POST, the below code to
- # pretend the request is a POST instead of a PUT will be too late
- # to make a difference. Also calling _load_post_and_files will result
- # in the following exception:
- # AttributeError: You cannot set the upload handlers after the upload has been processed.
- # The fix is to check for the presence of the _post field which is set
- # the first time _load_post_and_files is called (both by wsgi.py and
- # modpython.py). If it's set, the request has to be 'reset' to redo
- # the query value parsing in POST mode.
- if hasattr(request, '_post'):
- del request._post
- del request._files
-
- try:
- request.method = "POST"
- request._load_post_and_files()
- request.method = "PUT"
- except AttributeError:
- request.META['REQUEST_METHOD'] = 'POST'
- request._load_post_and_files()
- request.META['REQUEST_METHOD'] = 'PUT'
-
- request.PUT = request.POST
-
-# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
-#class object_dict(dict):
-# """object view of dict, you can
-# >>> a = object_dict()
-# >>> a.fish = 'fish'
-# >>> a['fish']
-# 'fish'
-# >>> a['water'] = 'water'
-# >>> a.water
-# 'water'
-# >>> a.test = {'value': 1}
-# >>> a.test2 = object_dict({'name': 'test2', 'value': 2})
-# >>> a.test, a.test2.name, a.test2.value
-# (1, 'test2', 2)
-# """
-# def __init__(self, initd=None):
-# if initd is None:
-# initd = {}
-# dict.__init__(self, initd)
-#
-# def __getattr__(self, item):
-# d = self.__getitem__(item)
-# # if value is the only key in object, you can omit it
-# if isinstance(d, dict) and 'value' in d and len(d) == 1:
-# return d['value']
-# else:
-# return d
-#
-# def __setattr__(self, item, value):
-# self.__setitem__(item, value)
-
-
-# From xml2dict
-class XML2Dict(object):
-
- def __init__(self):
- pass
-
- def _parse_node(self, node):
- node_tree = {}
- # Save attrs and text, hope there will not be a child with same name
- if node.text:
- node_tree = node.text
- for (k,v) in node.attrib.items():
- k,v = self._namespace_split(k, v)
- node_tree[k] = v
- #Save childrens
- for child in node.getchildren():
- tag, tree = self._namespace_split(child.tag, self._parse_node(child))
- if tag not in node_tree: # the first time, so store it in dict
- node_tree[tag] = tree
- continue
- old = node_tree[tag]
- if not isinstance(old, list):
- node_tree.pop(tag)
- node_tree[tag] = [old] # multi times, so change old dict to a list
- node_tree[tag].append(tree) # add the new one
-
- return node_tree
-
-
- def _namespace_split(self, tag, value):
- """
- Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
- ns = http://cs.sfsu.edu/csc867/myscheduler
- name = patients
- """
- result = re.compile("\{(.*)\}(.*)").search(tag)
- if result:
- print tag
- value.namespace, tag = result.groups()
- return (tag, value)
-
- def parse(self, file):
- """parse a xml file to a dict"""
- f = open(file, 'r')
- return self.fromstring(f.read())
-
- def fromstring(self, s):
- """parse a string"""
- t = ET.fromstring(s)
- unused_root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
- return root_tree
-
-
-def xml2dict(input):
- return XML2Dict().fromstring(input)
-
-
-# Piston:
-class XMLEmitter():
- def _to_xml(self, xml, data):
- if isinstance(data, (list, tuple)):
- for item in data:
- xml.startElement("list-item", {})
- self._to_xml(xml, item)
- xml.endElement("list-item")
-
- elif isinstance(data, dict):
- for key, value in data.iteritems():
- xml.startElement(key, {})
- self._to_xml(xml, value)
- xml.endElement(key)
-
- else:
- xml.characters(smart_unicode(data))
-
- def dict2xml(self, data):
- stream = StringIO.StringIO()
-
- xml = SimplerXMLGenerator(stream, "utf-8")
- xml.startDocument()
- xml.startElement("root", {})
-
- self._to_xml(xml, data)
-
- xml.endElement("root")
- xml.endDocument()
- return stream.getvalue()
-
-def dict2xml(input):
- return XMLEmitter().dict2xml(input)
diff --git a/src/settings.py b/src/settings.py
deleted file mode 100644
index fa3476db..00000000
--- a/src/settings.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# 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 = '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',
- 'testapp',
- 'rest',
-)
diff --git a/src/testapp/__init__.py b/src/testapp/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/testapp/__init__.py
+++ /dev/null
diff --git a/src/testapp/forms.py b/src/testapp/forms.py
deleted file mode 100644
index b86d8a5f..00000000
--- a/src/testapp/forms.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django import forms
-
-class ExampleForm(forms.Form):
- title = forms.CharField(max_length=100)
- message = forms.CharField()
- sender = forms.EmailField()
- valid = forms.BooleanField(required=False)
diff --git a/src/testapp/models.py b/src/testapp/models.py
deleted file mode 100644
index 909788a3..00000000
--- a/src/testapp/models.py
+++ /dev/null
@@ -1,94 +0,0 @@
-from django.db import models
-from django.template.defaultfilters import slugify
-import uuid
-
-def uuid_str():
- return str(uuid.uuid1())
-
-#class ExampleModel(models.Model):
-# num = models.IntegerField(default=2, choices=((1,'one'), (2, 'two')))
-# hidden_num = models.IntegerField(verbose_name='Something', help_text='HELP')
-# text = models.TextField(blank=False)
-# another = models.CharField(max_length=10)
-
-
-#class ExampleContainer(models.Model):
-# """Container. Has a key, a name, and some internal data, and contains a set of items."""
-# key = models.CharField(primary_key=True, default=uuid_str, max_length=36, editable=False)
-# name = models.CharField(max_length=256)
-# internal = models.IntegerField(default=0)
-
-# @models.permalink
-# def get_absolute_url(self):
-# return ('testapp.views.ContainerInstance', [self.key])
-
-
-#class ExampleItem(models.Model):
-# """Item. Belongs to a container and has an index number and a note.
-# Items are uniquely identified by their container and index number."""
-# container = models.ForeignKey(ExampleContainer, related_name='items')
-# index = models.IntegerField()
-# note = models.CharField(max_length=1024)
-# unique_together = (container, index)
-
-
-RATING_CHOICES = ((0, 'Awful'),
- (1, 'Poor'),
- (2, 'OK'),
- (3, 'Good'),
- (4, 'Excellent'))
-
-class BlogPost(models.Model):
- key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
- title = models.CharField(max_length=128)
- content = models.TextField()
- created = models.DateTimeField(auto_now_add=True)
- slug = models.SlugField(editable=False, default='')
-
- class Meta:
- ordering = ('created',)
-
- @models.permalink
- def get_absolute_url(self):
- return ('testapp.views.BlogPostInstance', (self.key,))
-
- @property
- @models.permalink
- def comments_url(self):
- """Link to a resource which lists all comments for this blog post."""
- return ('testapp.views.CommentList', (self.key,))
-
- @property
- @models.permalink
- def comment_url(self):
- """Link to a resource which can create a comment for this blog post."""
- return ('testapp.views.CommentCreator', (self.key,))
-
- def __unicode__(self):
- return self.title
-
- def save(self, *args, **kwargs):
- self.slug = slugify(self.title)
- super(self.__class__, self).save(*args, **kwargs)
-
-
-class Comment(models.Model):
- blogpost = models.ForeignKey(BlogPost, editable=False, related_name='comments')
- username = models.CharField(max_length=128)
- comment = models.TextField()
- rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='How did you rate this post?')
- created = models.DateTimeField(auto_now_add=True)
-
- class Meta:
- ordering = ('created',)
-
- @models.permalink
- def get_absolute_url(self):
- return ('testapp.views.CommentInstance', (self.blogpost.key, self.id))
-
- @property
- @models.permalink
- def blogpost_url(self):
- """Link to the blog post resource which this comment corresponds to."""
- return ('testapp.views.BlogPostInstance', (self.blogpost.key,))
-
diff --git a/src/testapp/tests.py b/src/testapp/tests.py
deleted file mode 100644
index 3fcfc9e1..00000000
--- a/src/testapp/tests.py
+++ /dev/null
@@ -1,162 +0,0 @@
-"""Test a range of REST API usage of the example application.
-"""
-
-from django.test import TestCase
-from django.core.urlresolvers import reverse
-from testapp import views
-#import json
-#from rest.utils import xml2dict, dict2xml
-
-
-class AcceptHeaderTests(TestCase):
- """Test correct behaviour of the Accept header as specified by RFC 2616:
-
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
-
- def assert_accept_mimetype(self, mimetype, expect=None):
- """Assert that a request with given mimetype in the accept header,
- gives a response with the appropriate content-type."""
- if expect is None:
- expect = mimetype
-
- resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
-
- self.assertEquals(resp['content-type'], expect)
-
-
- def test_accept_json(self):
- """Ensure server responds with Content-Type of JSON when requested."""
- self.assert_accept_mimetype('application/json')
-
- def test_accept_xml(self):
- """Ensure server responds with Content-Type of XML when requested."""
- self.assert_accept_mimetype('application/xml')
-
- def test_accept_json_when_prefered_to_xml(self):
- """Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
- self.assert_accept_mimetype('application/json,q=0.9;application/xml,q=0.1', expect='application/json')
-
- def test_accept_xml_when_prefered_to_json(self):
- """Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
- self.assert_accept_mimetype('application/xml,q=0.9;application/json,q=0.1', expect='application/xml')
-
- def test_default_json_prefered(self):
- """Ensure server responds with JSON in preference to XML."""
- self.assert_accept_mimetype('application/json;application/xml', expect='application/json')
-
- def test_accept_generic_subtype_format(self):
- """Ensure server responds with an appropriate type, when the subtype is left generic."""
- self.assert_accept_mimetype('text/*', expect='text/html')
-
- def test_accept_generic_type_format(self):
- """Ensure server responds with an appropriate type, when the type and subtype are left generic."""
- self.assert_accept_mimetype('*/*', expect='application/json')
-
- def test_invalid_accept_header_returns_406(self):
- """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
- resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid')
- self.assertNotEquals(resp['content-type'], 'invalid/invalid')
- self.assertEquals(resp.status_code, 406)
-
- def test_prefer_specific_over_generic(self): # This test is broken right now
- """More specific accept types have precedence over less specific types."""
- self.assert_accept_mimetype('application/xml;*/*', expect='application/xml')
-
-
-class AllowedMethodsTests(TestCase):
- """Basic tests to check that only allowed operations may be performed on a Resource"""
-
- def test_reading_a_read_only_resource_is_allowed(self):
- """GET requests on a read only resource should default to a 200 (OK) response"""
- resp = self.client.get(reverse(views.RootResource))
- self.assertEquals(resp.status_code, 200)
-
- def test_writing_to_read_only_resource_is_not_allowed(self):
- """PUT requests on a read only resource should default to a 405 (method not allowed) response"""
- resp = self.client.put(reverse(views.RootResource), {})
- self.assertEquals(resp.status_code, 405)
-#
-# def test_reading_write_only_not_allowed(self):
-# resp = self.client.get(reverse(views.WriteOnlyResource))
-# self.assertEquals(resp.status_code, 405)
-#
-# def test_writing_write_only_allowed(self):
-# resp = self.client.put(reverse(views.WriteOnlyResource), {})
-# self.assertEquals(resp.status_code, 200)
-#
-#
-#class EncodeDecodeTests(TestCase):
-# def setUp(self):
-# super(self.__class__, self).setUp()
-# self.input = {'a': 1, 'b': 'example'}
-#
-# def test_encode_form_decode_json(self):
-# content = self.input
-# resp = self.client.put(reverse(views.WriteOnlyResource), content)
-# output = json.loads(resp.content)
-# self.assertEquals(self.input, output)
-#
-# def test_encode_json_decode_json(self):
-# content = json.dumps(self.input)
-# resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json')
-# output = json.loads(resp.content)
-# self.assertEquals(self.input, output)
-#
-# #def test_encode_xml_decode_json(self):
-# # content = dict2xml(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/json')
-# # output = json.loads(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_form_decode_xml(self):
-# # content = self.input
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_json_decode_xml(self):
-# # content = json.dumps(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_xml_decode_xml(self):
-# # content = dict2xml(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-#class ModelTests(TestCase):
-# def test_create_container(self):
-# content = json.dumps({'name': 'example'})
-# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json')
-# output = json.loads(resp.content)
-# self.assertEquals(resp.status_code, 201)
-# self.assertEquals(output['name'], 'example')
-# self.assertEquals(set(output.keys()), set(('absolute_uri', 'name', 'key')))
-#
-#class CreatedModelTests(TestCase):
-# def setUp(self):
-# content = json.dumps({'name': 'example'})
-# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json', HTTP_ACCEPT='application/json')
-# self.container = json.loads(resp.content)
-#
-# def test_read_container(self):
-# resp = self.client.get(self.container["absolute_uri"])
-# self.assertEquals(resp.status_code, 200)
-# container = json.loads(resp.content)
-# self.assertEquals(container, self.container)
-#
-# def test_delete_container(self):
-# resp = self.client.delete(self.container["absolute_uri"])
-# self.assertEquals(resp.status_code, 204)
-# self.assertEquals(resp.content, '')
-#
-# def test_update_container(self):
-# self.container['name'] = 'new'
-# content = json.dumps(self.container)
-# resp = self.client.put(self.container["absolute_uri"], content, 'application/json')
-# self.assertEquals(resp.status_code, 200)
-# container = json.loads(resp.content)
-# self.assertEquals(container, self.container)
-
diff --git a/src/testapp/urls.py b/src/testapp/urls.py
deleted file mode 100644
index 16ea9a2f..00000000
--- a/src/testapp/urls.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.conf.urls.defaults import patterns
-
-urlpatterns = patterns('testapp.views',
- (r'^$', 'RootResource'),
- #(r'^read-only$', 'ReadOnlyResource'),
- #(r'^write-only$', 'WriteOnlyResource'),
- #(r'^read-write$', 'ReadWriteResource'),
- #(r'^model$', 'ModelFormResource'),
- #(r'^container$', 'ContainerFactory'),
- #(r'^container/((?P<key>[^/]+))$', 'ContainerInstance'),
-
- (r'^blog-posts/$', 'BlogPostList'),
- (r'^blog-post/$', 'BlogPostCreator'),
- (r'^blog-post/(?P<key>[^/]+)/$', 'BlogPostInstance'),
-
- (r'^blog-post/(?P<blogpost_id>[^/]+)/comments/$', 'CommentList'),
- (r'^blog-post/(?P<blogpost_id>[^/]+)/comment/$', 'CommentCreator'),
- (r'^blog-post/(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', 'CommentInstance'),
-)
diff --git a/src/testapp/views.py b/src/testapp/views.py
deleted file mode 100644
index 82539435..00000000
--- a/src/testapp/views.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from rest.resource import Resource
-from rest.modelresource import ModelResource, QueryModelResource
-from testapp.models import BlogPost, Comment
-
-##### Root Resource #####
-
-class RootResource(Resource):
- """This is the top level resource for the API.
- All the sub-resources are discoverable from here."""
- allowed_operations = ('read',)
-
- def read(self, headers={}, *args, **kwargs):
- return (200, {'blog-posts': self.reverse(BlogPostList),
- 'blog-post': self.reverse(BlogPostCreator)}, {})
-
-
-##### Blog Post Resources #####
-
-BLOG_POST_FIELDS = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url')
-
-class BlogPostList(QueryModelResource):
- """A resource which lists all existing blog posts."""
- allowed_operations = ('read', )
- model = BlogPost
- fields = BLOG_POST_FIELDS
-
-class BlogPostCreator(ModelResource):
- """A resource with which blog posts may be created."""
- allowed_operations = ('create',)
- model = BlogPost
- fields = BLOG_POST_FIELDS
-
-class BlogPostInstance(ModelResource):
- """A resource which represents a single blog post."""
- allowed_operations = ('read', 'update', 'delete')
- model = BlogPost
- fields = BLOG_POST_FIELDS
-
-
-##### Comment Resources #####
-
-COMMENT_FIELDS = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
-
-class CommentList(QueryModelResource):
- """A resource which lists all existing comments for a given blog post."""
- allowed_operations = ('read', )
- model = Comment
- fields = COMMENT_FIELDS
-
-class CommentCreator(ModelResource):
- """A resource with which blog comments may be created for a given blog post."""
- allowed_operations = ('create',)
- model = Comment
- fields = COMMENT_FIELDS
-
-class CommentInstance(ModelResource):
- """A resource which represents a single comment."""
- allowed_operations = ('read', 'update', 'delete')
- model = Comment
- fields = COMMENT_FIELDS
-
-
-
-#
-#'read-only-api': self.reverse(ReadOnlyResource),
-# 'write-only-api': self.reverse(WriteOnlyResource),
-# 'read-write-api': self.reverse(ReadWriteResource),
-# 'model-api': self.reverse(ModelFormResource),
-# 'create-container': self.reverse(ContainerFactory),
-#
-#class ReadOnlyResource(Resource):
-# """This is my docstring
-# """
-# allowed_operations = ('read',)
-#
-# def read(self, headers={}, *args, **kwargs):
-# return (200, {'ExampleString': 'Example',
-# 'ExampleInt': 1,
-# 'ExampleDecimal': 1.0}, {})
-#
-#
-#class WriteOnlyResource(Resource):
-# """This is my docstring
-# """
-# allowed_operations = ('update',)
-#
-# def update(self, data, headers={}, *args, **kwargs):
-# return (200, data, {})
-#
-#
-#class ReadWriteResource(Resource):
-# allowed_operations = ('read', 'update', 'delete')
-# create_form = ExampleForm
-# update_form = ExampleForm
-#
-#
-#class ModelFormResource(ModelResource):
-# allowed_operations = ('read', 'update', 'delete')
-# model = ExampleModel
-#
-## Nice things: form validation is applied to any input type
-## html forms for output
-## output always serialized nicely
-#class ContainerFactory(ModelResource):
-# allowed_operations = ('create',)
-# model = ExampleContainer
-# fields = ('absolute_uri', 'name', 'key')
-# form_fields = ('name',)
-#
-#
-#class ContainerInstance(ModelResource):
-# allowed_operations = ('read', 'update', 'delete')
-# model = ExampleContainer
-# fields = ('absolute_uri', 'name', 'key')
-# form_fields = ('name',)
-
-#######################
-
diff --git a/src/urls.py b/src/urls.py
deleted file mode 100644
index 41a32efa..00000000
--- a/src/urls.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from django.conf.urls.defaults import patterns, include
-from django.contrib import admin
-
-admin.autodiscover()
-
-urlpatterns = patterns('',
- # Example:
- (r'^', include('testapp.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)),
-)