Information for the developer
Display pages v/s Database Display pages
class MyDBDisplay(Display, Database,...):
This is used by all pages, which display/modify data stored in the database. The info page for example is a page which does not use the Database class, as the contents of the page is "static text".
The concepts of mode, shape, single/multiple, actions all apply exclusively to Database Display pages. The permissions on the other hand apply to all pages.
Things to remember:
- Typically single_container_control and multiple_container_control are some sort of control which outputs a TABLE object. If you want some static text to be printed before and/or after the TABLE, then set the multiple_container_control to be a
CoalesceContainerControl([< before controls >, SimpleContainerControl(TABLE), < after controls >],representative = k), where k is the index of the SimpleContainerControl in the list.
- Typically multiple_row_container_control outputs some sort of a TR object.
- Every control in multiple_controls should return something which can be put inside a <TD>...</TD>
- Most Controls when rendered in FORM shape (shapes=FORM) return an TR output object.
- As a corollory you should now be able to conclude that having a page be in FORM shape and be a multiple page is very difficult (if not impossible)
Different concepts associated with a page
- Is this a single / multiple page?
- Each page can be a single page (display info about one record) or multiple page (display info for more than one record) or both. In case it is both, the arguments given to the page usually determine, on a per invocation basis, whether this invocation is to be processes as a single or multiple page. This decision is implemented in the is_multiple method of the DisplayClass.
- Permissions to view the results
- If the user is not logged in, is he allowed to view the results?
- If the user is logged in, does (s)he have permissions to view the results?
The permissions are decided in permit_mode method of the DisplayClass. This usually is just a call to perms.may with appropriate arguments. The permit_mode method should be called in make_page and perform_action -- if this page supports actions, right in the beginning.
- How many modes does this page support?
- Does it have a display and edit mode or only one of them. The choice may also depend on whether it is a single page / multiple page. For eg. you may have a page which as a multiple page supports only display mode and as a single page supports both display and edit mode. This is also implemented in make_page and perform_action, right after the permissions are determined. Some times you may need to bounce to same page in a different mode, if the current mode does not make sense for the current page (this can depend on whether the page is currently a single database page or a multiple database page).
Some pages may need to support more than the two basic modes. For an example, see courses/description when displaying info for a single course.
- What shape does each mode correspond to?
- Once the current user has the right permission, and is in the right mode, we need to change the current shape to suit the mode. This is also done in make_page. This need not be done in perform_action since no HTML output is generated when an action is being performed.
For eg. in courses/description single database page, the display, retire, renumber, verify modes all correspond to the same display shape, though the HTML output varies a little with the mode.
- Which of these modes perform an action?
- Some modes after accepting some input from the user, have to make changes in the databases. These changes are implemented as actions. The input is accepted from the user through a HTML FORM. Clicking "Commit Changes" button on the form will generate a request to the same page, this time with the information the user entered. This time, the method perform_action is called instead of make_page. It is upto the perform_action method to check permissions, mode, verify the validity of the data entered (display.form_fields) and call perform_database_update or perform_database_insert depending on whether we want to add a new record or edit an existing record.
Where do I get the arguments for the page?
If you want to know when to use what, see here .
Modifying the database in db_update / db_insert
Dont forget to check the mode, and do the appropriate action. To help with the actual inserts and updates, use the queries.Update and queries.Insert class. Care should be taken to maintain the integrity of the database. Before doing any updates/inserts, one should lock all the tables, do the changes and then unlock the tables after the changes are made. This makes sure that other users seeing the database dont see it in an inconsistent state. A common misconception is to lock the tables only before the actual change to the database. You should lock the tables before doing any read which preceedes the write, as it is possible that some other process could read and write the database between our read and write (Only god knows the ways in which Operating Systems schedule the process!). Another thing to look out for is exceptions when modifying the database. You need to make sure that the tables are unlocked eventually, even if any exceptions are raised during the process of modifying the database. For eg. if a WebError exception is raised during the process of writing to a table, the control passes to the dispatcher module, and then the tables are not unlocked. After this no other process can read the locked tables, and one will have to restart the server. All this is handled in the following piece of psuedocode:
1. lock all the tables necessary
2. read existing data from the table and confirm that this modification will
not result in any integrity violations. (Eg. when adding a new course,
first check that it is not already there...)
3. Construct the Update/Insert query.
execute the update/insert query
code to catch SQL errors (e is the exception object)
-- if the code in step 2 is correct this should never be executed.
unlock all the tables
The finally clause, ensures that the code following it is always executed irrespective of whether an exception is raised within the try block or not.