<!--*-markdown-*-->
# `django-ctrl` v1.1

`django-ctrl` is a library for Django (but not an application) which adds the
concept of *controllers* to Django. These are similar but not identical to Rails
controllers; in essence they are classes which allow you to bring together
several pieces of related functionality into one definition that can be referred
to directly from the URLconf. They aim to promote DRYness in view definitions.

## Architecture

Controllers themselves are subclasses of *Controller Types*, classes which
define the pattern of functionality. Thanks to some trickery in `django-ctrl`,
these subclasses are never instantiated themselves. An alternative pattern is
feasible which involves instantiating the controller and serving results from a
single instance, but this was not chosen due to concerns for thread safety.
You'll see soon how, even with the no-instance constraint, Django controllers
can be flexible, powerful and convenient.

### Example

Four common Controller Types are provided in `djctrl.common`. One of these,
`ResourceController`, allows you to define different ways of processing each
type of HTTP request method without a large and unwieldy if/elif/else statement.
For example:
    
    from django.http import HttpResponse
    from djctrl.common import ResourceController
    
    
    class MyResource(ResourceController):
        
        def get(request):
            return HttpResponse('This resource has been GOT.\n')
        
        def post(request):
            return HttpResponse('You have POSTed to this resource.\n')
        
        def delete(request):
            return HttpResponse('Thou hast deleted me!\n')

You might have noticed something quite unconventional about this class
definition: the methods do not take an explicit `self` as their first argument.
This is because these methods are never called from an instance perspective, but
instead from the class. Technically speaking, they are static methods, but if
you apply a classmethod decorator you can access the class too (with an
additional `cls` parameter before the request). `django-ctrl` knows not to make
an explicitly defined class method into a static method.

In order to point to this from your URLconf, all you need is this:

    url(r'^some/path/to/the/view/$', 'myapp.views.MyResource')

This is because the class objects themselves are callables which accept a HTTP
request and some arguments and return a HTTP response.

## Provided Controller Types

### `ResourceController`

You have already encountered this from the example above, but a quick summary:

* Methods on the subclass refer to HTTP request methods.

* If a method is received which is not defined, a `405 "Method Not Allowed"`
response is returned with a list of the supported methods (determined
dynamically from the class).

* Methods do not accept an explicit `self`, and do not have access to the class.
If you wrap them using the `@classmethod` decorator, you can give them access to
the class via an explicit `cls` argument.

### `AuthController`

This Controller Type allows you to distinguish between various states of
authentication on the request. You can define a very coarse filter, like so:

    from djctrl.common import AuthController
    
    class MyView(AuthController):
    
        def authenticated(request):
            return HttpResponse('Authenticated.')
        
        def anonymous(request):
            return HttpResponse('Anonymous.')

You can also define more fine-grained methods, such as `superuser()`, `staff()`,
`active()` and `inactive()`. The controller will dynamically determine which
method to call.

### `FormController`

This is a Controller Type which allows you to dispatch based on whether or not a
form has been submitted to the Controller and, if so, whether or not it is
valid. This requires a couple of extra steps, demonstrated in the example below:
    
    from django.http import HttpResponse
    from djctrl.common import FormController
    
    from myapp.forms import SomeFormClass
    
    
    class MyFormController(FormController):
        
        form = SomeFormClass
        form_method = 'POST'
        
        def unbound(request, form):
            return HttpResponse('The form has not been submitted.')
        
        def valid(request, form):
            return HttpResponse('The form has been submitted, and is valid.')
        
        def invalid(request, form):
            return HttpResponse('The form has been submitted, but is invalid.')

There are a couple of things you'll notice here. To begin with, the `form` and
`form_method` attributes have been set on the class. This tells the Controller
that input data should be submitted via POST, and should be validated against
the `SomeFormClass` form. Note that this is the form *class*, not an instance
thereof.

Each of the methods defined accepts both `request` and `form`, the latter being
the instance of the form class (`SomeFormClass` in this example). Additional
arguments provided via the URLconf will be supplied after these two arguments.

