Most of the actual work so far has been markup really.

This commit is contained in:
markotibold 2011-05-18 22:13:48 +02:00
parent 49d4e50342
commit 92c015e049
6 changed files with 92 additions and 80 deletions

View File

@ -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. 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 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 from django.contrib.auth import authenticate
@ -26,20 +26,19 @@ class BaseAuthenticaton(object):
def __init__(self, view): 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 self.view = view
def authenticate(self, request): def authenticate(self, request):
""" """
:param request: Request to be authenticated Authenticate the :obj:`request` and return a :obj:`User` instance or :const:`None`. [*]_
:rtype: :obj:`User` or 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 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. it to determine if the request has the required permissions or not.
This can be an important distinction if you're implementing some token This can be an important distinction if you're implementing some token
based authentication mechanism, where the authentication context based authentication mechanism, where the authentication context
may be more involved than simply mapping to a :obj:`User`. may be more involved than simply mapping to a :obj:`User`.
@ -55,7 +54,7 @@ class BasicAuthenticaton(BaseAuthenticaton):
def authenticate(self, request): def authenticate(self, request):
""" """
Returns a :obj:`User` if a correct username and password have been supplied 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 from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
@ -85,7 +84,7 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton):
def authenticate(self, request): 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. # 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: if getattr(request, 'user', None) and request.user.is_active:

View File

@ -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 # cStringIO only if it's available
try: try:
@ -27,24 +29,25 @@ except ImportError:
# Lovely stuff # Lovely stuff
class RequestFactory(Client): 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() rf = RequestFactory()
get_request = rf.get('/hello/') get_request = rf.get('/hello/')
post_request = rf.post('/submit/', {'foo': 'bar'}) post_request = rf.post('/submit/', {'foo': 'bar'})
This class re-uses the django.test.client.Client interface, docs here: This class re-uses the :class:`django.test.client.Client` interface. Of which
http://www.djangoproject.com/documentation/testing/#the-test-client you can find the docs here__.
Once you have a request object you can pass it to any view function, __ http://www.djangoproject.com/documentation/testing/#the-test-client
just as if that view had been hooked up using a URLconf.
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): 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. has created it.
""" """
environ = { environ = {
@ -148,9 +151,11 @@ try:
import re import re
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor): class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
"""Override markdown's SetextHeaderProcessor, so that ==== headers are <h2> and ---- headers are <h3>. """
Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- headers are <h3>.
We use <h1> for the resource name.""" We use <h1> for the resource name.
"""
# Detect Setext-style header. Must be first 2 lines of block. # Detect Setext-style header. Must be first 2 lines of block.
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE) RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
@ -172,8 +177,11 @@ try:
blocks.insert(0, '\n'.join(lines[2:])) blocks.insert(0, '\n'.join(lines[2:]))
def apply_markdown(text): def apply_markdown(text):
"""Simple wrapper around markdown.markdown to apply our CustomSetextHeaderProcessor, """
and also set the base level of '#' style headers to <h2>.""" Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`,
and also set the base level of '#' style headers to <h2>.
"""
extensions = ['headerid(level=2)'] extensions = ['headerid(level=2)']
safe_mode = False, safe_mode = False,
output_format = markdown.DEFAULT_OUTPUT_FORMAT output_format = markdown.DEFAULT_OUTPUT_FORMAT

View File

