diff options
| author | tom christie tom@tomchristie.com | 2011-01-23 23:08:44 +0000 | 
|---|---|---|
| committer | tom christie tom@tomchristie.com | 2011-01-23 23:08:44 +0000 | 
| commit | e95198a1c0b206bd3b565bb62d167ada71595099 (patch) | |
| tree | 65dc7f469b28f09783b732862ab9822b8528f10d | |
| parent | 4100242fa2395bef8db0c5ffbab6f5d0cf95301d (diff) | |
| download | django-rest-framework-e95198a1c0b206bd3b565bb62d167ada71595099.tar.bz2 | |
Sphinx docs, examples, lots of refactoring
| -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 | 59 | ||||
| -rw-r--r-- | src/rest/modelresource.py | 394 | ||||
| -rw-r--r-- | src/rest/parsers.py | 63 | ||||
| -rw-r--r-- | src/rest/resource.py | 382 | ||||
| -rw-r--r-- | src/rest/status.py | 50 | ||||
| -rw-r--r-- | src/rest/templates/emitter.html | 93 | ||||
| -rw-r--r-- | src/rest/templates/emitter.txt | 8 | ||||
| -rw-r--r-- | src/rest/templates/emitter.xhtml | 3 | ||||
| -rw-r--r-- | src/rest/templatetags/__init__.py | 0 | ||||
| -rw-r--r-- | src/rest/templatetags/__init__.pyc | bin | 163 -> 0 bytes | |||
| -rw-r--r-- | src/rest/templatetags/add_query_param.py | 17 | ||||
| -rw-r--r-- | src/rest/templatetags/urlize_quoted_links.py | 100 | ||||
| -rw-r--r-- | src/rest/templatetags/urlize_quoted_links.pyc | bin | 4547 -> 0 bytes | |||
| -rw-r--r-- | src/rest/utils.py | 170 | ||||
| -rw-r--r-- | src/settings.py | 98 | ||||
| -rw-r--r-- | src/testapp/__init__.py | 0 | ||||
| -rw-r--r-- | src/testapp/forms.py | 7 | ||||
| -rw-r--r-- | src/testapp/models.py | 94 | ||||
| -rw-r--r-- | src/testapp/tests.py | 162 | ||||
| -rw-r--r-- | src/testapp/urls.py | 19 | ||||
| -rw-r--r-- | src/testapp/views.py | 118 | ||||
| -rw-r--r-- | src/urls.py | 15 | 
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 Binary files differdeleted file mode 100644 index 69527f63..00000000 --- a/src/rest/templatetags/__init__.pyc +++ /dev/null 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  = ['(', '<', '<', '"', "'"] -TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>', '"', "'"] - -# List of possible strings used for bullets in bulleted lists. -DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•'] - -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>(?: |\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 Binary files differdeleted file mode 100644 index cc77f97f..00000000 --- a/src/rest/templatetags/urlize_quoted_links.pyc +++ /dev/null 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)), -)  | 