### `AjaxController`

This Controller Type allows you to dispatch against requests which originate via
AJAX or the browser itself navigating to the view. This is useful in making
websites which degrade well, providing the accessibility of plain old HTML, but
also the benefit of the dynamic features of AJAX in those browsers that support
it. It uses `request.is_ajax()` to determine if the request is an AJAX request;
for more information consult the [official Django docs](http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_ajax).
In short, this technique supports most major JavaScript frameworks. An example
is shown here:

    from django.http import HttpResponse
    from djctrl.common import AjaxController
    
    class MyController(AjaxController):
    
        def web(request):
            return HttpResponse('Hello, browser!')
        
        def ajax(request):
            return HttpResponse('{"string": "some JSON response."}',
                                content_type='application/json')

### `CacheController`

This is a Controller Type which allows you to leverage HTTP caching headers.

CacheController handles the boilerplate of dealing with HTTP caching headers,
via entity tags and modification timestamps, letting you optimise your views.
You only need to define three methods: `etag()`, `last_modified()` and `view()`.
Each should accept the request, and any additional URLconf arguments which may
be passed.

`etag()` is used to generate an entity tag for a HTTP request. It should return
a simple string which uniquely identifies the requested resource at the current
point in time. Possible values include a hash of the complete view output, or a
hash of the model information which a request refers to.

`last_modified()` should return the last modification timestamp of the requested
resource. This should be a `datetime.datetime` instance, and will be compared
against the provided dates in the HTTP request to determine whether the
representation of the resource needs to be re-sent.

If either of these is calculated prior to the view processing, the value
returned from the method will be attached to the request object (as `etag` or
`last_modified`). If you end up carrying out any other computationally intensive
calculations on the request in either `etag()` or `last_modified()`, you can
store the results of these calculations on the request object, making them
available later on for the main view.

`view()` should be your main view (if it is a function, it will automatically be
made into a static method if you do not specify otherwise). If you want to use
another controller here, you can just do:
    
    class MyController(djctrl.CacheController):
        class view(djctrl.AjaxController):
            ...

Note that if you do not provide `etag()` or `last_modified()`, the controller
will simply ignore the relevant HTTP request headers.

## Writing Custom Controller Types

It's quite easy to write custom Controller Types. You will need to define a
*view classmethod*. This should be called `_view_function`, and will accept
`cls` as its first argument (this is the class object itself), followed by the
`request` and any additional URLconf arguments. It is recommended that the
signature looks something like `_view_function(cls, request, *args, **kwargs)`.
This can dispatch on the request parameters, etc., and introspect the class,
returning a `django.http.HttpResponse` instance.

For example, the definition for `AjaxController` is as follows:

    class AjaxController(core.ControllerType):
    
        def _view_function(cls, request, *args, **kwargs):
            if request.is_ajax():
                return cls.ajax(request, *args, **kwargs)
            return cls.web(request, *args, **kwargs)

See? It's very simple.

## Downloading and Installation

You can download and install this library in a few ways:

1. Clone a copy of this repo from bitbucket and just run `python setup.py
install` from the root directory.

2. Run `easy_install django-ctrl` from the command line; this will automatically
fetch and install the latest version.

3. Download the tarball
[here](http://bitbucket.org/zacharyvoase/django-ctrl/get/tip.gz), extract it and
run `python setup.py install` from the root directory.

## Testing

To run the test suite, just enter the `test/djctrltest/` directory, and run:
    
    python manage.py test formtest authtest resourcetest ajaxtest

## License

This software is licensed under the following MIT-style license:

> Copyright (c) 2009 Zachary Voase
> 
> Permission is hereby granted, free of charge, to any person
> obtaining a copy of this software and associated documentation
> files (the "Software"), to deal in the Software without
> restriction, including without limitation the rights to use,
> copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the
> Software is furnished to do so, subject to the following
> conditions:
> 
> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
> 
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> OTHER DEALINGS IN THE SOFTWARE.

## Author

Zachary Voase can be found on [Twitter](http://twitter.com/zacharyvoase).
