Scaffolding

One of the most powerful features of Ferris is scaffolding. Scaffolding can provide you with common CRUD functionality very quickly, and it may also be customized. Developers can use the entire scaffold or use certain parts as needed. Scaffolding provides both actions and templates for the actions list(), add(), view(), edit(), and delete().

Using Scaffolding

class ferris.core.scaffold.Scaffolding[source]

Scaffolding is enabled via the use of a special component called Scaffolding. Include it in your controller’s Meta class:

from ferris import Controller, scaffold

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

Note

This controller expects a Widget model available in app.models.widget. You can always manually specify a Model.

Once included, you can use the scaffold actions to provide functionality for your controller:

list = scaffold.list

For the full suite of functionality, include all actions:

from ferris import Controller, scaffold

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

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

Accessing your controller (in this case at /widgets) will present you with the complete suite of CRUD actions. Notice that you didn’t have to create any templates; there are a set of fallback scaffold templates that are automatically used if no template exists for the action.

It’s also useful to use scaffolding to generate an admin interface for your module. Because this is so useful, there are special provisions for the admin functionality. See admin scaffolding for more details.

Actions

The scaffold can provide the action logic for the common CRUD operations view(), list(), add(), edit(), and delete(). There are two ways to use these. You can use them directly:

view = scaffold.view
admin_list = scaffold.list

Or you can delegate to them:

def view(self, key):
    return scaffold.view(self, key)

The delegate pattern allows you to configure the scaffold on a per-action basis:

def view(self, key):
    self.scaffold.display_properties = ("name", "content")
    return scaffold.view(self, key)

Actions can also be used with prefixes:

def admin_view = view

Below are descriptions for each action provided.

ferris.core.scaffold.view(controller, key)[source]

Loads a given entity. Using the key (a urlsafe representation of an ndb.Key), it will fetch the item from the datastore and put it in the template context. It uses scaffold.singular as the template variable name. For example, if you’re on the controller “BrownBears” it’ll set context['brown_bear'].

ferris.core.scaffold.list(controller)[source]

Presents a list of entities. By default, this calls the model’s query method, but you can use scaffold.query_factory to change this behavior. It uses scaffold.plural as the template variable name. For example, if you’re on the controller “BrownBears” it’ll set context['brown_bears'].

ferris.core.scaffold.add(controller)[source]

Presents a form to the user to allow the creation of a new entity. It uses scaffold.ModelForm as the form to present to the user.

It also provides the logic for processing the form and saving the entity. This is done by checking if the request is a GET (in which case it shows the form) or a POST (in which case it processes the form). If the form does not pass validation then the item is not created and the user is presented with the form again along with any validation errors.

In the default operation, a new item is created by calling Model() then updating the model with the information. If you wish to specify arguments to the model constructor or do other initialization see scaffold.create_factory.

You can tap in to the behavior of this action using events. When processing the form, this action will fire the scaffold_before_apply, scaffold_after_apply, scaffold_before_save, and scaffold_after_save events.

Redirects to scaffold.redirect upon success.

ferris.core.scaffold.edit(controller, key)[source]

Similar to add, with the exception that instead of creating a new object, it loads an existing one using the given key. When the form is processed it will update that item in the datastore. The same events are fired. Also redirects to scaffold.redirect upon success.

ferris.core.scaffold.delete(controller, key)[source]

Will simply delete the entity specified by the given key from the datastore. Redirects to scaffold.redirect upon success.

Templates

There is a full set of templates for the list(), view(), add(), and edit() actions. These templates are used if no template exists for a particular action. For example, if the scaffold templates for add(), and edit() suited your needs but you wanted to use a custom list() then you would just create /app/templates/[controller]/list.html.

You can also inherit from the scaffold templates and override blocks and customize as needed:

{% extends "scaffolding/form.html" %}

{% block submit_text %}Commit{% endblock %}

Additionally, you can copy the templates from /ferris/templates/scaffolding into your app for even more control. It’s encouraged to use, extend, copy, and customize these simple templates to suit your needs.

view.html

Prints out each property and value in the entity at context[scaffolding.singular]. Uses scaffolding.display_properties <ScaffoldMeta.display_properties to determine which properties to print.

list.html

