From 92c015e0495b7cf39b0d0387fe6d376812a9ebef Mon Sep 17 00:00:00 2001 From: markotibold Date: Wed, 18 May 2011 22:13:48 +0200 Subject: [PATCH] Most of the actual work so far has been markup really. --- djangorestframework/authentication.py | 17 ++++----- djangorestframework/compat.py | 38 ++++++++++-------- djangorestframework/mixins.py | 55 +++++++++++++-------------- djangorestframework/parsers.py | 46 ++++++++++++---------- djangorestframework/permissions.py | 6 +-- djangorestframework/renderers.py | 10 ++--- 6 files changed, 92 insertions(+), 80 deletions(-) diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py index 1c5c832f3..19a7aa90d 100644 --- a/djangorestframework/authentication.py +++ b/djangorestframework/authentication.py @@ -4,7 +4,7 @@ The :mod:`authentication` module provides a set of pluggable authentication clas Authentication behavior is provided by mixing the :class:`mixins.AuthMixin` class into a :class:`View` class. The set of authentication methods which are used is then specified by setting the -:attr:`authentication` attribute on the :class:`View` class, and listing a set of authentication classes. +:attr:`authentication` attribute on the :class:`View` class, and listing a set of :class:`authentication` classes. """ from django.contrib.auth import authenticate @@ -26,20 +26,19 @@ class BaseAuthenticaton(object): def __init__(self, view): """ - :param view: :class:`Authentication` classes are always passed the current view on creation. + :class:`Authentication` classes are always passed the current view on creation. """ self.view = view def authenticate(self, request): """ - :param request: Request to be authenticated - :rtype: :obj:`User` or None [*]_ + Authenticate the :obj:`request` and return a :obj:`User` instance or :const:`None`. [*]_ - .. [*] The authentication context *will* typically be a :obj:`User`, + .. [*] The authentication context *will* typically be a :obj:`User` object, but it need not be. It can be any user-like object so long as the - permissions classes on the view can handle the object and use + permissions classes (see the :mod:`permissions` module) on the view can handle the object and use it to determine if the request has the required permissions or not. - + This can be an important distinction if you're implementing some token based authentication mechanism, where the authentication context may be more involved than simply mapping to a :obj:`User`. @@ -55,7 +54,7 @@ class BasicAuthenticaton(BaseAuthenticaton): def authenticate(self, request): """ Returns a :obj:`User` if a correct username and password have been supplied - using HTTP Basic authentication. Otherwise returns `None`. + using HTTP Basic authentication. Otherwise returns :const:`None`. """ from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError @@ -85,7 +84,7 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton): def authenticate(self, request): """ - Returns a :obj:`User` if the request session currently has a logged in user, otherwise `None`. + Returns a :obj:`User` object if the request session currently has a logged in user. Otherwise returns :const:`None`. """ # TODO: Switch this back to request.POST, and let FormParser/MultiPartParser deal with the consequences. if getattr(request, 'user', None) and request.user.is_active: diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index 98fbbb62c..45a695c8c 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -1,4 +1,6 @@ -"""Compatability module to provide support for backwards compatability with older versions of django/python""" +""" +Compatability module to provide support for backwards compatability with older versions of django/python +""" # cStringIO only if it's available try: @@ -27,24 +29,25 @@ except ImportError: # Lovely stuff class RequestFactory(Client): """ - Class that lets you create mock Request objects for use in testing. + Class that lets you create mock :obj:`Request` objects for use in testing. - Usage: + Usage:: - rf = RequestFactory() - get_request = rf.get('/hello/') - post_request = rf.post('/submit/', {'foo': 'bar'}) + rf = RequestFactory() + get_request = rf.get('/hello/') + post_request = rf.post('/submit/', {'foo': 'bar'}) - This class re-uses the django.test.client.Client interface, docs here: - http://www.djangoproject.com/documentation/testing/#the-test-client + This class re-uses the :class:`django.test.client.Client` interface. Of which + you can find the docs here__. - Once you have a request object you can pass it to any view function, - just as if that view had been hooked up using a URLconf. + __ http://www.djangoproject.com/documentation/testing/#the-test-client + Once you have a :obj:`request` object you can pass it to any :func:`view` function, + just as if that :func:`view` had been hooked up using a URLconf. """ def request(self, **request): """ - Similar to parent class, but returns the request object as soon as it + Similar to parent class, but returns the :obj:`request` object as soon as it has created it. """ environ = { @@ -148,9 +151,11 @@ try: import re class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor): - """Override markdown's SetextHeaderProcessor, so that ==== headers are

