diff --git a/docs/index.md b/docs/index.md
index 4abfba587..87ef0e9e3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -95,7 +95,7 @@ The following packages are optional:
* [coreapi][coreapi] (1.32.0+) - Schema generation support.
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
-* [django-filter][django-filter] (0.9.2+) - Filtering support.
+* [django-filter][django-filter] (1.0.1+) - Filtering support.
* [django-crispy-forms][django-crispy-forms] - Improved HTML display for filtering.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md
index 9be3a823e..4f655e3a5 100644
--- a/docs/topics/documenting-your-api.md
+++ b/docs/topics/documenting-your-api.md
@@ -207,4 +207,4 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[image-django-rest-swagger]: ../img/django-rest-swagger.png
[image-apiary]: ../img/apiary.png
[image-self-describing-api]: ../img/self-describing.png
-[schemas-examples]: api-guide/schemas/#examples
+[schemas-examples]: ../api-guide/schemas/#examples
diff --git a/docs/topics/tutorials-and-resources.md b/docs/topics/tutorials-and-resources.md
index 3bbe52e44..3fb1ec258 100644
--- a/docs/topics/tutorials-and-resources.md
+++ b/docs/topics/tutorials-and-resources.md
@@ -64,7 +64,7 @@ There are a wide range of resources available for learning and using Django REST
* [Classy Django REST Framework][cdrf.co]
* [DRF-schema-adapter][drf-schema]
-Want your Django REST Framework talk/tutorial/article to be added to our website? Or know of a resource that's not yet included here? Please [submit a pull request][submit-pr] or [email us][mailto:anna@django-rest-framework.org]!
+Want your Django REST Framework talk/tutorial/article to be added to our website? Or know of a resource that's not yet included here? Please [submit a pull request][submit-pr] or [email us][anna-email]!
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
@@ -107,3 +107,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[drf-tutorial]: https://tests4geeks.com/django-rest-framework-tutorial/
[building-a-restful-api-with-drf]: http://agiliq.com/blog/2014/12/building-a-restful-api-with-django-rest-framework/
[submit-pr]: https://github.com/tomchristie/django-rest-framework
+[anna-email]: mailto:anna@django-rest-framework.org
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 710dfb8e7..e7728e84c 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -216,26 +216,15 @@ It's important to remember that `ModelSerializer` classes don't do anything part
Let's see how we can write some API views using our new Serializer class.
For the moment we won't use any of REST framework's other features, we'll just write the views as regular Django views.
-We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`.
-
Edit the `snippets/views.py` file, and add the following.
- from django.http import HttpResponse
+ from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
- class JSONResponse(HttpResponse):
- """
- An HttpResponse that renders its content into JSON.
- """
- def __init__(self, data, **kwargs):
- content = JSONRenderer().render(data)
- kwargs['content_type'] = 'application/json'
- super(JSONResponse, self).__init__(content, **kwargs)
-
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
@csrf_exempt
@@ -246,15 +235,15 @@ The root of our API is going to be a view that supports listing all the existing
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
- return JSONResponse(serializer.data)
+ return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
- return JSONResponse(serializer.data, status=201)
- return JSONResponse(serializer.errors, status=400)
+ return JsonResponse(serializer.data, status=201)
+ return JsonResponse(serializer.errors, status=400)
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
@@ -272,15 +261,15 @@ We'll also need a view which corresponds to an individual snippet, and can be us
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
- return JSONResponse(serializer.data)
+ return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
- return JSONResponse(serializer.data)
- return JSONResponse(serializer.errors, status=400)
+ return JsonResponse(serializer.data)
+ return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index dfba0ecf0..f24775278 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
from django.http import Http404
+from rest_framework import exceptions
from rest_framework.compat import is_authenticated
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
@@ -107,6 +108,10 @@ class DjangoModelPermissions(BasePermission):
'app_label': model_cls._meta.app_label,
'model_name': model_cls._meta.model_name
}
+
+ if method not in self.perms_map:
+ raise exceptions.MethodNotAllowed(method)
+
return [perm % kwargs for perm in self.perms_map[method]]
def has_permission(self, request, view):
@@ -168,6 +173,10 @@ class DjangoObjectPermissions(DjangoModelPermissions):
'app_label': model_cls._meta.app_label,
'model_name': model_cls._meta.model_name
}
+
+ if method not in self.perms_map:
+ raise exceptions.MethodNotAllowed(method)
+
return [perm % kwargs for perm in self.perms_map[method]]
def has_object_permission(self, request, view, obj):
diff --git a/rest_framework/templates/rest_framework/admin/detail.html b/rest_framework/templates/rest_framework/admin/detail.html
index 759fa163d..42cd1a36d 100644
--- a/rest_framework/templates/rest_framework/admin/detail.html
+++ b/rest_framework/templates/rest_framework/admin/detail.html
@@ -1,7 +1,7 @@
{% load rest_framework %}
- {% for key, value in results.items %}
+ {% for key, value in results|items %}
{% if key in details %}
{{ key|capfirst }} | {{ value|format_value }} |
{% endif %}
diff --git a/rest_framework/templates/rest_framework/admin/dict_value.html b/rest_framework/templates/rest_framework/admin/dict_value.html
index 3392c901b..ef47b7228 100644
--- a/rest_framework/templates/rest_framework/admin/dict_value.html
+++ b/rest_framework/templates/rest_framework/admin/dict_value.html
@@ -1,10 +1,10 @@
{% load rest_framework %}
- {% for key, value in value.items %}
+ {% for k, v in value|items %}
- {{ key|format_value }} |
- {{ value|format_value }} |
+ {{ k|format_value }} |
+ {{ v|format_value }} |
{% endfor %}
diff --git a/rest_framework/templates/rest_framework/admin/list.html b/rest_framework/templates/rest_framework/admin/list.html
index 6044ebb38..fd394d44e 100644
--- a/rest_framework/templates/rest_framework/admin/list.html
+++ b/rest_framework/templates/rest_framework/admin/list.html
@@ -6,7 +6,7 @@
{% for row in results %}
- {% for key, value in row.items %}
+ {% for key, value in row|items %}
{% if key in columns %}
{{ value|format_value }}
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 4891f625f..a31c3ca94 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -255,6 +255,17 @@ def format_value(value):
return six.text_type(value)
+@register.filter
+def items(value):
+ """
+ Simple filter to return the items of the dict. Useful when the dict may
+ have a key 'items' which is resolved first in Django tempalte dot-notation
+ lookup. See issue #4931
+ Also see: https://stackoverflow.com/questions/15416662/django-template-loop-over-dictionary-items-with-items-as-key
+ """
+ return value.items()
+
+
@register.filter
def add_nested_class(value):
if isinstance(value, dict):
diff --git a/tests/test_permissions.py b/tests/test_permissions.py
index f8561e61d..cabf66883 100644
--- a/tests/test_permissions.py
+++ b/tests/test_permissions.py
@@ -200,6 +200,15 @@ class ModelPermissionsIntegrationTests(TestCase):
response = empty_list_view(request, pk=1)
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ def test_calling_method_not_allowed(self):
+ request = factory.generic('METHOD_NOT_ALLOWED', '/')
+ response = root_view(request)
+ self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ request = factory.generic('METHOD_NOT_ALLOWED', '/1')
+ response = instance_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+
class BasicPermModel(models.Model):
text = models.CharField(max_length=100)
@@ -384,6 +393,11 @@ class ObjectPermissionsIntegrationTests(TestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(response.data, [])
+ def test_cannot_method_not_allowed(self):
+ request = factory.generic('METHOD_NOT_ALLOWED', '/')
+ response = object_permissions_list_view(request)
+ self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+
class BasicPerm(permissions.BasePermission):
def has_permission(self, request, view):
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index eba5b8104..12ac2dfc8 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -648,3 +648,47 @@ class AdminRendererTests(TestCase):
assert result == ''
assert response.status_code == status.HTTP_303_SEE_OTHER
assert response['Location'] == 'http://example.com'
+
+ def test_render_dict(self):
+ factory = APIRequestFactory()
+
+ class DummyView(APIView):
+ renderer_classes = (AdminRenderer, )
+
+ def get(self, request):
+ return Response({'foo': 'a string'})
+ view = DummyView.as_view()
+ request = factory.get('/')
+ response = view(request)
+ response.render()
+ self.assertInHTML(' |
Foo | a string |
', str(response.content))
+
+ def test_render_dict_with_items_key(self):
+ factory = APIRequestFactory()
+
+ class DummyView(APIView):
+ renderer_classes = (AdminRenderer, )
+
+ def get(self, request):
+ return Response({'items': 'a string'})
+
+ view = DummyView.as_view()
+ request = factory.get('/')
+ response = view(request)
+ response.render()
+ self.assertInHTML('Items | a string |
', str(response.content))
+
+ def test_render_dict_with_iteritems_key(self):
+ factory = APIRequestFactory()
+
+ class DummyView(APIView):
+ renderer_classes = (AdminRenderer, )
+
+ def get(self, request):
+ return Response({'iteritems': 'a string'})
+
+ view = DummyView.as_view()
+ request = factory.get('/')
+ response = view(request)
+ response.render()
+ self.assertInHTML('Iteritems | a string |
', str(response.content))