Creating an Editor
This tutorial will give you an introduction how to create an editor for Stoq.
Before you start writing an editor you need to decide what object you need to show. That object is referred to as the model, which is usually a persistent object such as an instance of a SQLObject subclass. In this tutorial the object Person was chosen.
from stoqlib.gui.editors.baseeditor import BaseEditor from stoqlib.domain.person import Person class PersonEditor(BaseEditor): model_type = Person def create_model(self, conn): return Person(name="Unnamed", connection=conn)
This first example shows how a model is tied to an editor by setting the model_type attribute to the PersonEditor class pointing at the Person class, this is used to verify that the model sent in to the editor is of the right type.
A create_model method is also added, which defines how a Person is going to be created. create_model() is only called when we don't supply a model, eg when we want to create a new person instead of display an existing one. In this case we will create a person with the name "Unnamed".
User interface (View/Delegate)
So we have decided what object we want to show and how to create an empty one. The next step is to create the user interface, this is currently done through a .ui created by Glade.
The name of the toplevel window of the editor needs to be the same as the filename.
class PersonEditor(BaseEditor): gladefile = 'PersonEditor' model_type = Person def create_model(self, conn): return Person(name="Unnamed", connection=conn)
We added a gladefile attribute to the BaseEditor, this tells the editor that it should search for a file called PersonEditor.glade in the glade directories.
So let's create a Glade interface with the following hierarchy:
- Window (PersonEditor)
- VBox (vbox1)
- HBox (hbox1)
- Label (label1)
- KiwiEntry (name)
- HBox (hbox2)
- Label (label2)
- KiwiEntry (email)
- HBox (hbox1)
- VBox (vbox1)
The value inside of the parenthesis is the name of the widget. After creating the widget you need to do the following;
- Set the packing properties expand and fill to False on the hboxes, labels and entries.
- Change the label of label1 to Name: and set the xalign to 1.0
- Change the label of label2 to Email: and set the xalign to 1.0
- Set the data-type of both KiwiEntry's to Unicode
- Set the border width of the window to 6
- Set the spacing of the vbox and the hboxes to 6
- Add both labels to a horizontal sizegroup
Save the file as PersonEditor.glade.
Testing the editor
Now everything is setup to display the editor, here is a complete example how to initialize stoq and startup and show the editor:
from stoqlib.database.runtime import new_transaction from stoqlib.domain.person import Person from stoqlib.gui.editors.baseeditor import BaseEditor from stoqlib.gui.base.dialogs import run_dialog from stoq.lib.startup import setup setup() class PersonEditor(BaseEditor): gladefile = 'PersonEditor' model_type = Person def create_model(self, conn): return Person(name="Unnamed", email="email@example.com", connection=conn) trans = new_transaction() run_dialog(PersonEditor, None, trans)
The most interesting part here is the run_dialog line, which display the dialog. The first argment (PersonEditor) is the editor class which will be displayed The second argument (None) is the parent of the window, None in this case since we don't have a parent window. The third argument is the first argument which is sent in to the editor, which in this case is a transaction.
Save the content above in the same directory as the PersonEditor.glade and run it. A window will be displayed which will contain the widgets we created in glade, but it will also contain two buttons OK and cancel.
Connecting the user interface with the domain class (Proxy)
Now we have a model and an interface, but they're not coupled yet. To connect them with each other we'll create a Proxy which will fetch the content of the model and fill the interface.
This is pretty simple;
def setup_proxies(self): self.proxy = self.add_proxy(self.model, ['name', 'email'])
This method needs to be added to the BaseEditor subclass. The setup_proxies method will be called when creating a BaseEditor, it'll add a proxy for the specified attributes, eg name and email.
Add that and run the example again. It'll now show the contents of the model created in create_model().
Committing the data
To commit the data when the editor is created, do the following at the run_dialog callsite:
trans = new_transaction() model = run_dialog(PersonEditor, None, trans) finish_transaction(trans, model): trans.close()
There are a number of different ways to validate the data in Stoq. Here is a list of the different concepts and how you can combine them;
This is the most simple form of validation, for instance, numbers cannot contain letters and will complain.
Mandatory attributes are required to fill in, you cannot create confirm the creation or modification of the model unless the attribute is filled in (eg, non-zero).
custom validation hooks
If you need to verify the content of the attribute, for instance, requiring that a data is set in the future, you can implement a custom validation hook:
def on_name__validate(self, widget, name): if name in ["foo", "bar"]: return ValidationError("%s is not a valid name");
This method will prevent the user from creating a person called "foo" or "bar". Notice that you need to return the exception instead raising it.
While not strictly validation masks prevents the user from not entering a certain digits. It's usually combined with a custom validation hook.
It's important to ensure that the schema only contains valid data. Ideally all custom validation hooks should also include constraints which verifies the integrity of the data at a database level. The custom validation hooks only works for content edited by user, but a programmer could also make the mistake and forget to validate the data properly.
So for the name example mentioned above the following check should be done;
ALTER TABLE person ADD CONSTRAINT person_validate_name CHECK (name NOT IN ('foo', 'bar'));