Commit f144b769 by Tom Christie

Lots of good form validation and default actions

parent 48c7171a
......@@ -4,12 +4,13 @@ import json
from utils import dict2xml
class BaseEmitter(object):
def __init__(self, resource, request, status, headers):
def __init__(self, resource, request, status, headers, form):
self.request = request
self.resource = resource
self.status = status
self.headers = headers
self.form = form
def emit(self, output):
return output
......@@ -26,14 +27,13 @@ class TemplatedEmitter(BaseEmitter):
'headers': self.headers,
'resource_name': self.resource.__class__.__name__,
'resource_doc': self.resource.__doc__,
'create_form': self.resource.create_form and self.resource.create_form() or None,
'update_form': self.resource.update_form and self.resource.update_form() or None,
'allowed_methods': self.resource.allowed_methods,
'create_form': self.form,
'update_form': self.form,
'request': self.request,
'resource': self.resource,
})
return template.render(context)
class JSONEmitter(BaseEmitter):
def emit(self, output):
return json.dumps(output)
......
......@@ -17,6 +17,44 @@ 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, request):
if 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(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'
#
self.data = {}
for (key, val) in request.POST.items():
if key not in resource.RESERVED_PARAMS:
self.data[key] = val
def parse(self, input):
return self.request.POST
return self.data
......@@ -12,17 +12,17 @@
<h1>{{ resource_name }}</h1>
<p>{{ resource_doc }}</p>
<pre>{% autoescape off %}<b>{{ status }} {{ reason }}</b>
{% for key, val in headers.items %}<b>{{ key }}:</b> {{ val }}
{% for key, val in headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
{% endfor %}
{{ content|urlize_quoted_links }}{% endautoescape %} </pre>
{% if 'GET' in allowed_methods %}
{% if 'read' in resource.allowed_operations %}
<div class='action'>
<a href='{{ request.path }}'>Read</a>
</div>
{% endif %}
{% if 'POST' in resource.allowed_methods %}
{% if 'create' in resource.allowed_operations %}
<div class='action'>
<form action="{{ request.path }}" method="POST">
{% csrf_token %}
......@@ -32,7 +32,7 @@
</div>
{% endif %}
{% if 'PUT' in resource.allowed_methods %}
{% if 'update' in resource.allowed_operations %}
<div class='action'>
<form action="{{ request.path }}" method="POST">
<input type="hidden" name="{{ resource.METHOD_PARAM}}" value="PUT" />
......@@ -43,7 +43,7 @@
</div>
{% endif %}
{% if 'DELETE' in resource.allowed_methods %}
{% if 'delete' in resource.allowed_operations %}
<div class='action'>
<form action="{{ request.path }}" method="POST">
{% csrf_token %}
......
from django.db import models
import uuid
# Create your models here.
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)
\ No newline at end of file
......@@ -9,7 +9,8 @@ from django.test import TestCase
from django.core.urlresolvers import reverse
from testapp import views
import json
from rest.utils import xml2dict, dict2xml
#from rest.utils import xml2dict, dict2xml
class AcceptHeaderTests(TestCase):
def assert_accept_mimetype(self, mimetype, expect=None, expect_match=True):
......@@ -45,6 +46,10 @@ class AcceptHeaderTests(TestCase):
def test_invalid_accept_header_returns_406(self):
resp = self.client.get(reverse(views.ReadOnlyResource), HTTP_ACCEPT='invalid/invalid')
self.assertEquals(resp.status_code, 406)
def test_prefer_specific(self):
self.fail("Test not implemented")
class AllowedMethodsTests(TestCase):
def test_reading_read_only_allowed(self):
......@@ -63,6 +68,7 @@ class AllowedMethodsTests(TestCase):
resp = self.client.put(reverse(views.WriteOnlyResource), {})
self.assertEquals(resp.status_code, 200)
class EncodeDecodeTests(TestCase):
def setUp(self):
super(self.__class__, self).setUp()
......@@ -70,36 +76,71 @@ class EncodeDecodeTests(TestCase):
def test_encode_form_decode_json(self):
content = self.input
resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/json')
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', HTTP_ACCEPT='application/json')
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')
#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(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)
self.assertEquals(resp.status_code, 201)
self.assertEquals(output['name'], 'example')
self.assertEquals(set(output.keys()), set(('absolute_uri', 'name', 'key')))
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)
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_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)
\ No newline at end of file
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)
\ No newline at end of file
......@@ -5,4 +5,7 @@ urlpatterns = patterns('testapp.views',
(r'^read-only$', 'ReadOnlyResource'),
(r'^write-only$', 'WriteOnlyResource'),
(r'^read-write$', 'ReadWriteResource'),
(r'^model$', 'ModelFormResource'),
(r'^container$', 'ContainerFactory'),
(r'^container/((?P<key>[^/]+))$', 'ContainerInstance'),
)
from rest.resource import Resource
from rest.resource import Resource, ModelResource
from testapp.forms import ExampleForm
from testapp.models import ExampleModel, ExampleContainer
class RootResource(Resource):
"""This is my docstring
"""
allowed_methods = ('GET',)
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)}, {})
'read-write-api': self.reverse(ReadWriteResource),
'model-api': self.reverse(ModelFormResource),
'create-container': self.reverse(ContainerFactory)}, {})
class ReadOnlyResource(Resource):
"""This is my docstring
"""
allowed_methods = ('GET',)
allowed_operations = ('read',)
def read(self, headers={}, *args, **kwargs):
return (200, {'ExampleString': 'Example',
......@@ -26,13 +29,35 @@ class ReadOnlyResource(Resource):
class WriteOnlyResource(Resource):
"""This is my docstring
"""
allowed_methods = ('PUT',)
allowed_operations = ('update',)
def update(self, data, headers={}, *args, **kwargs):
return (200, data, {})
class ReadWriteResource(Resource):
allowed_methods = ('GET', 'PUT', 'DELETE')
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',)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment