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¶
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.
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.
- class Controller.Meta¶
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.
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)¶
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 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
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.
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.
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().
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.
def user_profile(): if not 'profile' in self.session: self.session['profile'] = UserProfile.find_by_user(self.user) return self.session['profile']
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.
The current action, such as ‘add’, ‘list’, ‘edit’, etc.
The current prefix, such as None, ‘admin’, ‘api’, etc.
The current controller’s name.
Any positional arguments passed inside of the route’s url template.
Any keyword arguments passed inside of the route’s url template.
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.
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
- class Controller.Util¶
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.
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!')
For a usual request, the events in order are:
- before_build_components, after_build_components
- before_startup, after_startup
- before_dispatch, after_dispatch
- template_names (only if using TemplateView)
- before_render, after_render (only if a view is rendered)
These events are broadcast to the global event bus with the prefix controller_.