@ -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 from django.contrib.auth.models import AnonymousUser
@ -41,7 +42,7 @@ __all__ = (
class RequestMixin(object): class RequestMixin(object):
""" """
Mixin class to provide request parsing behavior. `Mixin` class to provide request parsing behavior.
""" """
_USE_FORM_OVERLOADING = True _USE_FORM_OVERLOADING = True
@ -52,7 +53,7 @@ class RequestMixin(object):
""" """
The set of request parsers that the view can handle. 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 = () parsers = ()
@ -61,8 +62,8 @@ class RequestMixin(object):
""" """
Returns the HTTP method. Returns the HTTP method.
This should be used instead of ``request.method``, as it allows the method 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. to be overridden by using a hidden `form` field on a form POST request.
""" """
if not hasattr(self, '_method'): if not hasattr(self, '_method'):
self._load_method_and_content_type() self._load_method_and_content_type()
@ -100,7 +101,7 @@ class RequestMixin(object):
def FILES(self): def FILES(self):
""" """
Parses the request body and returns the files. 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). and also works on methods other than POST (eg PUT).
""" """
if not hasattr(self, '_files'): if not hasattr(self, '_files'):
@ -215,10 +216,10 @@ class RequestMixin(object):
class ResponseMixin(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. 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. 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. 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 = () renderers = ()
@ -237,7 +238,7 @@ class ResponseMixin(object):
# out of the box with existing Django classes that use render_to_response. # out of the box with existing Django classes that use render_to_response.
def render(self, 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 self.response = response
@ -354,21 +355,21 @@ class ResponseMixin(object):
class AuthMixin(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. 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 = () authentication = ()
""" """
The set of permissions that will be enforced on this view. 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 = () permissions = ()
@ -376,8 +377,8 @@ class AuthMixin(object):
@property @property
def user(self): def user(self):
""" """
Returns the user for the current request, as determined by the set of Returns the :obj:`user` for the current request, as determined by the set of
authentication classes applied to the ``View``. :class:`authentication` classes applied to the :class:`View`.
""" """
if not hasattr(self, '_user'): if not hasattr(self, '_user'):
self._user = self._authenticate() self._user = self._authenticate()
@ -413,12 +414,10 @@ class AuthMixin(object):
class ResourceMixin(object): class ResourceMixin(object):
""" """
Provides request validation and response filtering behavior. Provides request validation and response filtering behavior.
"""
""" Should be a class as described in the :mod:`resources` module.
Should be a class as described in the ``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, It provides validation on the content of incoming requests,
and filters the object representation into a serializable object for the response. 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): def validate_request(self, data, files):
""" """
Given the request data return the cleaned, validated content. Given the request *data* return the cleaned, validated content.
Typically raises a ErrorResponse with status code 400 (Bad Request) on failure. Typically raises an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
""" """
resource = self.resource(self) resource = self.resource(self)
return resource.validate_request(data, files) return resource.validate_request(data, files)
@ -459,8 +458,8 @@ class ResourceMixin(object):
class InstanceMixin(object): class InstanceMixin(object):
""" """
Mixin class that is used to identify a view class as being the canonical identifier `Mixin` class that is used to identify a `View` class as being the canonical identifier
for the resources it is mapped too. for the resources it is mapped to.
""" """
@classmethod @classmethod
@ -482,7 +481,7 @@ class InstanceMixin(object):
class ReadModelMixin(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): def get(self, request, *args, **kwargs):
model = self.resource.model model = self.resource.model
@ -501,7 +500,7 @@ class ReadModelMixin(object):
class CreateModelMixin(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): def post(self, request, *args, **kwargs):
model = self.resource.model model = self.resource.model
@ -525,7 +524,7 @@ class CreateModelMixin(object):
class UpdateModelMixin(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): def put(self, request, *args, **kwargs):
model = self.resource.model model = self.resource.model
@ -550,7 +549,7 @@ class UpdateModelMixin(object):
class DeleteModelMixin(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): def delete(self, request, *args, **kwargs):
model = self.resource.model model = self.resource.model
@ -570,7 +569,7 @@ class DeleteModelMixin(object):
class ListModelMixin(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? # NB. Not obvious to me if it would be better to set this on the resource?

View File

@ -5,8 +5,9 @@ to general HTTP requests.
We need a method to be able to: 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) 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
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) and multipart/form-data. (eg also handle multipart/json)
""" """
@ -22,47 +23,51 @@ __all__ = (
'BaseParser', 'BaseParser',
'JSONParser', 'JSONParser',
'PlainTextParser', 'PlainTextParser',
'DataFlatener',
'FormParser', 'FormParser',
'MultiPartParser' 'MultiPartParser',
) )
class BaseParser(object): class BaseParser(object):
""" """
All parsers should extend BaseParser, specifying a media_type attribute, All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
and overriding the parse() method. and overriding the :meth:`parse` method.
""" """
media_type = None media_type = None
def __init__(self, view): def __init__(self, view):
""" """
Initialize the parser with the ``View`` instance as state, 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 self.view = view
def can_handle_request(self, content_type): 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`` The default implementation for this function is to check the *content_type*
argument against the ``media_type`` attribute set on the class to see if argument against the :attr:`media_type` attribute set on the class to see if
they match. they match.
This may be overridden to provide for other behavior, but typically you'll 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) return media_type_matches(content_type, self.media_type)
def parse(self, stream): 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). Should return a 2-tuple of (data, files).
""" """
raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.") raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.")
class JSONParser(BaseParser): class JSONParser(BaseParser):
"""
Parses JSON-serialized data.
"""
media_type = 'application/json' media_type = 'application/json'
def parse(self, stream): def parse(self, stream):
@ -74,11 +79,14 @@ class JSONParser(BaseParser):
class DataFlatener(object): 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): def flatten_data(self, data):
"""Given a data dictionary {<key>: <value_list>}, returns a flattened dictionary """Given a *data* dictionary ``{<key>: <value_list>}``, returns a flattened dictionary
with information provided by the method "is_a_list".""" with information provided by the method :meth:`is_a_list`."""
flatdata = dict() flatdata = dict()
for key, val_list in data.items(): for key, val_list in data.items():
if self.is_a_list(key, val_list): if self.is_a_list(key, val_list):
@ -93,15 +101,13 @@ class DataFlatener(object):
return flatdata return flatdata
def is_a_list(self, key, val_list): 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.""" *val_list* which is the received value for parameter *key* can be used to guess the answer."""
return False return False
class PlainTextParser(BaseParser): class PlainTextParser(BaseParser):
""" """
Plain text parser.
Simply returns the content of the stream. Simply returns the content of the stream.
""" """
media_type = 'text/plain' media_type = 'text/plain'
@ -113,10 +119,10 @@ class PlainTextParser(BaseParser):
class FormParser(BaseParser, DataFlatener): class FormParser(BaseParser, DataFlatener):
""" """
The default parser for form data. 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), 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' media_type = 'application/x-www-form-urlencoded'

