Controllers

As you may have guessed, Controllers are the ‘C’ in MVC. They are responsible for processing and responding to HTTP requests. Controllers contain methods called Actions that respond to requests, and are typically lightweight classes that glue the Models to the Templates.

Although controllers are typically associated with a single model, it’s completely possible to use multiple or no models at all. Usually you’ll want to put most of your business logic in your models and simply write glue code in the controller. Controllers can be extended with Components and scaffolded.

The routing layer determines which Controller and Action to call when a request is made.

class ferris.core.controller.Controller[source]

Controllers allows grouping of common actions and provides them with automatic routing, reusable components, request data parsering, and view rendering.

A Controller that deals with time travel might look like this:

from ferris import Controller, route

class Tardis(Controller):
    class Meta:
        prefixes = ('admin',)
        components = (Extrapolator, Time)

    def list(self):
        return 'We are currently at %s' % self.components.time.current()

    @route
    def set(self, time):
        self.components.time.set(time)
        return "Time set to %s" % self.components.time.current()

    @route
    def admin_self_destruct(self):
        start_self_destruct_sequence()
        return "Run!"

The implementation of the “Extrapolator” and “Time” components is left as an exercise for the reader.

Conventions

Controllers are named plural nouns in upper camel case (UpperCamelCase) from the models that they are associated with (for example: Pages, Users, Images, Bears, etc.). There are many cases where the plural convention doesn’t make sense, such as controllers that don’t have an associated model or controllers that span multiple models.

Each controller class should be in its own file under /app/controllers and the name of the file should be the underscored class name. For example, to create a controller to act on fuzzy bears, create the file /app/controllers/fuzzy_bears.py. Inside, define a class named FuzzyBears.

This section focuses on actions and how to process and respond to requests. Other features are tied into controllers in various ways. Actions are exposed via the Routing system. Controllers can automatically render a view, process POST and JSON data and attach it to Forms or Messages using Request Parsers. Finally, controllers also allow you to break out common functionality using Components.

Configuration

class Controller.Meta[source]

The Meta class stores configuration information for a Controller. This class is constructed into an instance and made available at self.meta. This class is optional, Controllers that do not specify it will receive the default configuration. Additionally, you need not inherit from this class as Controller’s metaclass will ensure it.

For example:

def Posts(Controller):
    class Meta:  # no inheritance
        prefixes = ('admin', )
        #  all other properties inherited from default.
Parser = 'Form'

Which RequestParser class to use by default. See Controller.parse_request().

View

Which View class to use by default. use change_view() to switch views.

alias of TemplateView

authorizations = (<function inner_inner at 0x10d8002a8>,)

Authorizations control access to the controller. Each authorization is a callable. Authorizations are called in order and all must return True for the request to be processed. If they return False or a tuple like (False, ‘message’), the request will be rejected. You should always have auth.require_admin_for_prefix(prefix=('admin',)) in your authorization chain.

change_view(view, persist_context=True)[source]

Swaps the view, and by default keeps context between the two views.

Parameters:view – View class or name.
components = ()

List of components. When declaring a controller, this must be a list or tuple of classes. When the controller is constructed, controller.components will be populated with instances of these classes.

prefixes = ()

Prefixes are added in from of controller (like admin_list) and will cause routing to produce a url such as ‘/admin/name/list’ and a name such as ‘admin:name:list’

This Meta class is also used to configure components:

class Meta:
    components = (Pagination,)
    pagination_limit = 5

The Meta class is constructed and made available via self.meta (lowercase). You can use this to change configuration on the fly:

@route
def list_long(self):
    self.meta.pagination_limit = 50
    self.meta.change_view('json')

Actions

Actions are normal instance methods that can be invoked via HTTP. Actions are responsible for receiving requests and generating a response.

Ferris takes care of automatically routing actions and generating URLS. The CRUD actions list, view, add, edit, and delete are automatically routed. Other actions have to be explicitly marked for routing.

A simple action might look like this:

# via /controller/echo/<text>
@route
def echo(self, text):
    return text

Requests

Actions can access the current request using self.request:

def list(self):
    return self.request.path

For more information on the request object see the webapp2 documentation on requests.

Data

Actions can also access the GET and POST variables using self.request.params:

# i.e. /controller?text=meow
def list(self):
    return self.request.params['text']

For just GET variables use self.request.GET, and for POST only use self.request.POST. PUT and PATCH data are always in self.request.POST.

