Tutorial by JamesHenstridge. (See also lib/canonical/launchpad/doc/launchpadform.txt.)
To start with, here is a very basic example:
from canonical.launchpad.webapp import ( LaunchpadFormView, action) class MyView(LaunchpadFormView): schema = ISomeContentObject @action("Change") def change_action(self, action, data): # data is a dictionary of decoded form values @property def next_url(self): return canonical_url(self.context)
This class can be registered in ZCML using the standard <browser:page> element rather than a special element. The page template should use the "context/@@launchpad_form/form" macro rather than the addform, editform or generalform macros.
The form will display widgets for each field defined in the given schema. It presents a single button at the bottom of the form labelled "Change". If all the validators attached to the fields pass, the change_action() method will be called. After a successful form submission, they'll get redirected to the URL of the context object.
If you want to limit the fields displayed by the form, this can be done by setting the field_names class attribute on the view class (rather than doing it in ZCML):
field_names = ['field1', 'field2', 'field3']
If you need to use custom widgets for any of the fields, this can be done with a call to custom_widget() in the class definition:
from canonical.launchpad.webapp import custom_widget class MyView(LaunchpadFormView): schema = ISomeContentObject custom_widget('password', PasswordWidget)
If you need to perform any extra validation not covered by field validators, this can be done by overriding the validate() method. It should call the addError() method (for form-wide error messages) or setFieldError() method (to set the error for a particular field) for each problem found:
def validate(self, data): if data.get('field1') == 42: self.setFieldError('field1', 'Field 1 can not be 42') if data.get('field2') != data.get('field3'): self.addError('Field 2 must be equal to Field 3')
If any errors are recorded by the validate method, the form submission will fail and the messages will be displayed to the user. The action method will not be called. To enable us to improve the presentation of errors to the user in the future, make sure that you do validation from the validate() method rather than in the action method.
If you want to set some initial values for the form, this can be done with the initial_values attribute/property, the same as you can with GeneralFormView.
If you need multiple submit buttons in your form, simply define multiple action methods. The form machinary will make sure the buttons are displayed, and call the appropriate action method on form submission.
If you are writing an edit form, there is a special LaunchpadEditFormView subclass you can use as a base. It provides the following functionality on top of LaunchpadFormView:
- the field values will be initialised from the context object.
- an updateContextFromData() method is provided to help implement the action method (it updates the context and emits an SQLObjectModifiedEvent if needed).
A typical action method for an edit view would look like this:
@action("Edit Product", name="edit") def edit_action(self, action, data): self.updateContextFromData(data)
There is no LaunchpadAddFormView subclass since we didn't really see any particularly special requirements for this type of form. A simple LaunchpadFormView subclass should suffice.