aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'djangorestframework/views.py')
-rw-r--r--djangorestframework/views.py204
1 files changed, 154 insertions, 50 deletions
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index d50e126f..5b3cc855 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -1,66 +1,170 @@
-from django.contrib.auth.views import *
-#from django.contrib.sites.models import get_current_site
-from django.conf import settings
+"""
+The :mod:`views` module provides the Views you will most probably
+be subclassing in your implementation.
+
+By setting or modifying class attributes on your view, you change it's predefined behaviour.
+"""
+
+from django.core.urlresolvers import set_script_prefix
from django.http import HttpResponse
-import base64
+from django.views.decorators.csrf import csrf_exempt
+
+from djangorestframework.compat import View as DjangoView
+from djangorestframework.response import Response, ErrorResponse
+from djangorestframework.mixins import *
+from djangorestframework import resources, renderers, parsers, authentication, permissions, status
+
+
+__all__ = (
+ 'View',
+ 'ModelView',
+ 'InstanceModelView',
+ 'ListModelView',
+ 'ListOrCreateModelView'
+)
+
+
+
+class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
+ """
+ Handles incoming requests and maps them to REST operations.
+ Performs request deserialization, response serialization, authentication and input validation.
+ """
+
+ """
+ The resource to use when validating requests and filtering responses,
+ or `None` to use default behaviour.
+ """
+ resource = None
+
+ """
+ List of renderers the resource can serialize the response with, ordered by preference.
+ """
+ renderers = ( renderers.JSONRenderer,
+ renderers.DocumentingHTMLRenderer,
+ renderers.DocumentingXHTMLRenderer,
+ renderers.DocumentingPlainTextRenderer,
+ renderers.XMLRenderer )
+
+ """
+ List of parsers the resource can parse the request with.
+ """
+ parsers = ( parsers.JSONParser,
+ parsers.FormParser,
+ parsers.MultiPartParser )
+
+ """
+ List of all authenticating methods to attempt.
+ """
+ authentication = ( authentication.UserLoggedInAuthenticaton,
+ authentication.BasicAuthenticaton )
+
+ """
+ List of all permissions that must be checked.
+ """
+ permissions = ( permissions.FullAnonAccess, )
+
+
+ @classmethod
+ def as_view(cls, **initkwargs):
+ """
+ Override the default :meth:`as_view` to store an instance of the view
+ as an attribute on the callable function. This allows us to discover
+ information about the view when we do URL reverse lookups.
+ """
+ view = super(View, cls).as_view(**initkwargs)
+ view.cls_instance = cls(**initkwargs)
+ return view
+
+
+ @property
+ def allowed_methods(self):
+ """
+ Return the list of allowed HTTP methods, uppercased.
+ """
+ return [method.upper() for method in self.http_method_names if hasattr(self, method)]
+
+
+ def http_method_not_allowed(self, request, *args, **kwargs):
+ """
+ Return an HTTP 405 error if an operation is called which does not have a handler method.
+ """
+ raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
+ {'detail': 'Method \'%s\' not allowed on this resource.' % self.method})
-def deny_robots(request):
- return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain')
-def favicon(request):
- data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA='
- return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon')
+ def initial(self, request, *args, **kargs):
+ """
+ Hook for any code that needs to run prior to anything else.
+ Required if you want to do things like set `request.upload_handlers` before
+ the authentication and dispatch handling is run.
+ """
+ pass
-# BLERGH
-# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
-# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
-# be making settings changes in order to accomodate django-rest-framework
-@csrf_protect
-@never_cache
-def api_login(request, template_name='api_login.html',
- redirect_field_name=REDIRECT_FIELD_NAME,
- authentication_form=AuthenticationForm):
- """Displays the login form and handles the login action."""
+ # Note: session based authentication is explicitly CSRF validated,
+ # all other authentication is CSRF exempt.
+ @csrf_exempt
+ def dispatch(self, request, *args, **kwargs):
+ self.request = request
+ self.args = args
+ self.kwargs = kwargs
- redirect_to = request.REQUEST.get(redirect_field_name, '')
+ # Calls to 'reverse' will not be fully qualified unless we set the scheme/host/port here.
+ prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
+ set_script_prefix(prefix)
- if request.method == "POST":
- form = authentication_form(data=request.POST)
- if form.is_valid():
- # Light security check -- make sure redirect_to isn't garbage.
- if not redirect_to or ' ' in redirect_to:
- redirect_to = settings.LOGIN_REDIRECT_URL
+ try:
+ self.initial(request, *args, **kwargs)
+
+ # Authenticate and check request has the relevant permissions
+ self._check_permissions()
- # Heavier security check -- redirects to http://example.com should
- # not be allowed, but things like /view/?param=http://example.com
- # should be allowed. This regex checks if there is a '//' *before* a
- # question mark.
- elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
- redirect_to = settings.LOGIN_REDIRECT_URL
+ # Get the appropriate handler method
+ if self.method.lower() in self.http_method_names:
+ handler = getattr(self, self.method.lower(), self.http_method_not_allowed)
+ else:
+ handler = self.http_method_not_allowed
- # Okay, security checks complete. Log the user in.
- auth_login(request, form.get_user())
+ response_obj = handler(request, *args, **kwargs)
- if request.session.test_cookie_worked():
- request.session.delete_test_cookie()
+ # Allow return value to be either HttpResponse, Response, or an object, or None
+ if isinstance(response_obj, HttpResponse):
+ return response_obj
+ elif isinstance(response_obj, Response):
+ response = response_obj
+ elif response_obj is not None:
+ response = Response(status.HTTP_200_OK, response_obj)
+ else:
+ response = Response(status.HTTP_204_NO_CONTENT)
- return HttpResponseRedirect(redirect_to)
+ # Pre-serialize filtering (eg filter complex objects into natively serializable types)
+ response.cleaned_content = self.filter_response(response.raw_content)
+
+ except ErrorResponse, exc:
+ response = exc.response
+
+ # Always add these headers.
+ #
+ # TODO - this isn't actually the correct way to set the vary header,
+ # also it's currently sub-obtimal for HTTP caching - need to sort that out.
+ response.headers['Allow'] = ', '.join(self.allowed_methods)
+ response.headers['Vary'] = 'Authenticate, Accept'
- else:
- form = authentication_form(request)
+ return self.render(response)
- request.session.set_test_cookie()
- #current_site = get_current_site(request)
+class ModelView(View):
+ """A RESTful view that maps to a model in the database."""
+ resource = resources.ModelResource
- return render_to_response(template_name, {
- 'form': form,
- redirect_field_name: redirect_to,
- #'site': current_site,
- #'site_name': current_site.name,
- 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
- }, context_instance=RequestContext(request))
+class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
+ """A view which provides default operations for read/update/delete against a model instance."""
+ _suffix = 'Instance'
+class ListModelView(ListModelMixin, ModelView):
+ """A view which provides default operations for list, against a model in the database."""
+ _suffix = 'List'
-def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME):
- return logout(request, next_page, template_name, redirect_field_name)
+class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
+ """A view which provides default operations for list and create, against a model in the database."""
+ _suffix = 'List'