More complex request data such as Forms and Messages instance handled using Request Parsers.

Parameters

Actions can also take various parameters on the URL as described in Routing:

# /controller/concat/<text>/<number>
@route
def concat(self, text, number):
    return text + str(number)

User & Session

Controller.user = None

The current user as determined by google.appengine.api.users.get_current_user().

Controller.session[source]

Sessions are a simple dictionary of data that’s persisted across requests for particular browser session.

Sessions are backed by an encrypted cookie and memcache.

For example:

def user_profile():
    if not 'profile' in self.session:
        self.session['profile'] = UserProfile.find_by_user(self.user)
    return self.session['profile']

Route Info

The controller provides all of the information about the current route via self.route. The purpose of these variables is explained in depth in Routing.

Controller.route
Controller.route.action

The current action, such as ‘add’, ‘list’, ‘edit’, etc.

Controller.route.prefix

The current prefix, such as None, ‘admin’, ‘api’, etc.

Controller.route.controller

The current controller’s name.

Controller.route.name

The canonical route name, as generated by URL and Name Generation.

Controller.route.args

Any positional arguments passed inside of the route’s url template.

Controller.route.kwargs

Any keyword arguments passed inside of the route’s url template.

Response

Ferris simplifies responding by allowing you to return plain types that get transformed into responses or just allowing the Views to auto-render. However, it’s often useful to directly access the response to set headers or output binary data. Actions can directly access the current response using self.response:

def list(self):
    self.response.headers['x-test'] = "greeting"
    self.response.write('hi')
    return self.response

For more information on the request object see the webapp2 documentation on responses.

Return Values

Ferris uses Response Handlers to transform anything returned from an action into a response.

Actions can return a string and the string will become the body of the response with the content-type ‘text/html’:

def list(self):
    return 'Hi!'

You can set the content-type beforehand if you’d like:

def list(self):
    self.response.content_type = 'text/plain'
    return 'plain, plain, old text.'

Actions can return an integer. They will become the status of the response. For example, this response will be 404 Not Found:

def list(self):
    return 404

Actions can return any webapp2.Response class, including self.response:

def list(self):
    self.response.content_type = 'application/json'
    self.response.text = '[0,1,2]'
    return self.response

Even if you return a string or integer, any changes to self.response are kept (except for the body or status, respectively):

def list(self):
    self.response.content_type = 'text/xml'
    self.response.headers['cache-control'] = 'nocache'
    return '<x>Hello!</x>'

Returning nothing (None) will trigger the automatic view rendering unless self.meta.view.auto_render is set to False. See Views for more information:

def list(self):
    # nothing returned, so by default 'app/templates/controller/list.html' will be rendered.
    pass

Redirection

Redirects can be generated using self.redirect and uri():

@route
def auto(self):
    return self.redirect(self.uri(action='exterminate', who='everything'))

Security

Controllers are secured using Authorization Chains.

Utilities

class Controller.Util[source]

Provides some basic utility functions. This class is constructed into an instance and made available at controller.util.

static decode_key(str, kind=None)

Decodes a urlsafe ndb.Key.

static encode_key(ins)

Encode an ndb.Key (or ndb.Model instance) into an urlsafe string.

static parse_json(str)

Decodes a json string.

static stringify_json(data)

Encodes a json string.

decode_key is especially useful for working with urlsafe keys:

@route
def one(self):
    item = Widget.find_by_name('screwdriver')
    return self.redirect(
        self.uri(action='two', key=item.key.urlsafe()))

@route
def two(self, key):
    item = self.util.decode_key(key).get()
    return item.name

The Startup Method and Events

Handlers have various Events that are called during the lifecycle of a request. Event handlers should be registered at the beginning of a request using the startup callback method.

Controller.startup()[source]

Called when a new request is received and before authorization and dispatching. This is the main point in which to listen for events or change dynamic configuration.

You can tap into these events using Controller.events which is a NamedEvents instance:

def startup(self):
    self.events.before_dispatch += self.on_after_dispatch

def self.on_after_dispatch(self, controller=None, response=None):
    logging.info('hello!')

Events

For a usual request, the events in order are:

  1. setup_template_variables
  2. before_build_components, after_build_components
  3. before_startup, after_startup
  4. before_dispatch, after_dispatch
  5. template_names (only if using TemplateView)
  6. before_render, after_render (only if a view is rendered)
  7. dispatch_complete

These events are broadcast to the global event bus with the prefix controller_.