diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/blogpost/models.py | 3 | ||||
| -rw-r--r-- | examples/blogpost/resources.py | 27 | ||||
| -rw-r--r-- | examples/blogpost/tests.py | 164 | ||||
| -rw-r--r-- | examples/blogpost/urls.py | 29 | ||||
| -rw-r--r-- | examples/modelresourceexample/resources.py | 7 | ||||
| -rw-r--r-- | examples/modelresourceexample/urls.py | 10 | ||||
| -rw-r--r-- | examples/permissionsexample/__init__.py (renamed from examples/modelresourceexample/views.py) | 0 | ||||
| -rw-r--r-- | examples/permissionsexample/urls.py | 6 | ||||
| -rw-r--r-- | examples/permissionsexample/views.py | 20 | ||||
| -rw-r--r-- | examples/pygments_api/views.py | 11 | ||||
| -rw-r--r-- | examples/resourceexample/urls.py | 6 | ||||
| -rw-r--r-- | examples/resourceexample/views.py | 33 | ||||
| -rw-r--r-- | examples/sandbox/views.py | 4 | ||||
| -rw-r--r-- | examples/urls.py | 1 |
14 files changed, 177 insertions, 144 deletions
diff --git a/examples/blogpost/models.py b/examples/blogpost/models.py index c4925a15..d77f530d 100644 --- a/examples/blogpost/models.py +++ b/examples/blogpost/models.py @@ -22,6 +22,9 @@ class BlogPost(models.Model): slug = models.SlugField(editable=False, default='') def save(self, *args, **kwargs): + """ + For the purposes of the sandbox, limit the maximum number of stored models. + """ self.slug = slugify(self.title) super(self.__class__, self).save(*args, **kwargs) for obj in self.__class__.objects.order_by('-created')[MAX_POSTS:]: diff --git a/examples/blogpost/resources.py b/examples/blogpost/resources.py new file mode 100644 index 00000000..9b91ed73 --- /dev/null +++ b/examples/blogpost/resources.py @@ -0,0 +1,27 @@ +from django.core.urlresolvers import reverse +from djangorestframework.resources import ModelResource +from blogpost.models import BlogPost, Comment + + +class BlogPostResource(ModelResource): + """ + A Blog Post has a *title* and *content*, and can be associated with zero or more comments. + """ + model = BlogPost + fields = ('created', 'title', 'slug', 'content', 'url', 'comments') + ordering = ('-created',) + + def comments(self, instance): + return reverse('comments', kwargs={'blogpost': instance.key}) + + +class CommentResource(ModelResource): + """ + A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*. + """ + model = Comment + fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') + ordering = ('-created',) + + def blogpost(self, instance): + return reverse('blog-post', kwargs={'key': instance.blogpost.key})
\ No newline at end of file diff --git a/examples/blogpost/tests.py b/examples/blogpost/tests.py index 9b9a682f..e55f0f90 100644 --- a/examples/blogpost/tests.py +++ b/examples/blogpost/tests.py @@ -7,79 +7,80 @@ from django.core.urlresolvers import reverse from django.utils import simplejson as json from djangorestframework.compat import RequestFactory - -from blogpost import views, models -import blogpost - - -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 dont_test_accept_json(self): - """Ensure server responds with Content-Type of JSON when requested.""" - self.assert_accept_mimetype('application/json') - - def dont_test_accept_xml(self): - """Ensure server responds with Content-Type of XML when requested.""" - self.assert_accept_mimetype('application/xml') - - def dont_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 dont_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/json;q=0.1, application/xml;q=0.9', expect='application/xml') - - def dont_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 dont_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 dont_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 dont_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 dont_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') - 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 dont_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 dont_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) +from djangorestframework.views import InstanceModelView, ListOrCreateModelView + +from blogpost import models, urls +#import blogpost + + +# 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 dont_test_accept_json(self): +# """Ensure server responds with Content-Type of JSON when requested.""" +# self.assert_accept_mimetype('application/json') +# +# def dont_test_accept_xml(self): +# """Ensure server responds with Content-Type of XML when requested.""" +# self.assert_accept_mimetype('application/xml') +# +# def dont_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 dont_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/json;q=0.1, application/xml;q=0.9', expect='application/xml') +# +# def dont_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 dont_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 dont_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 dont_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 dont_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') +# 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 dont_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 dont_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)) @@ -178,32 +179,33 @@ class TestRotation(TestCase): models.BlogPost.objects.all().delete() def test_get_to_root(self): - '''Simple test to demonstrate how the requestfactory needs to be used''' + '''Simple get to the *root* url of blogposts''' request = self.factory.get('/blog-post') - view = views.BlogPosts.as_view() + view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) response = view(request) self.assertEqual(response.status_code, 200) def test_blogposts_not_exceed_MAX_POSTS(self): '''Posting blog-posts should not result in more than MAX_POSTS items stored.''' - for post in range(views.MAX_POSTS + 5): + for post in range(models.MAX_POSTS + 5): form_data = {'title': 'This is post #%s' % post, 'content': 'This is the content of post #%s' % post} request = self.factory.post('/blog-post', data=form_data) - view = views.BlogPosts.as_view() + view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) view(request) - self.assertEquals(len(models.BlogPost.objects.all()),views.MAX_POSTS) + self.assertEquals(len(models.BlogPost.objects.all()),models.MAX_POSTS) def test_fifo_behaviour(self): '''It's fine that the Blogposts are capped off at MAX_POSTS. But we want to make sure we see FIFO behaviour.''' for post in range(15): form_data = {'title': '%s' % post, 'content': 'This is the content of post #%s' % post} request = self.factory.post('/blog-post', data=form_data) - view = views.BlogPosts.as_view() + view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) view(request) request = self.factory.get('/blog-post') - view = views.BlogPosts.as_view() + view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource) response = view(request) response_posts = json.loads(response.content) response_titles = [d['title'] for d in response_posts] - self.assertEquals(response_titles, ['%s' % i for i in range(views.MAX_POSTS - 5, views.MAX_POSTS + 5)]) + response_titles.reverse() + self.assertEquals(response_titles, ['%s' % i for i in range(models.MAX_POSTS - 5, models.MAX_POSTS + 5)])
\ No newline at end of file diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py index c677b8fa..e9bd2754 100644 --- a/examples/blogpost/urls.py +++ b/examples/blogpost/urls.py @@ -1,36 +1,11 @@ from django.conf.urls.defaults import patterns, url -from django.core.urlresolvers import reverse - from djangorestframework.views import ListOrCreateModelView, InstanceModelView -from djangorestframework.resources import ModelResource - -from blogpost.models import BlogPost, Comment - - -class BlogPostResource(ModelResource): - """ - A Blog Post has a *title* and *content*, and can be associated with zero or more comments. - """ - model = BlogPost - fields = ('created', 'title', 'slug', 'content', 'url', 'comments') - ordering = ('-created',) - - def comments(self, instance): - return reverse('comments', kwargs={'blogpost': instance.key}) - - -class CommentResource(ModelResource): - """ - A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*. - """ - model = Comment - fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost') - ordering = ('-created',) +from blogpost.resources import BlogPostResource, CommentResource urlpatterns = patterns('', url(r'^$', ListOrCreateModelView.as_view(resource=BlogPostResource), name='blog-posts-root'), - url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource)), + url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource), name='blog-post'), url(r'^(?P<blogpost>[^/]+)/comments/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'), url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)), ) diff --git a/examples/modelresourceexample/resources.py b/examples/modelresourceexample/resources.py new file mode 100644 index 00000000..634ea6b3 --- /dev/null +++ b/examples/modelresourceexample/resources.py @@ -0,0 +1,7 @@ +from djangorestframework.resources import ModelResource +from modelresourceexample.models import MyModel + +class MyModelResource(ModelResource): + model = MyModel + fields = ('foo', 'bar', 'baz', 'url') + ordering = ('created',) diff --git a/examples/modelresourceexample/urls.py b/examples/modelresourceexample/urls.py index bb71ddd3..b6a16542 100644 --- a/examples/modelresourceexample/urls.py +++ b/examples/modelresourceexample/urls.py @@ -1,14 +1,8 @@ from django.conf.urls.defaults import patterns, url from djangorestframework.views import ListOrCreateModelView, InstanceModelView -from djangorestframework.resources import ModelResource -from modelresourceexample.models import MyModel - -class MyModelResource(ModelResource): - model = MyModel - fields = ('foo', 'bar', 'baz', 'url') - ordering = ('created',) +from modelresourceexample.resources import MyModelResource urlpatterns = patterns('', url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'), - url(r'^([0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)), + url(r'^(?P<pk>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)), ) diff --git a/examples/modelresourceexample/views.py b/examples/permissionsexample/__init__.py index e69de29b..e69de29b 100644 --- a/examples/modelresourceexample/views.py +++ b/examples/permissionsexample/__init__.py diff --git a/examples/permissionsexample/urls.py b/examples/permissionsexample/urls.py new file mode 100644 index 00000000..d17f5159 --- /dev/null +++ b/examples/permissionsexample/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import patterns, url +from permissionsexample.views import ThrottlingExampleView + +urlpatterns = patterns('', + url(r'^$', ThrottlingExampleView.as_view(), name='throttled-resource'), +) diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py new file mode 100644 index 00000000..20e7cba7 --- /dev/null +++ b/examples/permissionsexample/views.py @@ -0,0 +1,20 @@ +from djangorestframework.views import View +from djangorestframework.permissions import PerUserThrottling + + +class ThrottlingExampleView(View): + """ + A basic read-only View that has a **per-user throttle** of 10 requests per minute. + + If a user exceeds the 10 requests limit within a period of one minute, the + throttle will be applied until 60 seconds have passed since the first request. + """ + + permissions = ( PerUserThrottling, ) + throttle = '10/min' + + def get(self, request): + """ + Handle GET requests. + """ + return "Successful response to GET request because throttle is not yet active."
\ No newline at end of file diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index 76647107..e50029f6 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -46,19 +46,12 @@ class HTMLRenderer(BaseRenderer): media_type = 'text/html' - -class PygmentsFormResource(FormResource): - """ - """ - form = PygmentsForm - - class PygmentsRoot(View): """ - This example demonstrates a simple RESTful Web API aound the awesome pygments library. + This example demonstrates a simple RESTful Web API around the awesome pygments library. This top level resource is used to create highlighted code snippets, and to list all the existing code snippets. """ - resource = PygmentsFormResource + form = PygmentsForm def get(self, request): """ diff --git a/examples/resourceexample/urls.py b/examples/resourceexample/urls.py index cb6435bb..6e141f3c 100644 --- a/examples/resourceexample/urls.py +++ b/examples/resourceexample/urls.py @@ -1,7 +1,7 @@ from django.conf.urls.defaults import patterns, url -from resourceexample.views import ExampleResource, AnotherExampleResource +from resourceexample.views import ExampleView, AnotherExampleView urlpatterns = patterns('', - url(r'^$', ExampleResource.as_view(), name='example-resource'), - url(r'^(?P<num>[0-9]+)/$', AnotherExampleResource.as_view(), name='another-example-resource'), + url(r'^$', ExampleView.as_view(), name='example-resource'), + url(r'^(?P<num>[0-9]+)/$', AnotherExampleView.as_view(), name='another-example'), ) diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py index 29651fbf..990c7834 100644 --- a/examples/resourceexample/views.py +++ b/examples/resourceexample/views.py @@ -1,42 +1,45 @@ from django.core.urlresolvers import reverse from djangorestframework.views import View -from djangorestframework.resources import FormResource from djangorestframework.response import Response from djangorestframework import status from resourceexample.forms import MyForm -class MyFormValidation(FormResource): - """ - A resource which applies form validation on the input. - """ - form = MyForm - -class ExampleResource(View): +class ExampleView(View): """ - A basic read-only resource that points to 3 other resources. + A basic read-only view that points to 3 other views. """ def get(self, request): - return {"Some other resources": [reverse('another-example-resource', kwargs={'num':num}) for num in range(3)]} + """ + Handle GET requests, returning a list of URLs pointing to 3 other views. + """ + return {"Some other resources": [reverse('another-example', kwargs={'num':num}) for num in range(3)]} -class AnotherExampleResource(View): +class AnotherExampleView(View): """ - A basic GET-able/POST-able resource. + A basic view, that can be handle GET and POST requests. + Applies some simple form validation on POST requests. """ - resource = MyFormValidation + form = MyForm def get(self, request, num): - """Handle GET requests""" + """ + Handle GET requests. + Returns a simple string indicating which view the GET request was for. + """ if int(num) > 2: return Response(status.HTTP_404_NOT_FOUND) return "GET request to AnotherExampleResource %s" % num def post(self, request, num): - """Handle POST requests""" + """ + Handle POST requests, with form validation. + Returns a simple string indicating what content was supplied. + """ if int(num) > 2: return Response(status.HTTP_404_NOT_FOUND) return "POST request to AnotherExampleResource %s, with content: %s" % (num, repr(self.CONTENT)) diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py index 1c55c28f..1e326f43 100644 --- a/examples/sandbox/views.py +++ b/examples/sandbox/views.py @@ -31,4 +31,6 @@ class Sandbox(View): {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')}, {'name': 'Object store API', 'url': reverse('object-store-root')}, {'name': 'Code highlighting API', 'url': reverse('pygments-root')}, - {'name': 'Blog posts API', 'url': reverse('blog-posts-root')}] + {'name': 'Blog posts API', 'url': reverse('blog-posts-root')}, + {'name': 'Permissions example', 'url': reverse('throttled-resource')} + ] diff --git a/examples/urls.py b/examples/urls.py index cf4d4042..08d97a14 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('', (r'^object-store/', include('objectstore.urls')), (r'^pygments/', include('pygments_api.urls')), (r'^blog-post/', include('blogpost.urls')), + (r'^permissions-example/', include('permissionsexample.urls')), (r'^', include('djangorestframework.urls')), ) |
