Getting input from a user¶
User input is a tricky thing. Users always provide input as strings, but programs work in terms of objects such as integers, boolean values, dates, etc. Part of dealing with user input thus means translating (or marshalling) such a string to the actual Python object it represents. That’s not all though – user input also need to be validated, and appropriate error messages given to guide a user as to what the program considers valid input.
These are considerable tasks for a framework to deal with.
User input in Reahl¶
In order to get input from a user, Reahl uses special
Inputs. The responsibility of an
Input is to show a user the value of some item
in your program, and allow the user to change that value. There are
different kinds of
different ways of dealing with the visual and behavioural aspects of
Another player, the
the job of checking whether a string sent by a user is valid, to turn
that string into Python object, and to finally set such a Python
object as the value of an attribute of one of your model objects
Here is a what we need in the address book application:
Reahl models use makeup¶
The first step towards allowing a user to input anything is to
augment the model with
Fields – one for each of its attributes that will be accessible from the user interface of
the application. In our example, the name and email_address attributes of
an Address are exposed to the user.
To augment our Address class with
Fields, you create a special method fields() on the class, and annotate it with the @exposed decorator. Such a method should take one argument: fields on which you can set each
The name of each
Field you set here should match the name of a normal Python attribute on your class. Reahl uses this information to set the attribute value on your object upon receiving user input for that
class Address(Base): __tablename__ = 'addressbook2_address' id = Column(Integer, primary_key=True) email_address = Column(UnicodeText) name = Column(UnicodeText) @exposed def fields(self, fields): fields.name = Field(label='Name', required=True) fields.email_address = EmailField(label='Email', required=True)
The @exposed decorator turns your fields() method into a property named fields on instances of this class. The property is special only in that it is initialised by the method you provide.
The .fields attribute of an Address instance now contains a
Field for each corresponding
attribute on that Address which can be accessed via a user
interface. We will need it in a moment when we start creating
Fields vs Columns
One thing that may be confusing is that SQLAlchemy Columns are added to
Address, but Reahl
Fields are added as well.
You have to keep in mind that SQLAlchemy has the job of persisting data in a database. The SQLAlchemy Column for email_address specifies that the email_address attribute of an Address object will be saved to a database column that can deal with unicode text.
Field added for email_address is not concerned with
persistence at all. It specifies several bits of useful
metainformation about the email_address attribute of an Address
object – information that can be used to govern interaction with
Fields could happily describe an attribute which does not get
persisted at all. Similarly, an SQLAlchemy Column could happily
describe an attribute that is not exposed to a user interface.
Fields provide information about attributes¶
The extra information provided by
Fields is useful in other
contexts. Take the label that is specified everywhere, for
example. This is what a user may call the
Field in natural
language – quite a useful bit of information when you want to create a
user interface referring to the
The required=True keyword argument can be passed to a
Field to say
that this information normally has to be provided as part of input.
(What good is an Address with only a name, or only an
Notice also that email_address is an
EmailField. A valid email
address has a specific form. The string ‘john’ is not a valid email
address, but 'firstname.lastname@example.org‘ is. Because email_address was
specified as an
EmailField, user input to email_address can now be
validated automatically to make sure only valid input ever makes it into the model.
Adding the actual Widgets¶
You should have gathered by now that we are going to have to add a
Form so that we can also add
TextInputs for each of the
Fields we are
exposing. That’s not the entire story though: we want it to look nice
too – with a label next to each
successive labels and
aligned underneath each other. It would also be visually pleasing if
we add a heading to this section of our page.
Two elements come to our aid in this regard: An
InputGroup is a
that groups such a section together, and give the section a heading.
LabelledBlockInput is a
Widget that wraps around another
Input (such as a
TextInput), and provides it with a
arrange themselves neatly below each other.
Let’s finish the example in the next section.