Loops through context[scaffolding.plural] and prints each item in a table format. Uses scaffolding.display_properties <ScaffoldMeta.display_properties to determine which properties to print.

Additionally displays action buttons for each item. These buttons are links to view(), edit(), and delete() if the actions are defined. You can override these buttons using the block item_action_buttons.

form.html

Uses the form macros to display the model form. Because it uses the form macros it also displays any validation errors.

Provides the following blocks:

  • form_tag - the literal html form tag.
  • form_actions - contains the cancel and submit buttons.
  • form_fields - contains all of the fields that make up the form.
  • cancel_button - contains the button that sends the user back to the list() action.
  • cancel_text - contains the text that’s inside of the cancel button.
  • submit_button - contains the button that submits the form.
  • submit_text - contains the text that’s inside of the submit button.
add.html

Just inherits from form.html and sets the submit_text block to Create.

edit.html

Just inherits from form.html and sets the submit_text block to Save.

nav.html

Provides sub-navigation. This used in admin scaffolding to display links to list() and add(). It also displays any navigation items listed in scaffold.navigation. You can override and extend this to provide links to other actions.

Admin Scaffolding

You can generate a basic CRUD administration interface using the admin prefix along with scaffolding. This interface is limited to users who are App Engine administrators. When the scaffold detects that your controller has the admin prefix it will automatically add it to the module navigation available at /admin. It will also use the nav.html template to provide sub-navigation for your controller. This is all provided by the layout at /ferris/templates/layouts/admin.html which you can overload and extend as needed.

Here’s a complete example:

class Posts(Controller):
    class Meta:
        prefixes = ('admin',)
        components = (scaffold.Scaffolding,)

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

Component Integration

Scaffolding plays well with both the pagination and messages component.

If pagination is used then the list method will automatically page as expected.

Using the messages component enables scaffolding to use Message classes instead of Forms for the add() and edit() classes and also enables JSON-formatted responses for list() and view(). See the messages documentation for more information.

Events

The scaffold taps into the controller’s event system to provide callbacks during the add(), edit(), and delete() actions.

To listen and respond to these events use self.events inside of the controller:

def add(self):
    def before_save_callback(controller, container, item):
        logging.info(item)

    self.events.scaffold_before_save += before_save_callback

    return scaffold.add(self)
ScaffoldEvents.scaffold_before_apply(controller, container, item)

Triggered during add() and edit() before the form’s data is applied to the item.

ScaffoldEvents.scaffold_after_apply(controller, container, item)

Triggered during add() and edit() after the form’s data is applied to the item but before saving.

ScaffoldEvents.scaffold_before_save(controller, container, item)

Triggered during add() and edit() after the form’s data has been applied but before the item is saved to the datastore.

ScaffoldEvents.scaffold_after_save(controller, key)

Triggered during delete() before the item is permanently deleted from the datastore.

ScaffoldEvents.scaffold_before_delete(controller, key)

Triggered during delete() after the item is permanently deleted from the datastore.

Configuration

The scaffold always needs a model in order to operate. In most cases you won’t have to specify one: it can automatically load your Model if it matches the singular inflection naming scheme. For example, if your controller is Bears and you have a model named Bear in app.models.bear then scaffold will automatically find and load the model.

To specify the model manually, use Controller.Meta.Model:

class Bears(Controller):
    class Meta:
        Model = BrownBear

For all other configuration the scaffold uses its own inner configuration class similar to the controller’s meta class. This can be used to configure various thing about the scaffold’s behavior. For example:

from ferris import Controller, scaffold

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

    class Scaffold:
        title = "Magical Widgets"
        display_properties = ("name", "price")

Like Meta, this is constructed and made available during requests at self.scaffold (lowercase):

def list(self):
    self.scaffold.display_properties = ("name",)
    return scaffold.list(self)
class ferris.core.scaffold.ScaffoldMeta

Default meta configuration for the scaffold.

ScaffoldMeta.query_factory

The default query factory for use in the list() action. By default this just calls Model.query(). You can specify any callable. The factory should be in the form of factory(controller).

For example:

def location_query_factory(controller):
    location = controller.request.params.get('location')
    Model = controller.meta.Model
    return Model.query(Model.location == location)
