A type definition is made up of:
name
A human readable type name - this value is stored in the node data and is the key to all subsequent interpretation.
paths
A set of URLs relating to the current node that are to appear in the navigation area of a page. The basic set consists of:
Since nodes may contain any data (and be of any type) these standard links will normally be to the generalised code. The generalised code supports user interface extensions and may be sufficient in many cases. The standard url dispatcher also supports type dependent controllers which provide flexibility at the aplication level.
Other links may be added to support business process actions. Generally, the presence or absence of a link on a user's view is controlled by user access permissions.
other data
There will also be special text and codes that are interpretable by the application. Specifically, there may be option lists that are specific to the application. For example, a set of possible priorities. Option lists take a specific structure.
Controllers respond to URLs. For URLs that contain a node reference, the dispatcher can invoke a controller that depends on the type of the node. This has the effect of turning a node type into an application.
- display an editable page = 'nb.controller.display.edit'
- display a page for adding a new entry = 'nb.controller.display.add'
- display data read-only = 'nb.controller.display.formatted'
- display a list of sub-nodes = 'nb.controller.display.list'
- present a default page = 'nb.controller.display.default'
- respond to a POST = 'nb.controller.respond.update'
- respond to a POST = 'nb.controller.respond.insert'
- respond to request for a file = 'nb.controller.download.file'
Each of these code segments overrides the relevant generalised page and must provide a complete view for the page, using all of the relevant data for the node type. Node navigation is not handled or replaced by these code segments.
The use of these type-specific pages assumes that data validation and insert/update is handled by the data specific code segments outlined in the next section.
Any controller can, of course, make use of the user interface extensions.
A data definition provides a number of code segments that support read validate and update of the data as well as optional user interface extensions for search, display and edit.
read/write/validate
Read from the database is managed using a node-based query that is modified according to filtering or sorting options. Adding entities to this query allows for data extensions, but there is no guarantee of the order in which entities are added. At the same time, some extensions involve multiple sub-records, or data from a separate database, so separate queries for this data might be appropriate. The data from the various queries is used to construct a single object dictionary that can be passed to other processes so that these other processes see a consistent picture of the data.
extend query = 'nd.read.query.extend'
extend the given node query - perhaps using a join - to include the data extension.
extend data object = 'nd.read.data.extend'
Place the extension data into the data object. This could be a simple as identifying the database entity from the returned result of the node query, or a more complex construction of multiple sub-records.
write from an extended object = 'nd.write.data.extend'
Use an extended object (or possibly a sub-set of one) as a data source to write data back to the database.
delete from an extended object = 'nd.delete.data.extend'
Use an extended object to identify, and delete, entries in the database.
validate data in an extended object = 'nd.validate.data.extend'
Validate the data in the given extended object and raise errors.
compare data in extended objects = 'nd.compare.data.extend'
Compare two extended objects and report on differences
search
Search requires the creation and the application of a filter. A filter is simply a dictionary containing named values. The following item is part of the data interface because it does not depend on the user interface and can be used by other code.
extend filter query = 'nd.search.query.extend'
Extend a filter query using the given filter.
These items extend the generalised interface. They will normally be invoked for every node, regardless of whether the node has the relevant data extension. Since every data extension can be seen on the generalised interface the user may potentially populate any or all data extensions on any one node. The data extension code must impose any limitations that might be required by the application context by applying relevant conditions from other data or by applying access permissions.
Node display and interaction
add form elements = 'nd.view.form.extend'
Place form elements on to the given display form.
populate form elements = 'nd.view.form.populate'
Place data from the data object into the form elements.
process form elements = 'nd.view.form.process'
Validate user input.
store from form elements = 'nd.view.form.store'
Store the data to the database.
Populate the main page in the display page = 'nd.display.main'
Provide some text that can appear in the main body of the display page.
Populate the sidebar in the display page = 'nd.display.side'
Provide some text that can appear in the side bar of the display page.
search
Search requires the creation and the application of a filter. A filter is simply a dictionary containing named values. These items extend the user interface accordingly.
make filter form = 'nd.search.form.extend'
Add form elements to a form to give the user a search facility on this data.
fill form from filter = 'nd.search.form.populate'
Given a filter, populate the form
Populate the filter object = 'nd.search.form.store'
Place items from the form into the filter object.
make filter from form
Given a form, construct the elements of the filter.
Extensions and types are registered in a static instance of the ExtensionStore class. The data structures that are used to define the data extensions and types are held somewhere within each package and passed to the register method of the ExtensionStore.
There are, in principle, (and possibly 'at'least') four ways of having a type or extension register itself:
For each extension or group of extensions, place a code module into an 'extensions' directory that is accessible from the startup environment. Initialisation code in nb will execute (that is, import) all modules within this directory.
This method seems to challenge the software release process as it becomes difficult to manage extensions that have different revision cycles.
For each extension or group of extensions, place a suitable 'import' statement into a startup.py module that is accessible from the startup environment.
This keeps the configuration separate from the revision cycle. It is much the same as the ini file approach, with the disadvantage that it is a separate file.
Place references to the modules to be imported into the initialisation file.
This keeps the configuration separate from the revision cycle.
Have the nb startup code search the full path for all modules named 'nb_register_extension.py', or something similar, and import each one.
This seems to do the job without explicit configuration, although it leads to slow start-up and to the possibility of finding something that is not actually what we want.
The method that is used is a combination of the last two mechanisms.
- The ini file will contain one or more 'package = xxx' entries.
- Each package entry refers to a python package that is visible on the path.
- The package is then treated as a directory and searched for files named 'nb_register_extension.py' or 'nb_register_*_extension.py' and imports each one.