View File

@ -36,7 +36,7 @@ class BasePermission(object):
def check_permission(self, auth): def check_permission(self, auth):
""" """
Should simply return, or raise an ErrorResponse. Should simply return, or raise an :class:`response.ErrorResponse`.
""" """
pass pass
@ -59,7 +59,7 @@ class IsAuthenticated(BasePermission):
if not user.is_authenticated(): if not user.is_authenticated():
raise _403_FORBIDDEN_RESPONSE raise _403_FORBIDDEN_RESPONSE
class IsAdminUser(): class IsAdminUser(BasePermission):
""" """
Allows access only to admin users. Allows access only to admin users.
""" """
@ -85,7 +85,7 @@ class PerUserThrottling(BasePermission):
""" """
Rate throttling of requests on a per-user basis. 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 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. The user id will be used as a unique identifier if the user is authenticated.

View File

@ -36,8 +36,8 @@ __all__ = (
class BaseRenderer(object): class BaseRenderer(object):
""" """
All renderers must extend this class, set the media_type attribute, All renderers must extend this class, set the :attr:`media_type` attribute,
and override the render() function. and override the :meth:`render` method.
""" """
media_type = None media_type = None
@ -51,7 +51,7 @@ class BaseRenderer(object):
The requested media type is also passed to this method, The requested media type is also passed to this method,
as it may contain parameters relevant to how the parser as it may contain parameters relevant to how the parser
should render the output. should render the output.
EG: 'application/json; indent=4' EG: ``application/json; indent=4``
By default render simply returns the output as-is. By default render simply returns the output as-is.
Override this method to provide for other behavior. Override this method to provide for other behavior.
@ -102,8 +102,8 @@ class TemplateRenderer(BaseRenderer):
A Base class provided for convenience. A Base class provided for convenience.
Render the object simply by using the given template. Render the object simply by using the given template.
To create a template renderer, subclass this, and set To create a template renderer, subclass this class, and set
the ``media_type`` and ``template`` attributes the :attr:`media_type` and `:attr:template` attributes.
""" """
media_type = None media_type = None
template = None template = None