Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added http response shortcuts to RestView

added rfc 3339 datetime serialization
added absolute URL wrapper for django's reverse
misc view mixing refactoring
fixed exception middleware
  • Loading branch information...
commit 388a59cd20844e1765d93c97252b7044facc1f54 1 parent 5ce2b61
@aehlke authored
View
29 catnap/django_urls.py
@@ -2,17 +2,11 @@
# See: http://github.com/jezdez/django-urls/
from django.conf import settings
+from django.core.urlresolvers import reverse
import urlparse
-class UrlMixin(object):
-
- def get_url(self):
- if hasattr(self.get_url_path, 'dont_recurse'):
- raise NotImplementedError
- try:
- path = self.get_url_path()
- except NotImplementedError:
- raise
+
+def relative_to_absolute_url(path):
# Should we look up a related site?
#if getattr(self._meta, 'url_by_site'):
prefix = getattr(settings, 'DEFAULT_URL_PREFIX', None)
@@ -25,6 +19,17 @@ def get_url(self):
except Site.DoesNotExist:
pass
return prefix + path
+
+class UrlMixin(object):
+
+ def get_url(self):
+ if hasattr(self.get_url_path, 'dont_recurse'):
+ raise NotImplementedError
+ try:
+ path = self.get_url_path()
+ except NotImplementedError:
+ raise
+ return relative_to_absolute_url(path)
get_url.dont_recurse = True
def get_url_path(self):
@@ -38,3 +43,9 @@ def get_url_path(self):
return urlparse.urlunparse(('', '') + bits[2:])
get_url_path.dont_recurse = True
+
+def absolute_reverse(viewname, *args, **kwargs):
+ path = reverse(viewname, *args, **kwargs)
+ return relative_to_absolute_url(path)
+
+
View
15 catnap/http.py
@@ -3,8 +3,23 @@
status codes.
'''
from django.http import HttpResponse
+from django.utils.encoding import iri_to_uri
+class HttpResponseCreated(HttpResponse):
+ status_code = 201
+
+ def __init__(self, location, **kwargs):
+ HttpResponse.__init__(self, **kwargs)
+ self['Location'] = iri_to_uri(location)
+
+class HttpResponseSeeOther(HttpResponse):
+ status_code = 303
+
+ def __init__(self, redirect_to):
+ HttpResponse.__init__(self)
+ self['Location'] = iri_to_uri(redirect_to)
+
class HttpResponseNotAcceptable(HttpResponse):
status_code = 406
View
2  catnap/middleware.py
@@ -59,7 +59,7 @@ class HttpExceptionMiddleware(object):
See `catnap.exceptions`.
'''
def process_exception(self, request, exception):
- if (hasattr(exception, response)
+ if (hasattr(exception, 'response')
and isinstance(exception, HttpResponse)):
return exception.response
return None
View
84 catnap/restviews.py
@@ -12,16 +12,38 @@
+class _HttpResponseShortcuts(object):
+ '''
+ Shortcuts for `RestView.get_response`.
+ '''
+ def __init__(self, parent_view):
+ self.parent = parent_view
+
+ def see_other(redirect_to):
+ return self.parent.get_response(redirect_to,
+ content_type='',
+ httpresponse_class=HttpResponseSeeOther)
+
+ def created(location):
+ return self.parent.get_response(location,
+ content_type='',
+ httpresponse_class=HttpResponseCreated)
+
+
class RestView(View):
'''
A base class view that cares a little more about RESTy things,
- like strict content types.
+ like strict content types and HTTP response codes.
Also lets you use itself as a function rather than making
you put as_view() in your URL conf. (-not yet implemented-!)
Requires `HttpAcceptMiddleware` to be installed.
'''
+
+ def __init__(self, *args, **kwargs):
+ responses = _HttpResponseShortcuts(self)
+
def dispatch(self, request, *args, **kwargs):
# Make sure the `Accept` header matches our content type.
if self.content_type not in request.accept:
@@ -29,16 +51,38 @@ def dispatch(self, request, *args, **kwargs):
return super(RestView, self).dispatch(request, *args, **kwargs)
+ def get_response(self,
+ content,
+ content_type=None,
+ httpresponse_class=HttpResponse,
+ **httpresponse_kwargs):
+ '''
+ Construct an `HttpResponse` object, or whatever response class
+ is specified by `httpresponse_class`.
+
+ The `content_type` defaults to whatever `self.content_type`
+ evaluates to.
- def get_response(self, content, **httpresponse_kwargs):
- 'Construct an `HttpResponse` object.'
+ To take advantage of `self.content_type` and other mixins
+ which may process responses, this method should always be used
+ to construct `HttpResponse` objects (or any other methods
+ which end up calling this one) instead of using `HttpResponse`
+ directly.
+ '''
## The following are for IE especially
#response['Pragma'] = 'no-cache'
#response['Cache-Control'] = 'must-revalidate'
#response['If-Modified-Since'] = str(datetime.datetime.now())
- return HttpResponse(content,
- content_type=self.content_type,
- **httpresponse_kwargs)
+ if content_type is None:
+ content_type = self.content_type
+
+ if content_type == '':
+ content_type = None
+
+ return httpresponse_class(content,
+ content_type=content_type,
+ **httpresponse_kwargs)
+
@@ -92,7 +136,7 @@ def get_context_data(self, **kwargs):
return context
-class ResourceClassDependencyMixin(object):
+class _ResourceClassDependencyMixin(object):
resource_class = None
def get_resource_class(self):
@@ -105,7 +149,7 @@ def get_resource_class(self):
class RestMultipleObjectMixin(SerializableMultipleObjectMixin,
- ResourceClassDependencyMixin,
+ _ResourceClassDependencyMixin,
UrlMixin):
'''
Extends the `SerializableMultipleObjectMixin` class to instantiate
@@ -135,7 +179,7 @@ def get_context_data(self, object_list=None, **kwargs):
class RestSingleObjectMixin(SingleObjectMixin,
- ResourceClassDependencyMixin):
+ _ResourceClassDependencyMixin):
'''
This is a version of `SingleObjectMixin` which is more careful
to avoid duplicate or otherwise unnecessary context items.
@@ -174,19 +218,31 @@ class ListView(RestMultipleObjectMixin, BaseListView):
pass
-class JsonResponseMixin(object):
+class _BaseEmitterMixin(object):
+ @property
+ def serialize_context(self):
+ raise NotImplementedError(
+ 'serialize_context method must be implemented in subclass.')
+
+ def render_to_response(self, context):
+ '''
+ Returns a response containing `context` as payload,
+ using the implemented serializer method.
+ '''
+ return self.get_response(self.serialize_context(context))
+
+
+class JsonEmitterMixin(_BaseEmitterMixin):
# Override this for vendor-specific content types.
# e.g "application/vnd.mycompany.FooBar+json"
content_type = 'application/json'
- def render_to_response(self, context):
- 'Returns a JSON response containing `context` as payload'
- return self.get_response(self.convert_context_to_json(context))
-
def convert_context_to_json(self, context):
'Convert the context dictionary into a JSON object'
return json_serialize(context)
+ serialize_context = convert_context_to_json
+
class AutoContentTypeMixin(object):
'''
View
7 catnap/serializers.py
@@ -17,6 +17,8 @@
from django.core import serializers
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
+import datetime
+from rfc3339 import rfc3339
#from utils import HttpStatusCode
@@ -44,6 +46,8 @@ def base_serialize(data):
in cases where it doesn't recognize the type,
it will fall back to Django's `smart_unicode`.
+ Uses RFC 3339 for datetimes, dates, and times.
+
Returns `dict`.
'''
def _any(thing):
@@ -62,6 +66,9 @@ def _any(thing):
ret = _dict(thing)
elif isinstance(thing, decimal.Decimal):
ret = str(thing)
+ elif isinstance(thing,
+ (datetime.datetime, datetime.date, datetime.time)):
+ ret = rfc3339(thing, use_system_timezone=False)
elif isinstance(thing, Model):
# e.g. a single element from a queryset
ret = _model(thing)
View
4 catnap/tests/api/views.py
@@ -7,13 +7,13 @@
from django.template import RequestContext, loader
from django.core.urlresolvers import reverse
from django.utils.decorators import method_decorator
-from catnap.restviews import (JsonResponseMixin, AutoContentTypeMixin,
+from catnap.restviews import (JsonEmitterMixin, AutoContentTypeMixin,
RestView, ListView, DetailView)
from restresources import UserResource, DeckResource
import models
-class MyRestView(JsonResponseMixin, AutoContentTypeMixin, RestView):
+class MyRestView(JsonEmitterMixin, AutoContentTypeMixin, RestView):
'''
Our JSON-formatted response base class.
'''
View
1  setup.py
@@ -28,6 +28,7 @@
'setuptools',
'Django >= 1.3',
'webob',
+ 'rfc3339',
],
#entry_points
)
Please sign in to comment.
Something went wrong with that request. Please try again.