ScaffoldMeta.create_factory

The default factory to create new model instances for the add() action. This is useful if items need to be created with certain data or a ancestor key. You can specify any callable; it should be in the form of factory(controller).

For example:

def ancestored_create_factory(controller):
    return controller.meta.Model(parent=ndb.Key("User", controller.user.email()))
ScaffoldMeta.title

The proper title of the controller. Used in the <title> html tag as well as for page headers. This is automatically set to inflector.titleize(Controller.__name__).

ScaffoldMeta.plural

The pluralized, underscored version of the controller’s name, useful for getting list data from the view context.

For example scaffold’s list() sets self.context[self.scaffold.plural]. If you wanted to get that data later you would use self.context[self.scaffold.plural].

ScaffoldMeta.singular

The singularized, underscore version of the controller’s name. Similar to plural, you typically don’t want to modify it but it’s useful for getting the data set by the view(), add(), and edit() actions which set self.context[self.scaffold.singular].

ScaffoldMeta.ModelForm

The default form to use for the add() and edit() actions. By default it calls model_form() with the default options to generate a form class. You can override this to provide your own:

WidgetForm = model_form(Widget, exclude=('foo', 'baz'))

...

def add(self):
    self.scaffold.ModelForm = WidgetForm
    return scaffold.add(self)
ScaffoldMeta.display_properties

Determines which of the model’s properties are visible in the list() and view() actions.

Example:

def list(self):
    self.scaffold.display_properties = ("created_by", "title", "publish_date")
    return scaffold.list(self)
ScaffoldMeta.redirect

The URI in which to send the user when they successly complete the add() or edit() actions. By default this is list() but can be changed. If set to False no redirection will occur.

For example, to redirect the user to the newly created object:

def add(self):
    def set_redirect(controller, container, item):
        controller.scaffold.redirect = controller.uri(action='view', key=item.key.urlsafe())

    self.events.scaffold_after_save += set_redirect

    return scaffold.add(self)
ScaffoldMeta.form_action

The default URL to which forms are submitted. By default this is the current action: the add() form submits to add() and the :func:`edit form to edit(). In some cases it’s desirable to submit the form somewhere else (such as upload component)

ScaffoldMeta.form_encoding

The encoding used by the forms in add() and edit(). By default this is set to 'application/x-www-form-urlencoded' but may be changed if file uploads are needed.

ScaffoldMeta.flash_messages

Specifies whether or not to use flash messages. Flash messages are messages that appear on the next page after an action. The form actions add() and edit() use flash messages to tell the user if their save was successful or not. The delete() action uses it to confirm deletion. In the case of non-html views flash messages may not be wanted so you can disable them.

ScaffoldMeta.layouts

A dictionary of layouts to use for each prefix. By default no prefix uses the layout default.html and the admin prefix uses the layout admin.html. You can always override those templates or change this attribute to use custom layouts if desired:

class Scaffold:
    layouts = {
        None: "custom.html",
        "admin": "custom-admin.html"
    }
ScaffoldMeta.navigation

Used to specify actions in the sub-navigation. This is used by the nav.html template to display links to actions in your controller.

For example:

class Scaffold:
    nav = {
        "metrics": "Metrics",
        "logs": "Logs"
    }

Macros

Scaffold contains a few useful macros in /ferris/templates/scaffolding/macros.html. If you look at the templates for each method you can see that these macros are used frequently to help simply building templates.

To use these macros in your template use import:

{% import 'scaffolding/macros.html' as s with context %}

{{s.next_page_link()}}
ScaffoldMacros.layout()

Chooses the layout for the current prefix that’s configured via scaffold.layouts.

An alias to the pagination macro’s next_page_link(). Inserts a paginator if the pagination component is active.

ScaffoldMacros.print(s)

An alias to ferris.format_value(). Tries to find the best way to represent the given object as a string.

ScaffoldMacros.action_button(item, icon, btn, class=None, confirm=False)

Creates a bootstrap style button link to a particular action. This is used to generate the action buttons in the list view for view(), edit(), and delete().

ScaffoldMacros.action_buttons(item, class=None)

Creates the default set of buttons for view(), edit(), and delete() by calling action_button().

Creates a navigation link for nav.html.