Components

Components are reusable pieces of functionality that can be invoked via a controller or react to controller events. This are a great way to re-use code between controllers, such as with pagination, searching, emailing, etc.

Using Components

Import the components and include them in the Meta.components property on your controller, like so:

from ferris.components.pagination import Pagination
from ferris.components.search import Search

class Documents(Controller):
    class Meta:
        components = (Pagination, Search)

Inside of your actions, you can access the component instances using self.components:

def list(self):
    self.components.pagination()

Creating Components

You can of course create your own components. Here’s a very simple component that listens for the startup event and logs:

class Logger(object):
    def __init__(self, controller):
        self.controller = controller
        self.controller.events.after_start += self.on_after_startup

    def on_after_startup(self, controller):
        logging.info("Hello!")

Built-in Components

The ferris.components module provides a few built-in components. These can be used directly, customized, or used as guidelines when building your own.

Cache

The Cache component provides utilities for doing client-side (edge) caching. This is not to be confused with memcache which is server-side caching.

class ferris.components.cache.Cache(controller)[source]

Provides easy methods to for setting edge caching, both via the browser and App Engine’s intermediate caching proxies.

Usage is pretty easy. Include the cache component and use the set_cache decorator.

ferris.components.cache.set_cache(mode='public', minutes=None)[source]

Decorator that calls the cache component automatically for an action

For example:

from ferris import Controller, route
from ferris.components.cache import Cache, set_cache

class Posts(Controller):
    class Meta:
        components = (Cache)

    @route
    @set_cache('public', 60*60)
    def list_all(self):
        ...

    @route
    @set_cache('private', 60*60)
    def list_mine(self):
        ...

OAuth

The OAuth component is documented in OAuth2.

Pagination

class ferris.components.pagination.Pagination(controller)[source]

Provides a generic, reusable Pagination API.

This can be used to automatically paginate ndb.Query objects but it can also be used directly to provide pagination over custom datasources.

Automatically happens for any list actions but can also be manually invoked via paginate() or __call__().

Pagination.paginate(query=None, cursor=None, limit=None)[source]

Paginates a ndb.Query and sets up the appropriate template variables.

Uses Controller.Meta.pagination_limit to determine how many items per page or defaults to 10 if omitted.

Returns the data, and if query is a string, sets that template variable.

If query is omitted it’ll attempt to find the dataset using the scaffold variable names.

For example of using this, see Extras

You can also use the pagination component to provide pagination for custom datasets such as external apis or CloudSQL. The pagination component can automatically keep track of previous cursors to provide backwards pagination. For example:

cursor, limit = self.components.pagination.get_pagination_params()

data, next_cursor = get_custom_data(cursor, limit)

self.components.pagination.set_pagination_info(
    current_cursor=cursor,
    next_cursor=next_cursor,
    limit=limit,
    count=len(data)
)
Pagination.get_pagination_params(cursor=None, limit=None)[source]

Retuns the pagination parameters provided by the request. Use this in your custom APIs to determine which cursor and the number of objects the user is requesting.

Pagination.set_pagination_info(current_cursor=None, next_cursor=None, limit=None, count=None)[source]

Sets the pagintion information for the view context. Use by your custom APIs to expose the next cursor, the limit, and the number of objects currently visible.

Sets the paging template variable to a dictionary like:

{
    "cursor": "abc...",
    "previous_cursor": "rvx...",
    "next_cursor": "nzb...",
    "limit": 10,
    "count": 10
}
Pagination.get_pagination_info()[source]

Returns the current pagination infomation from the view context: previous cursor, current cursor, next cursor, page, limit, and count.

There is also a helpful macro to print simple forward and backwards pagination controls:

{% import "macros/pagination.html" as p %}

{{p.next_page_link()}}

Generates bootstrap style forward and backwards pagination arrows.

Upload

The Upload component can take the guesswork out of using the Blobstore API to upload binary files.

class ferris.components.upload.Upload(controller)[source]

Automatically handles file upload fields that need to use the blobstore.

This works by:

  • Detecting if you’re on an add or edit action (you can add additional actions with upload_actions, or set process_uploads to True)
  • Adding the upload_url template variable that points to the blobstore
  • Updating the form_action and form_encoding scaffolding variables to use the new blobstore action
  • Processing uploads when they come back
  • Adding each upload’s key to the form data so that it can be saved to the model

Does not require that the controller subclass BlobstoreUploadHandler, however to serve blobs you must subclass BlobstoreDownloadHandler.

This component is designed to work instantly with with scaffolding and forms. Almost no configuration is needed:

from ferris import Model, ndb, Controller, scaffold
from ferris.components.upload import Upload

class Picture(Model):
    file = ndb.BlobKeyProperty()

class Pictures(Controller):
    class Meta:
        components = (scaffold.Scaffolding, Upload)

    add = scaffold.add
    edit = scaffold.edit
    list = scaffold.list
    view = scaffold.view
    delete = scaffold.delete

However, there are instances where you need more direct access. This is possible as well. Upload happens in two phases. First, you have to generate an upload url and provide that to the client. The client then uploads files to that URL. When the upload is successful the special upload handler will redirect back to your action with the blob data. Here’s an example of that flow for a JSON/REST API:

from ferris import Controller, route
from ferris.components.upload import Upload

class Upload(Controller):
    class Meta:
        components = (Upload,)

    @route
    def url(self):
        return self.components.upload.generate_upload_url(action='complete')

    @route
    def complete(self):
        uploads = self.components.upload.get_uploads()
        for blobinfo in uploads:
            logging.info(blobinfo.filename)

        return 200
Upload.generate_upload_url(action=None)[source]
Upload.get_uploads(field_name=None)[source]

Get uploads sent to this controller.

Args: field_name: Only select uploads that were sent as a specific field.

Returns: A list of BlobInfo records corresponding to each upload. Empty list if there are no blobinfo records for field_name.