diff --git a/src/rest/emitters.py b/src/rest/emitters.py
index bafbf372e..87faa559b 100644
--- a/src/rest/emitters.py
+++ b/src/rest/emitters.py
@@ -18,7 +18,7 @@ class TemplatedEmitter(BaseEmitter):
template = None
def emit(self, output):
- content = json.dumps(output, indent=4)
+ content = json.dumps(output, indent=4, sort_keys=True)
template = loader.get_template(self.template)
context = RequestContext(self.request, {
'content': content,
diff --git a/src/rest/resource.py b/src/rest/resource.py
index f14b2ba5d..c57ebfad9 100644
--- a/src/rest/resource.py
+++ b/src/rest/resource.py
@@ -137,15 +137,20 @@ class Resource(object):
- def determine_form(self, data=None):
+ def determine_form(self, input_data=None, return_data=None):
"""Optionally return a Django Form instance, which may be used for validation
and/or rendered by an HTML/XHTML emitter.
- The data argument will be non Null if the form is required to be bound to some deserialized
- input data, or Null if the form is required to be unbound.
+ The input_data or return_data arguments can be used to bind the form either to the deserialized input,
+ or to a return object.
"""
if self.form:
- return self.form(data)
+ if input_data:
+ return self.form(input_data)
+ elif return_data:
+ return self.form(return_data)
+ else:
+ return self.form()
return None
@@ -259,12 +264,13 @@ class Resource(object):
if method in ('PUT', 'POST'):
parser = self.determine_parser(request)
data = parser(self, request).parse(request.raw_post_data)
- form = self.determine_form(data)
+ form = self.determine_form(input_data=data)
data = self.cleanup_request(data, form)
(status, ret, headers) = func(data, request.META, *args, **kwargs)
else:
(status, ret, headers) = func(request.META, *args, **kwargs)
+ form = self.determine_form(return_data=ret)
except ResourceException, exc:
@@ -274,7 +280,7 @@ class Resource(object):
if emitter is None:
mimetype, emitter = self.emitters[0]
- # Use a form unbound to any data if one has not yet been created
+ # Create an unbound form if one has not yet been created
if form is None:
form = self.determine_form()
@@ -284,7 +290,6 @@ class Resource(object):
# Serialize the response content
ret = self.cleanup_response(ret)
content = emitter(self, request, status, headers, form).emit(ret)
- print content
# Build the HTTP Response
resp = HttpResponse(content, mimetype=mimetype, status=status)
@@ -308,7 +313,7 @@ class ModelResource(Resource):
fields = None
form_fields = None
- def determine_form(self, data=None):
+ def determine_form(self, input_data=None, return_data=None):
"""Return a form that may be used in validation and/or rendering an html emitter"""
if self.form:
return self.form
@@ -317,12 +322,14 @@ class ModelResource(Resource):
class NewModelForm(ModelForm):
class Meta:
model = self.model
- fields = self.form_fields if self.form_fields else self.fields
+ fields = self.form_fields if self.form_fields else None #self.fields
- if data is None:
- return NewModelForm()
+ if input_data:
+ return NewModelForm(input_data)
+ elif return_data:
+ return NewModelForm(instance=return_data)
else:
- return NewModelForm(data)
+ return NewModelForm()
else:
return None
@@ -359,6 +366,12 @@ class ModelResource(Resource):
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):
@@ -417,7 +430,7 @@ class ModelResource(Resource):
ret = { }
#handler = self.in_typemapper(type(data), self.anonymous) # TRC
handler = None # TRC
- get_absolute_uri = False
+ get_absolute_url = False
if handler or fields:
v = lambda f: getattr(data, f.attname)
@@ -444,12 +457,13 @@ class ModelResource(Resource):
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_uri' in get_fields: # MOVED (TRC)
- get_absolute_uri = True
+ if 'absolute_url' in get_fields: # MOVED (TRC)
+ get_absolute_url = True
met_fields = _method_fields(handler, get_fields) # TRC
@@ -508,14 +522,37 @@ class ModelResource(Resource):
# 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:
- ret[f.attname] = _any(getattr(data, f.attname))
+ if f.attname != 'id':
+ ret[f.attname] = _any(getattr(data, f.attname))
- fields = dir(data.__class__) + ret.keys()
- add_ons = [k for k in dir(data) if k not in fields]
+ # 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))
+ #for k in add_ons:
+ # ret[k] = _any(getattr(data, k))
# TRC
# resouce uri
@@ -532,9 +569,13 @@ class ModelResource(Resource):
# except: pass
# absolute uri
- if hasattr(data, 'get_absolute_url') and get_absolute_uri:
- try: ret['absolute_uri'] = self.make_absolute(data.get_absolute_url())
+ if hasattr(data, 'get_absolute_url') and get_absolute_url:
+ try: ret['absolute_url'] = self.make_absolute(data.get_absolute_url())
except: pass
+
+ for key, val in ret.items():
+ if key.endswith('_url') or key.endswith('_uri'):
+ ret[key] = self.make_absolute(val)
return ret
@@ -560,8 +601,9 @@ class ModelResource(Resource):
return _any(data, self.fields)
- def create(self, data, headers={}):
- instance = self.model(**data)
+ def create(self, data, headers={}, *args, **kwargs):
+ all_kw_args = dict(data.items() + kwargs.items())
+ instance = self.model(**all_kw_args)
instance.save()
headers = {}
if hasattr(instance, 'get_absolute_url'):
@@ -569,17 +611,37 @@ class ModelResource(Resource):
return (201, instance, headers)
def read(self, headers={}, *args, **kwargs):
- instance = self.model.objects.get(**kwargs)
+ try:
+ instance = self.model.objects.get(**kwargs)
+ except self.model.DoesNotExist:
+ return (404, '', {})
+
return (200, instance, {})
def update(self, data, headers={}, *args, **kwargs):
- instance = self.model.objects.get(**kwargs)
- for (key, val) in data.items():
- setattr(instance, key, val)
+ 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):
instance = self.model.objects.get(**kwargs)
instance.delete()
- return (204, '', {})
\ No newline at end of file
+ return (204, '', {})
+
+
+class QueryModelResource(ModelResource):
+ allowed_methods = ('read',)
+
+ def determine_form(self, input_data=None, return_data=None):
+ return None
+
+ def read(self, headers={}, *args, **kwargs):
+ query = self.model.objects.all()
+ return (200, query, {})
diff --git a/src/rest/templates/emitter.html b/src/rest/templates/emitter.html
index 8be41b7cf..ddc91fbf9 100644
--- a/src/rest/templates/emitter.html
+++ b/src/rest/templates/emitter.html
@@ -11,10 +11,10 @@
{{ resource_name }}
{{ resource_doc }}
- {% autoescape off %}{{ status }} {{ reason }}
+ {{ status }} {{ reason }}{% autoescape off %}
{% for key, val in headers.items %}{{ key }}: {{ val|urlize_quoted_links }}
{% endfor %}
-{{ content|urlize_quoted_links }}{% endautoescape %}
+{{ content|urlize_quoted_links }}
{% endautoescape %}
{% if 'read' in resource.allowed_operations %}
diff --git a/src/rest/templatetags/urlize_quoted_links.py b/src/rest/templatetags/urlize_quoted_links.py
index cef179bf1..4e3ae6c85 100644
--- a/src/rest/templatetags/urlize_quoted_links.py
+++ b/src/rest/templatetags/urlize_quoted_links.py
@@ -33,7 +33,7 @@ html_gunk_re = re.compile(r'(?:
|
<\/i>|<\/b>|<\/em>|(?:%s).*?[a-zA-Z].*?\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
trailing_empty_content_re = re.compile(r'(?:(?: |\s|
)*?
\s*)+\Z')
-def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=False):
+def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=True):
"""
Converts any URLs in text into clickable links.
@@ -90,6 +90,10 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=Fa
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()
diff --git a/src/rest/templatetags/urlize_quoted_links.pyc b/src/rest/templatetags/urlize_quoted_links.pyc
index b49e16b67..cc77f97fb 100644
Binary files a/src/rest/templatetags/urlize_quoted_links.pyc and b/src/rest/templatetags/urlize_quoted_links.pyc differ
diff --git a/src/testapp/models.py b/src/testapp/models.py
index 3960d0043..32d9a612e 100644
--- a/src/testapp/models.py
+++ b/src/testapp/models.py
@@ -1,63 +1,90 @@
from django.db import models
from django.template.defaultfilters import slugify
-from datetime import datetime
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 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)
+#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])
+# @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)
+#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):
- slug = models.SlugField(editable=False, primary_key=True, default='blah')
- title = models.CharField(max_length=128)
- content = models.TextField()
- when = models.DateTimeField(editable=False)
+ key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
+ title = models.CharField(max_length=128, help_text='The article title (Required)')
+ content = models.TextField(help_text='The article body (Required)')
+ 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.slug,))
+ 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)
- self.when = datetime.now()
super(self.__class__, self).save(*args, **kwargs)
class Comment(models.Model):
- blogpost = models.ForeignKey(BlogPost, related_name='comments')
- name = models.CharField(max_length=128)
- content = models.TextField()
- when = models.DateTimeField(auto_now_add=True)
+ blogpost = models.ForeignKey(BlogPost, editable=False, related_name='comments')
+ username = models.CharField(max_length=128, help_text='Please enter a username (Required)')
+ comment = models.TextField(help_text='Enter your comment here (Required)')
+ rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='Please rate the blog post (Optional)')
+ created = models.DateTimeField(auto_now_add=True)
@models.permalink
def get_absolute_url(self):
- return ('testapp.views.CommentInstance', (self.blogpost.slug, self.id))
-
- def save(self):
- self.index = self.blogpost.comments.count()
\ No newline at end of file
+ return ('testapp.views.CommentInstance', (self.blogpost.key, self.id))
+
+ @property
+ @models.permalink
+ def blogpost_url(self):
+ return ('testapp.views.BlogPostInstance', (self.blogpost.key,))
+
diff --git a/src/testapp/tests.py b/src/testapp/tests.py
index ec1607ad1..e37c57c0b 100644
--- a/src/testapp/tests.py
+++ b/src/testapp/tests.py
@@ -1,8 +1,4 @@
-"""
-This file demonstrates two different styles of tests (one doctest and one
-unittest). These will both pass when you run "manage.py test".
-
-Replace these with more appropriate tests for your application.
+"""Test a range of REST API usage of the example application.
"""
from django.test import TestCase
@@ -13,134 +9,154 @@ import json
class AcceptHeaderTests(TestCase):
- def assert_accept_mimetype(self, mimetype, expect=None, expect_match=True):
- """
- Assert that a request with given mimetype in the accept header,
- gives a response with the appropriate content-type.
- """
+ """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.ReadOnlyResource), HTTP_ACCEPT=mimetype)
+ resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
- if expect_match:
- self.assertEquals(resp['content-type'], expect)
- else:
- self.assertNotEquals(resp['content-type'], expect)
+ self.assertEquals(resp['content-type'], expect)
- def test_accept_xml(self):
- self.assert_accept_mimetype('application/xml')
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_prefered_to_json(self):
- self.assert_accept_mimetype('application/xml,q=0.9;application/json,q=0.1', expect='application/xml')
+ 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_prefered_to_xml(self):
+ 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_dont_accept_invalid(self):
- self.assert_accept_mimetype('application/invalid', expect_match=False)
+ 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):
- resp = self.client.get(reverse(views.ReadOnlyResource), HTTP_ACCEPT='invalid/invalid')
+ """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(self):
- self.fail("Test not implemented")
+ 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):
- def test_reading_read_only_allowed(self):
- resp = self.client.get(reverse(views.ReadOnlyResource))
+ """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_read_only_not_allowed(self):
- resp = self.client.put(reverse(views.ReadOnlyResource), {})
+ 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)
+#
+# 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
index 6f87c698c..16ea9a2f2 100644
--- a/src/testapp/urls.py
+++ b/src/testapp/urls.py
@@ -2,13 +2,18 @@ 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[^/]+))$', 'ContainerInstance'),
+ #(r'^read-only$', 'ReadOnlyResource'),
+ #(r'^write-only$', 'WriteOnlyResource'),
+ #(r'^read-write$', 'ReadWriteResource'),
+ #(r'^model$', 'ModelFormResource'),
+ #(r'^container$', 'ContainerFactory'),
+ #(r'^container/((?P[^/]+))$', 'ContainerInstance'),
- (r'^blogpost/create$', 'BlogPostCreator'),
- (r'^blogposts/(?P[^/]+)', 'BlogPostInstance'),
+ (r'^blog-posts/$', 'BlogPostList'),
+ (r'^blog-post/$', 'BlogPostCreator'),
+ (r'^blog-post/(?P[^/]+)/$', 'BlogPostInstance'),
+
+ (r'^blog-post/(?P[^/]+)/comments/$', 'CommentList'),
+ (r'^blog-post/(?P[^/]+)/comment/$', 'CommentCreator'),
+ (r'^blog-post/(?P[^/]+)/comments/(?P[^/]+)/$', 'CommentInstance'),
)
diff --git a/src/testapp/views.py b/src/testapp/views.py
index 33e56bbd0..eca69cc33 100644
--- a/src/testapp/views.py
+++ b/src/testapp/views.py
@@ -1,78 +1,111 @@
-from rest.resource import Resource, ModelResource
-from testapp.forms import ExampleForm
-from testapp.models import ExampleModel, ExampleContainer, BlogPost, Comment
+from rest.resource import Resource, ModelResource, QueryModelResource
+from testapp.models import BlogPost, Comment
class RootResource(Resource):
- """This is my docstring
- """
+ """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, {'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),
- 'blog-post-creator': self.reverse(BlogPostCreator)}, {})
+ return (200, {'blog-posts': self.reverse(BlogPostList),
+ 'blog-post': self.reverse(BlogPostCreator)}, {})
-class ReadOnlyResource(Resource):
- """This is my docstring
- """
- allowed_operations = ('read',)
+# Blog Post Resources
- def read(self, headers={}, *args, **kwargs):
- return (200, {'ExampleString': 'Example',
- 'ExampleInt': 1,
- 'ExampleDecimal': 1.0}, {})
+class BlogPostList(QueryModelResource):
+ """A resource which lists all existing blog posts."""
+ allowed_operations = ('read', )
+ model = BlogPost
-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):
+class BlogPostCreator(ModelResource):
+ """A resource with which blog posts may be created."""
allowed_operations = ('create',)
- model = ExampleContainer
- fields = ('absolute_uri', 'name', 'key')
- form_fields = ('name',)
+ model = BlogPost
+ fields = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url')
-class ContainerInstance(ModelResource):
+class BlogPostInstance(ModelResource):
+ """A resource which represents a single blog post."""
allowed_operations = ('read', 'update', 'delete')
- model = ExampleContainer
- fields = ('absolute_uri', 'name', 'key')
- form_fields = ('name',)
+ model = BlogPost
+ fields = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url')
+
+
+# Comment Resources
+
+class CommentList(QueryModelResource):
+ """A resource which lists all existing comments for a given blog post."""
+ allowed_operations = ('read', )
+ model = Comment
+
+
+class CommentCreator(ModelResource):
+ """A resource with which blog comments may be created for a given blog post."""
+ allowed_operations = ('create',)
+ model = Comment
+ fields = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
+
+
+class CommentInstance(ModelResource):
+ """A resource which represents a single comment."""
+ allowed_operations = ('read', 'update', 'delete')
+ model = Comment
+ fields = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
+
+#
+#'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',)
#######################
-
-class BlogPostCreator(ModelResource):
- """A Resource with which blog posts may be created.
- This is distinct from blog post instance so that it is discoverable by the client.
- (ie the client doens't need to know how to form a blog post url in order to create a blog post)"""
- allowed_operations = ('create',)
- model = BlogPost
-
-class BlogPostInstance(ModelResource):
- """Represents a single Blog Post."""
- allowed_operations = ('read', 'update', 'delete')
- model = BlogPost
\ No newline at end of file