and ---- headers are

. + """ + Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are

and ---- headers are

. - We use

for the resource name.""" + We use

for the resource name. + """ # Detect Setext-style header. Must be first 2 lines of block. RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE) @@ -172,8 +177,11 @@ try: blocks.insert(0, '\n'.join(lines[2:])) def apply_markdown(text): - """Simple wrapper around markdown.markdown to apply our CustomSetextHeaderProcessor, - and also set the base level of '#' style headers to

.""" + """ + Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`, + and also set the base level of '#' style headers to

. + """ + extensions = ['headerid(level=2)'] safe_mode = False, output_format = markdown.DEFAULT_OUTPUT_FORMAT diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index e101b7883..524e9cce7 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -1,5 +1,6 @@ """ -The mixins module provides a set of reusable mixin classes that can be added to a ``View``. +The :mod:`mixins` module provides a set of reusable `mixin` +classes that can be added to a `View`. """ from django.contrib.auth.models import AnonymousUser @@ -41,7 +42,7 @@ __all__ = ( class RequestMixin(object): """ - Mixin class to provide request parsing behavior. + `Mixin` class to provide request parsing behavior. """ _USE_FORM_OVERLOADING = True @@ -52,7 +53,7 @@ class RequestMixin(object): """ The set of request parsers that the view can handle. - Should be a tuple/list of classes as described in the ``parsers`` module. + Should be a tuple/list of classes as described in the :mod:`parsers` module. """ parsers = () @@ -61,8 +62,8 @@ class RequestMixin(object): """ Returns the HTTP method. - This should be used instead of ``request.method``, as it allows the method - to be overridden by using a hidden form field on a form POST request. + This should be used instead of just reading :const:`request.method`, as it allows the `method` + to be overridden by using a hidden `form` field on a form POST request. """ if not hasattr(self, '_method'): self._load_method_and_content_type() @@ -100,7 +101,7 @@ class RequestMixin(object): def FILES(self): """ Parses the request body and returns the files. - Similar to request.FILES, except that it handles arbitrary parsers, + Similar to ``request.FILES``, except that it handles arbitrary parsers, and also works on methods other than POST (eg PUT). """ if not hasattr(self, '_files'): @@ -215,10 +216,10 @@ class RequestMixin(object): class ResponseMixin(object): """ - Adds behavior for pluggable Renderers to a :class:`.BaseView` or Django :class:`View`. class. + Adds behavior for pluggable `Renderers` to a :class:`views.BaseView` or Django :class:`View` class. Default behavior is to use standard HTTP Accept header content negotiation. - Also supports overriding the content type by specifying an _accept= parameter in the URL. + Also supports overriding the content type by specifying an ``_accept=`` parameter in the URL. Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead. """ @@ -228,7 +229,7 @@ class ResponseMixin(object): """ The set of response renderers that the view can handle. - Should be a tuple/list of classes as described in the ``renderers`` module. + Should be a tuple/list of classes as described in the :mod:`renderers` module. """ renderers = () @@ -237,7 +238,7 @@ class ResponseMixin(object): # out of the box with existing Django classes that use render_to_response. def render(self, response): """ - Takes a ``Response`` object and returns an ``HttpResponse``. + Takes a :obj:`Response` object and returns an :obj:`HttpResponse`. """ self.response = response @@ -354,21 +355,21 @@ class ResponseMixin(object): class AuthMixin(object): """ - Simple mixin class to add authentication and permission checking to a ``View`` class. + Simple :class:`mixin` class to add authentication and permission checking to a :class:`View` class. """ """ The set of authentication types that this view can handle. - Should be a tuple/list of classes as described in the ``authentication`` module. + Should be a tuple/list of classes as described in the :mod:`authentication` module. """ authentication = () """ The set of permissions that will be enforced on this view. - Should be a tuple/list of classes as described in the ``permissions`` module. + Should be a tuple/list of classes as described in the :mod:`permissions` module. """ permissions = () @@ -376,8 +377,8 @@ class AuthMixin(object): @property def user(self): """ - Returns the user for the current request, as determined by the set of - authentication classes applied to the ``View``. + Returns the :obj:`user` for the current request, as determined by the set of + :class:`authentication` classes applied to the :class:`View`. """ if not hasattr(self, '_user'): self._user = self._authenticate() @@ -413,12 +414,10 @@ class AuthMixin(object): class ResourceMixin(object): """ Provides request validation and response filtering behavior. - """ - """ - Should be a class as described in the ``resources`` module. + Should be a class as described in the :mod:`resources` module. - The ``resource`` is an object that maps a view onto it's representation on the server. + The :obj:`resource` is an object that maps a view onto it's representation on the server. It provides validation on the content of incoming requests, and filters the object representation into a serializable object for the response. @@ -436,8 +435,8 @@ class ResourceMixin(object): def validate_request(self, data, files): """ - Given the request data return the cleaned, validated content. - Typically raises a ErrorResponse with status code 400 (Bad Request) on failure. + Given the request *data* return the cleaned, validated content. + Typically raises an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure. """ resource = self.resource(self) return resource.validate_request(data, files) @@ -459,8 +458,8 @@ class ResourceMixin(object): class InstanceMixin(object): """ - Mixin class that is used to identify a view class as being the canonical identifier - for the resources it is mapped too. + `Mixin` class that is used to identify a `View` class as being the canonical identifier + for the resources it is mapped to. """ @classmethod @@ -482,7 +481,7 @@ class InstanceMixin(object): class ReadModelMixin(object): """ - Behavior to read a model instance on GET requests + Behavior to read a `model` instance on GET requests """ def get(self, request, *args, **kwargs): model = self.resource.model @@ -501,7 +500,7 @@ class ReadModelMixin(object): class CreateModelMixin(object): """ - Behavior to create a model instance on POST requests + Behavior to create a `model` instance on POST requests """ def post(self, request, *args, **kwargs): model = self.resource.model @@ -525,7 +524,7 @@ class CreateModelMixin(object): class UpdateModelMixin(object): """ - Behavior to update a model instance on PUT requests + Behavior to update a `model` instance on PUT requests """ def put(self, request, *args, **kwargs): model = self.resource.model @@ -550,7 +549,7 @@ class UpdateModelMixin(object): class DeleteModelMixin(object): """ - Behavior to delete a model instance on DELETE requests + Behavior to delete a `model` instance on DELETE requests """ def delete(self, request, *args, **kwargs): model = self.resource.model @@ -570,7 +569,7 @@ class DeleteModelMixin(object): class ListModelMixin(object): """ - Behavior to list a set of model instances on GET requests + Behavior to list a set of `model` instances on GET requests """ # NB. Not obvious to me if it would be better to set this on the resource? diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 4337098af..06883ca35 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -5,8 +5,9 @@ to general HTTP requests. We need a method to be able to: -1) Determine the parsed content on a request for methods other than POST (eg typically also PUT) -2) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded +1.) Determine the parsed content on a request for methods other than POST (eg typically also PUT) + +2.) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded and multipart/form-data. (eg also handle multipart/json) """ @@ -22,47 +23,51 @@ __all__ = ( 'BaseParser', 'JSONParser', 'PlainTextParser', + 'DataFlatener', 'FormParser', - 'MultiPartParser' + 'MultiPartParser', ) class BaseParser(object): """ - All parsers should extend BaseParser, specifying a media_type attribute, - and overriding the parse() method. + All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute, + and overriding the :meth:`parse` method. """ media_type = None def __init__(self, view): """ Initialize the parser with the ``View`` instance as state, - in case the parser needs to access any metadata on the ``View`` object. + in case the parser needs to access any metadata on the :obj:`View` object. """ self.view = view def can_handle_request(self, content_type): """ - Returns `True` if this parser is able to deal with the given media type. + Returns :const:`True` if this parser is able to deal with the given *content_type*. - The default implementation for this function is to check the ``media_type`` - argument against the ``media_type`` attribute set on the class to see if + The default implementation for this function is to check the *content_type* + argument against the :attr:`media_type` attribute set on the class to see if they match. This may be overridden to provide for other behavior, but typically you'll - instead want to just set the ``media_type`` attribute on the class. + instead want to just set the :attr:`media_type` attribute on the class. """ return media_type_matches(content_type, self.media_type) def parse(self, stream): """ - Given a stream to read from, return the deserialized output. + Given a *stream* to read from, return the deserialized output. Should return a 2-tuple of (data, files). """ raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.") class JSONParser(BaseParser): + """ + Parses JSON-serialized data. + """ media_type = 'application/json' def parse(self, stream): @@ -74,11 +79,14 @@ class JSONParser(BaseParser): class DataFlatener(object): - """Utility object for flattening dictionaries of lists. Useful for "urlencoded" decoded data.""" - + """ + Utility object for flattening dictionaries of lists. Useful for "urlencoded" decoded data. + """ + # TODO: move me to utils ?? + def flatten_data(self, data): - """Given a data dictionary {: }, returns a flattened dictionary - with information provided by the method "is_a_list".""" + """Given a *data* dictionary ``{: }``, returns a flattened dictionary + with information provided by the method :meth:`is_a_list`.""" flatdata = dict() for key, val_list in data.items(): if self.is_a_list(key, val_list): @@ -93,15 +101,13 @@ class DataFlatener(object): return flatdata def is_a_list(self, key, val_list): - """Returns True if the parameter with name *key* is expected to be a list, or False otherwise. + """Returns :const:`True` if the parameter with name *key* is expected to be a list, or :const:`False` otherwise. *val_list* which is the received value for parameter *key* can be used to guess the answer.""" return False class PlainTextParser(BaseParser): """ - Plain text parser. - Simply returns the content of the stream. """ media_type = 'text/plain' @@ -113,10 +119,10 @@ class PlainTextParser(BaseParser): class FormParser(BaseParser, DataFlatener): """ The default parser for form data. - Return a dict containing a single value for each non-reserved parameter. + Returns a dict containing a single value for each non-reserved parameter. In order to handle select multiple (and having possibly more than a single value for each parameter), - you can customize the output by subclassing the method 'is_a_list'.""" + you can customize the output by subclassing the method :meth:`DataFlatener.is_a_list`.""" media_type = 'application/x-www-form-urlencoded' diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 1b1515581..10ba7eace 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -36,7 +36,7 @@ class BasePermission(object): def check_permission(self, auth): """ - Should simply return, or raise an ErrorResponse. + Should simply return, or raise an :class:`response.ErrorResponse`. """ pass @@ -59,7 +59,7 @@ class IsAuthenticated(BasePermission): if not user.is_authenticated(): raise _403_FORBIDDEN_RESPONSE -class IsAdminUser(): +class IsAdminUser(BasePermission): """ Allows access only to admin users. """ @@ -85,7 +85,7 @@ class PerUserThrottling(BasePermission): """ Rate throttling of requests on a per-user basis. - The rate is set by a 'throttle' attribute on the ``View`` class. + The rate (requests / seconds) is set by a :attr:`throttle` attribute on the ``View`` class. The attribute is a two tuple of the form (number of requests, duration in seconds). The user id will be used as a unique identifier if the user is authenticated. diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 112736d2f..3e59511c3 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -36,8 +36,8 @@ __all__ = ( class BaseRenderer(object): """ - All renderers must extend this class, set the media_type attribute, - and override the render() function. + All renderers must extend this class, set the :attr:`media_type` attribute, + and override the :meth:`render` method. """ media_type = None @@ -51,7 +51,7 @@ class BaseRenderer(object): The requested media type is also passed to this method, as it may contain parameters relevant to how the parser should render the output. - EG: 'application/json; indent=4' + EG: ``application/json; indent=4`` By default render simply returns the output as-is. Override this method to provide for other behavior. @@ -102,8 +102,8 @@ class TemplateRenderer(BaseRenderer): A Base class provided for convenience. Render the object simply by using the given template. - To create a template renderer, subclass this, and set - the ``media_type`` and ``template`` attributes + To create a template renderer, subclass this class, and set + the :attr:`media_type` and `:attr:template` attributes. """ media_type = None template = None