changed implementation - Mixin Style

changed naming of class attributes to be more explicit
This commit is contained in:
Ludwig Kraatz 2012-12-08 11:54:36 +01:00
parent a873fef36b
commit b98bff30f9
5 changed files with 100 additions and 78 deletions

View File

@ -213,3 +213,23 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) return self.destroy(request, *args, **kwargs)
class RedirectAPIView(mixins.RedirectMixin,
GenericAPIView):
"""
Redirect View used to redirect requests to a different/moved endpoint
"""
def get(self, request, *args, **kwargs):
return self.redirect(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.redirect(request, *args, **kwargs)
def options(self, request, *args, **kwargs):
return self.redirect(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.redirect(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.redirect(request, *args, **kwargs)

View File

@ -7,6 +7,7 @@ which allows mixin classes to be composed in interesting ways.
from django.http import Http404 from django.http import Http404
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.reverse import reverse
class CreateModelMixin(object): class CreateModelMixin(object):
@ -123,3 +124,42 @@ class DestroyModelMixin(object):
self.object = self.get_object() self.object = self.get_object()
self.object.delete() self.object.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class RedirectMixin(object):
"""
A Mixin that provides a redirect method,
redirecting to a different resource endpoint
"""
redirect_permanent = True
redirect_with_query_string = True
redirect_to_view_name = None
redirect_to_url = None
def get_redirect_url(self, request, *args, **kwargs):
"""
Returning where to redirect to.
To url, view-name or nowhere
"""
if self.redirect_to_url:
url = self.redirect_to_url % kwargs
else:
try:
url = reverse(self.redirect_view_name, args=args, kwargs=kwargs, request=request)
except:
return None
query_string = self.request.META.get('QUERY_STRING', '')
if query_string and self.redirect_with_query_string:
url = '%(url)s?%(query_string)s' % {'url': url, 'query_string': query_string}
return url
def redirect(self, request, *args, **kwargs):
url = self.get_redirect_url(request, *args, **kwargs)
if url:
headers = {'Location': url}
if self.redirect_permanent:
return Response(status=status.HTTP_301_MOVED_PERMANENTLY, headers=headers)
else:
return Response(status=status.HTTP_302_FOUND, headers=headers)
else:
return Response(status=status.HTTP_410_GONE)

View File

@ -1,4 +1,5 @@
from django.test import TestCase from django.test import TestCase
from django.conf.urls.defaults import patterns, url
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.utils import simplejson as json from django.utils import simplejson as json
from rest_framework import generics, serializers, status from rest_framework import generics, serializers, status
@ -14,13 +15,19 @@ class RootView(generics.ListCreateAPIView):
""" """
model = BasicModel model = BasicModel
class InstanceView(generics.RetrieveUpdateDestroyAPIView): class InstanceView(generics.RetrieveUpdateDestroyAPIView):
""" """
Example description for OPTIONS. Example description for OPTIONS.
""" """
model = BasicModel model = BasicModel
class RedirectToRootView(generics.RedirectAPIView):
redirect_permanent = False
redirect_view_name = 'root-view'
class RedirectToURL(generics.RedirectAPIView):
redirect_to_url = 'http://foo.bar'
redirect_with_query_string = False
class SlugSerializer(serializers.ModelSerializer): class SlugSerializer(serializers.ModelSerializer):
slug = serializers.Field() # read only slug = serializers.Field() # read only
@ -38,6 +45,10 @@ class SlugBasedInstanceView(InstanceView):
serializer_class = SlugSerializer serializer_class = SlugSerializer
urlpatterns = patterns('',
url(r'^root/$', RootView.as_view(), name='root-view'),
)
class TestRootView(TestCase): class TestRootView(TestCase):
def setUp(self): def setUp(self):
""" """
@ -301,3 +312,28 @@ class TestCreateModelWithAutoNowAddField(TestCase):
self.assertEquals(response.status_code, status.HTTP_201_CREATED) self.assertEquals(response.status_code, status.HTTP_201_CREATED)
created = self.objects.get(id=1) created = self.objects.get(id=1)
self.assertEquals(created.content, 'foobar') self.assertEquals(created.content, 'foobar')
class RedirectViewTests(TestCase):
urls = 'rest_framework.tests.generics'
def test_redirect(self):
request = factory.get('/redirect_to_root/?foo=bar')
response = RedirectToRootView.as_view()(request)
self.assertEquals(response.status_code, status.HTTP_302_FOUND)
self.assertEquals(response['Location'], 'http://testserver/root/?foo=bar')
def test_redirect_to_nowhere(self):
request = factory.get('/redirect_to_nowhere/')
response = generics.RedirectAPIView.as_view()(request)
self.assertEquals(response.status_code, status.HTTP_410_GONE)
def test_redirect_to_url(self):
request = factory.get('/redirect_to_url/?foo=bar')
response = RedirectToURL.as_view()(request)
self.assertEquals(response.status_code, status.HTTP_301_MOVED_PERMANENTLY)
self.assertEquals(response['Location'], 'http://foo.bar')

View File

@ -1,12 +1,11 @@
import copy import copy
from django.conf.urls.defaults import patterns, url
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.views import APIView, RedirectAPIView from rest_framework.views import APIView
factory = RequestFactory() factory = RequestFactory()
@ -18,16 +17,6 @@ class BasicView(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
return Response({'method': 'POST', 'data': request.DATA}) return Response({'method': 'POST', 'data': request.DATA})
class RedirectBasicView(RedirectAPIView):
permanent = False
view_name = 'basic-view'
urlpatterns = patterns('',
url(r'^basic/$', BasicView.as_view(), name='basic-view'),
url(r'^redirect_to_basic/$', RedirectBasicView.as_view(), name='old-basic-view'),
)
@api_view(['GET', 'POST', 'PUT']) @api_view(['GET', 'POST', 'PUT'])
def basic_view(request): def basic_view(request):
@ -105,17 +94,4 @@ class FunctionBasedViewIntegrationTests(TestCase):
'detail': u'JSON parse error - No JSON object could be decoded' 'detail': u'JSON parse error - No JSON object could be decoded'
} }
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(sanitise_json_error(response.data), expected) self.assertEquals(sanitise_json_error(response.data), expected)
class RedirectViewTests(TestCase):
urls = 'rest_framework.tests.views'
def setUp(self):
self.view = RedirectBasicView.as_view()
def test_redirect(self):
request = factory.get('/redirect_to_basic/?foo=bar', content_type='application/json')
response = self.view(request)
self.assertEquals(response.status_code, status.HTTP_302_FOUND)
self.assertEquals(response['Location'], 'http://testserver/basic/?foo=bar')

View File

@ -13,7 +13,6 @@ from rest_framework.compat import View, apply_markdown
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.reverse import reverse
def _remove_trailing_string(content, trailing): def _remove_trailing_string(content, trailing):
@ -372,53 +371,4 @@ class APIView(View):
We may as well implement this as Django will otherwise provide We may as well implement this as Django will otherwise provide
a less useful default implementation. a less useful default implementation.
""" """
return Response(self.metadata(request), status=status.HTTP_200_OK) return Response(self.metadata(request), status=status.HTTP_200_OK)
class RedirectAPIView(APIView):
"""
A view that provides a redirect to a different resource endpoint
"""
permanent = True
view_name = None
def get_redirect_url(self, request, *args, **kwargs):
"""
Return the URL redirect to. Arguments and Keyword arguments from the
URL pattern match generating the redirect request
are provided as kwargs to this method.
"""
try:
url = reverse(self.view_name, args=args, kwargs=kwargs, request=request)
except:
return None
query_string = self.request.META.get('QUERY_STRING', '')
if query_string:
url = '%(url)s?%(query_string)s' % {'url': url, 'query_string': query_string}
return url
def get(self, request, *args, **kwargs):
url = self.get_redirect_url(request, *args, **kwargs)
if url:
headers = {'Location': url}
if self.permanent:
return Response(status=status.HTTP_301_MOVED_PERMANENTLY, headers=headers)
else:
return Response(status=status.HTTP_302_FOUND, headers=headers)
else:
return Response(status=status.HTTP_410_GONE)
def head(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def options(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)