diff --git a/rest_framework/generics.py b/rest_framework/generics.py index dd8dfcf8d..69ae53ae9 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -213,3 +213,23 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, def delete(self, 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) \ No newline at end of file diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 1edcfa5c9..69ba1b63b 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -7,6 +7,7 @@ which allows mixin classes to be composed in interesting ways. from django.http import Http404 from rest_framework import status from rest_framework.response import Response +from rest_framework.reverse import reverse class CreateModelMixin(object): @@ -123,3 +124,42 @@ class DestroyModelMixin(object): self.object = self.get_object() self.object.delete() 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) diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index a8279ef2b..760ed0047 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -1,4 +1,5 @@ from django.test import TestCase +from django.conf.urls.defaults import patterns, url from django.test.client import RequestFactory from django.utils import simplejson as json from rest_framework import generics, serializers, status @@ -14,13 +15,19 @@ class RootView(generics.ListCreateAPIView): """ model = BasicModel - class InstanceView(generics.RetrieveUpdateDestroyAPIView): """ Example description for OPTIONS. """ 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): slug = serializers.Field() # read only @@ -38,6 +45,10 @@ class SlugBasedInstanceView(InstanceView): serializer_class = SlugSerializer +urlpatterns = patterns('', + url(r'^root/$', RootView.as_view(), name='root-view'), +) + class TestRootView(TestCase): def setUp(self): """ @@ -301,3 +312,28 @@ class TestCreateModelWithAutoNowAddField(TestCase): self.assertEquals(response.status_code, status.HTTP_201_CREATED) created = self.objects.get(id=1) 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') + + \ No newline at end of file