Customizing the way Rum interprets models

Rum translates model classes into a list of rum.fields.Field instances using the rum.interfaces.IRepositoryFactory which knows how to introspect each kind of model objects.

This list of fields can be queried by the view layer to render an appropiate widget to allow viewing or creating each field as well as validating that the data the user submits is valid.

It is this abstraction the one that allows different kind of models to share the same view and viceversa since it de-couples both implementations.

Customizing fields

Rum may not always guess right what kind of data a particular field is supposed to store since it is not possible to derive their semantic meaning without resorting to extremely fragile heuristics (like treating a field named “email” as an email address). For example, a text field might as well hold an email address or a url or a password...

We have several ways to override the list of fields Rum will see for each model.

Using a class decorator

Probably the most straight-forward way is to decorate the model class with the rum.fields.FieldFactory.fields() class decorator. However, this has the disadvantage of coupling Rum to our domain model which could make reusing the domain model a bit harder since we can no longer package it without depending on Rum.

Lets see an example, we’ll customize the fields of the Movie model we just wrote in the Rumifying your models - A tutorial

from rum import fields

class Movie(Model):
    # code omitted...
    fields.FieldFactory.fields(
        'title',
        'genre',
        'filmed_on',
        'director',
        'actors',
        fields.HTMLText('synopsis')
        )
    # code omitted...

Declaring fields without modifying the domain model

However, the preferred option is to assign this metadata sepaerate from the package where the domain model lives, in the tutorial we would have done it in the app.py module.

from rum import fields
from model import Movie

fields.FieldFactory.fields(Movie, (
    'title',
    'genre',
    'filmed_on',
    'director',
    'actors',
    fields.HTMLText('synopsis')
    ))

Notice that we’re no longer modifying the code inside model.py and the similar, but slightly different, syntax of the same rum.fields.FieldFactory.fields() method

Note

There’s another way to customize the way Rum interprets models but it will not be documented yet. It involves extending generic functions and is very powerful but the API is still in a state of flux.

What effect does this have?

By providing a list of field names and a rum.fields.HTMLText instance named synopsis we’ve told Rum that from this model it should only care about these particular fields in the user interface (our users don’t care about the meaningless id primary key since it is an implementation detail). Field names as strings tell rum that we want it to guess what kind of fields are those but only those and in that particular order. A rum.fields.Field instance tells Rum that it should not guess what kind of field synopsis is since we’re explictly telling it that it is a field that will hold HTML text.

Note

The view layer is not obliged to do something with this extra information, (although it should probably do). They are just hints that it can interpret to render a specific widget if it knows how to do it.

A complete list of available fields is at the rum.fields document. The list is far from complete, if you miss any kind of field please drop by the rum-discuss mailing list and tell us.

Customizing views

As it has been said above, the view layer is not obliged to render any specific widget for each kind of field, however, we can also override which widget will be used for each of the model’s fields or the widget for the whole model.

To do this we need to lose some generality and instead of using the base rum.view.ViewFactory we need to use the specific view factory of the view plugin we’re using. This is because each view factory generates widgets/views for each field that might not be possible to aggregate into a compound view for the whole model if all of the widgets are not of the same “family”.

Using the class decorator

Like in the case of overriding rum.fields.Field instances, this method puts the view definition close to the model but might couple them in an unacceptable way.

from tw.rum import WidgetFactory
from somewhere import MovieForm

class Movie(Model):
    WidgetFactory.view(MovieForm(), action='edit')
    WidgetFactory.view(MovieForm(), action='update')
    WidgetFactory.view(MovieForm(), action='new')
    WidgetFactory.view(MovieForm(), action='create')

Declaring views without modifying the domain model

WidgetFactory.view(Movie, MovieForm(), action='edit')
...

Overriding the widget/view for a single field

Ok, say we want to use a custom calendar widget we’ve written to edit the filmed_on field

from somewhere import FanciestCalendar
from tw.rum.viewfactory import WidgetFactory
from model import Movie
WidgetFactory.view(Movie, FanciestCalendar(), attr='filmed_on', action='edit')

Notice the attr keyword argument which specifies the name of the field the rule that will be generated should match..

What effect does this have?

By overriding the whole view of a model we can control exactly how an instance will be displayed, edited and validated. Note that we need to override the complementary actions (new/create, edit/update) since the same view, or another but compatible view, will be used to validate the input generated when the form was submitted from the browser.

Customizing controllers:

Controllers can be overrided for particular models to add new URL endpoints to handle certain messages the browser sends to the server in a similar way as the above.

TODO: Explain it

Customizing the repository

Repositories can be overrided for particular models to alter the way these are saved, updated, retrieved, etc. in a similar way as the above.

TODO: Explain it

Customizing the way Rum looks like

TODO: Explain this