Code

instance -> tracker, node -> item
[roundup.git] / doc / customizing.txt
1 ===================
2 Customising Roundup
3 ===================
5 :Version: $Revision: 1.23 $
7 .. This document borrows from the ZopeBook section on ZPT. The original is at:
8    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
10 .. contents::
13 What You Can Do
14 ---------------
16 Customisation of Roundup can take one of five forms:
18 1. `tracker configuration`_ file changes
19 2. database, or `tracker schema`_ changes
20 3. "definition" class `database content`_ changes
21 4. behavioural changes, through detectors_
22 5. `access controls`_
24 The third case is special because it takes two distinctly different forms
25 depending upon whether the tracker has been initialised or not. The other two
26 may be done at any time, before or after tracker initialisation. Yes, this
27 includes adding or removing properties from classes.
30 Trackers in a Nutshell
31 ----------------------
33 Trackers have the following structure:
35 +-------------------+--------------------------------------------------------+
36 |config.py          |Holds the basic `tracker configuration`_                |
37 +-------------------+--------------------------------------------------------+
38 |dbinit.py          |Holds the `tracker schema`_                             |
39 +-------------------+--------------------------------------------------------+
40 |interfaces.py      |Defines the Web and E-Mail interfaces for the tracker   |
41 +-------------------+--------------------------------------------------------+
42 |select_db.py       |Selects the database back-end for the tracker           |
43 +-------------------+--------------------------------------------------------+
44 |db/                |Holds the tracker's database                            |
45 +-------------------+--------------------------------------------------------+
46 |db/files/          |Holds the tracker's upload files and messages           |
47 +-------------------+--------------------------------------------------------+
48 |detectors/         |Auditors and reactors for this tracker                  |
49 +-------------------+--------------------------------------------------------+
50 |html/              |Web interface templates, images and style sheets        |
51 +-------------------+--------------------------------------------------------+
53 Tracker Configuration
54 ---------------------
56 The config.py located in your tracker home contains the basic
57 configuration for the web and e-mail components of roundup's interfaces. This
58 file is a Python module. The configuration variables available are:
60 **INSTANCE_HOME** - ``os.path.split(__file__)[0]``
61  The tracker home directory. The above default code will automatically
62  determine the tracker home for you.
64 **MAILHOST** - ``'localhost'``
65  The SMTP mail host that roundup will use to send e-mail.
67 **MAIL_DOMAIN** - ``'your.tracker.email.domain.example'``
68  The domain name used for email addresses.
70 **DATABASE** - ``os.path.join(INSTANCE_HOME, 'db')``
71  This is the directory that the database is going to be stored in. By default
72  it is in the tracker home.
74 **TEMPLATES** - ``os.path.join(INSTANCE_HOME, 'html')``
75  This is the directory that the HTML templates reside in. By default they are
76  in the tracker home.
78 **INSTANCE_NAME** - ``'Roundup issue tracker'``
79  A descriptive name for your roundup tracker. This is sent out in e-mails and
80  appears in the heading of CGI pages.
82 **ISSUE_TRACKER_EMAIL** - ``'issue_tracker@%s'%MAIL_DOMAIN``
83  The email address that e-mail sent to roundup should go to. Think of it as the
84  tracker's personal e-mail address.
86 **ISSUE_TRACKER_WEB** - ``'http://your.tracker.url.example/'``
87  The web address that the tracker is viewable at. This will be included in
88  information sent to users of the tracker.
90 **ADMIN_EMAIL** - ``'roundup-admin@%s'%MAIL_DOMAIN``
91  The email address that roundup will complain to if it runs into trouble.
93 **FILTER_POSITION** - ``'top'``, ``'bottom'`` or ``'top and bottom'``
94  Where to place the web filtering HTML on the index page.
96 **ANONYMOUS_ACCESS** - ``'deny'`` or ``'allow'``
97  Deny or allow anonymous access to the web interface.
99 **ANONYMOUS_REGISTER** - ``'deny'`` or ``'allow'``
100  Deny or allow anonymous users to register through the web interface.
102 **ANONYMOUS_REGISTER_MAIL** - ``'deny'`` or ``'allow'``
103  Deny or allow anonymous users to register through the mail interface.
105 **MESSAGES_TO_AUTHOR** - ``'yes'`` or``'no'``
106  Send nosy messages to the author of the message.
108 **ADD_AUTHOR_TO_NOSY** - ``'new'``, ``'yes'`` or ``'no'``
109  Does the author of a message get placed on the nosy list automatically?
110  If ``'new'`` is used, then the author will only be added when a message
111  creates a new issue. If ``'yes'``, then the author will be added on followups
112  too. If ``'no'``, they're never added to the nosy.
114 **ADD_RECIPIENTS_TO_NOSY** - ``'new'``, ``'yes'`` or ``'no'``
115  Do the recipients (To:, Cc:) of a message get placed on the nosy list?
116  If ``'new'`` is used, then the recipients will only be added when a message
117  creates a new issue. If ``'yes'``, then the recipients will be added on
118  followups too. If ``'no'``, they're never added to the nosy.
120 **EMAIL_SIGNATURE_POSITION** - ``'top'``, ``'bottom'`` or ``'none'``
121  Where to place the email signature in messages that Roundup generates.
123 **EMAIL_KEEP_QUOTED_TEXT** - ``'yes'`` or ``'no'``
124  Keep email citations. Citations are the part of e-mail which the sender has
125  quoted in their reply to previous e-mail.
127 **EMAIL_LEAVE_BODY_UNCHANGED** - ``'no'``
128  Preserve the email body as is. Enabiling this will cause the entire message
129  body to be stored, including all citations and signatures. It should be
130  either ``'yes'`` or ``'no'``.
132 **MAIL_DEFAULT_CLASS** - ``'issue'`` or ``''``
133  Default class to use in the mailgw if one isn't supplied in email
134  subjects. To disable, comment out the variable below or leave it blank.
136 **HEADER_INDEX_LINKS** - ``['DEFAULT', 'UNASSIGNED', 'USER']``
137  Define what index links are available in the header, and what their
138  labels are. Each key is used to look up one of the index specifications
139  below - so ``'DEFAULT'`` will use ``'DEFAULT_INDEX'``.
141  Example ``DEFAULT_INDEX``::
143   {
144    'LABEL': 'All Issues',
145    'CLASS': 'issue',
146    'SORT': ['-activity'],
147    'GROUP': ['priority'],
148    'FILTER': ['status'],
149    'COLUMNS': ['id','activity','title','creator','assignedto'],
150    'FILTERSPEC': {
151      'status': ['-1', '1', '2', '3', '4', '5', '6', '7'],
152    },
153   }
155  This defines one of the index links that appears in the
156  ``HEADER_INDEX_LINKS`` list.
158  **LABEL** - ``'All Issues'``
159   The text that appears as the link label.
160  **CLASS** - ``'issue'``
161   The class to display the index for.
162  **SORT** - ``['-activity']``
163   Sort by prop name, optionally preceeded with '-' to give descending or
164   nothing for ascending sorting.
165  **GROUP** - ``['priority']``
166   Group by prop name, optionally preceeded with '-' or to sort in descending
167   or nothing for ascending order.
168  **FILTER** - ``['status']``
169   Selects which props should be displayed in the filter section.
170   Default is all. 
171  **COLUMNS** - ``['id','activity','title','creator','assignedto']``
172   Selects the columns that should be displayed. Default is all.
173  **FILTERSPEC** - *a dictionary giving the filter specification*
174   The ``FILTERSPEC`` gives the filtering arguments. This selects the values
175   the item properties given by propname must have.
177   Where the ``FILTERSPEC`` value is ``'CURRENT USER'``, it will be replaced
178   by the id of the logged-in user. For example::
180    'FILTERSPEC': {
181      'status': ['-1', '1', '2', '3', '4', '5', '6', '7'],
182      'assignedto': 'CURRENT USER',
183    },
185 **HEADER_ADD_LINKS** - ``['issue']``
186  List the classes that users are able to add items to.
188 **HEADER_SEARCH_LINKS** - ``['issue']``
189  List the classes that users can search.
191 **SEARCH_FILTERS** - ``['ISSUE_FILTER', 'SUPPORT_FILTER']``
192  List search filters per class. Like the INDEX entries above, each key is
193  used to look up one of the filter specifications below - so ``'ISSUE'``
194  will use ``'ISSUE_FILTER'``.
196  Example ``ISSUE_FILTER``::
198   ISSUE_FILTER = {
199     'CLASS': 'issue',
200     'FILTER': ['status', 'priority', 'assignedto', 'creator']
201   }
203   **CLASS** - ``'issue'``
204    The class that the search page is for.
205   **FILTER** - ``['status', 'priority', 'assignedto', 'creator']``
206    Selects which props should be displayed on the filter page. Default is
207    all.
209 The default config.py is given below - as you
210 can see, the MAIL_DOMAIN must be edited before any interaction with the
211 tracker is attempted.::
213     # roundup home is this package's directory
214     INSTANCE_HOME=os.path.split(__file__)[0]
216     # The SMTP mail host that roundup will use to send mail
217     MAILHOST = 'localhost'
219     # The domain name used for email addresses.
220     MAIL_DOMAIN = 'your.tracker.email.domain.example'
222     # the next two are only used for the standalone HTTP server.
223     HTTP_HOST = ''
224     HTTP_PORT = 9080
226     # This is the directory that the database is going to be stored in
227     DATABASE = os.path.join(INSTANCE_HOME, 'db')
229     # This is the directory that the HTML templates reside in
230     TEMPLATES = os.path.join(INSTANCE_HOME, 'html')
232     # A descriptive name for your roundup tracker
233     INSTANCE_NAME = 'Roundup issue tracker'
235     # The email address that mail to roundup should go to
236     ISSUE_TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
238     # The web address that the tracker is viewable at
239     ISSUE_TRACKER_WEB = 'http://your.tracker.url.example/'
241     # The email address that roundup will complain to if it runs into trouble
242     ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
244     # Somewhere for roundup to log stuff internally sent to stdout or stderr
245     LOG = os.path.join(INSTANCE_HOME, 'roundup.log')
247     # Where to place the web filtering HTML on the index page
248     FILTER_POSITION = 'bottom'          # one of 'top', 'bottom', 'top and bottom'
250     # Deny or allow anonymous access to the web interface
251     ANONYMOUS_ACCESS = 'deny'           # either 'deny' or 'allow'
253     # Deny or allow anonymous users to register through the web interface
254     ANONYMOUS_REGISTER = 'deny'         # either 'deny' or 'allow'
256     # Deny or allow anonymous users to register through the mail interface
257     ANONYMOUS_REGISTER_MAIL = 'deny'    # either 'deny' or 'allow'
259     # Send nosy messages to the author of the message
260     MESSAGES_TO_AUTHOR = 'no'           # either 'yes' or 'no'
262     # Does the author of a message get placed on the nosy list automatically?
263     # If 'new' is used, then the author will only be added when a message
264     # creates a new issue. If 'yes', then the author will be added on followups
265     # too. If 'no', they're never added to the nosy.
266     ADD_AUTHOR_TO_NOSY = 'new'          # one of 'yes', 'no', 'new'
268     # Do the recipients (To:, Cc:) of a message get placed on the nosy list?
269     # If 'new' is used, then the recipients will only be added when a message
270     # creates a new issue. If 'yes', then the recipients will be added on followups
271     # too. If 'no', they're never added to the nosy.
272     ADD_RECIPIENTS_TO_NOSY = 'new'      # either 'yes', 'no', 'new'
274     # Where to place the email signature
275     EMAIL_SIGNATURE_POSITION = 'bottom' # one of 'top', 'bottom', 'none'
277     # Keep email citations
278     EMAIL_KEEP_QUOTED_TEXT = 'no'       # either 'yes' or 'no'
280     # Preserve the email body as is
281     EMAIL_LEAVE_BODY_UNCHANGED = 'no'   # either 'yes' or 'no'
283     # Default class to use in the mailgw if one isn't supplied in email
284     # subjects. To disable, comment out the variable below or leave it blank.
285     # Examples:
286     MAIL_DEFAULT_CLASS = 'issue'   # use "issue" class by default
287     #MAIL_DEFAULT_CLASS = ''        # disable (or just comment the var out)
289 Tracker Schema
290 --------------
292 Note: if you modify the schema, you'll most likely need to edit the
293       `web interface`_ HTML template files and `detectors`_ to reflect
294       your changes.
296 A tracker schema defines what data is stored in the tracker's database. The
297 two schemas shipped with Roundup turn it into a typical software bug tracker
298 (the extended schema allowing for support issues as well as bugs). Schemas are
299 defined using Python code. The "classic" schema looks like this::
301     pri = Class(db, "priority", name=String(), order=String())
302     pri.setkey("name")
303     pri.create(name="critical", order="1")
304     pri.create(name="urgent", order="2")
305     pri.create(name="bug", order="3")
306     pri.create(name="feature", order="4")
307     pri.create(name="wish", order="5")
309     stat = Class(db, "status", name=String(), order=String())
310     stat.setkey("name")
311     stat.create(name="unread", order="1")
312     stat.create(name="deferred", order="2")
313     stat.create(name="chatting", order="3")
314     stat.create(name="need-eg", order="4")
315     stat.create(name="in-progress", order="5")
316     stat.create(name="testing", order="6")
317     stat.create(name="done-cbb", order="7")
318     stat.create(name="resolved", order="8")
320     keyword = Class(db, "keyword", name=String())
321     keyword.setkey("name")
323     user = Class(db, "user", username=String(), password=String(),
324         address=String(), realname=String(), phone=String(),
325         organisation=String())
326     user.setkey("username")
327     user.create(username="admin", password=adminpw,
328         address=config.ADMIN_EMAIL)
330     msg = FileClass(db, "msg", author=Link("user"), recipients=Multilink
331         ("user"), date=Date(), summary=String(), files=Multilink("file"))
333     file = FileClass(db, "file", name=String(), type=String())
335     issue = IssueClass(db, "issue", assignedto=Link("user"),
336         topic=Multilink("keyword"), priority=Link("priority"), status=Link
337         ("status"))
338     issue.setkey('title')
340 XXX security definitions
342 Classes and Properties - creating a new information store
343 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
345 In the tracker above, we've defined 7 classes of information:
347   priority
348       Defines the possible levels of urgency for issues.
350   status
351       Defines the possible states of processing the issue may be in.
353   keyword
354       Initially empty, will hold keywords useful for searching issues.
356   user
357       Initially holding the "admin" user, will eventually have an entry for all
358       users using roundup.
360   msg
361       Initially empty, will all e-mail messages sent to or generated by
362       roundup.
364   file
365       Initially empty, will all files attached to issues.
367   issue
368       Initially emtyp, this is where the issue information is stored.
370 We define the "priority" and "status" classes to allow two things: reduction in
371 the amount of information stored on the issue and more powerful, accurate
372 searching of issues by priority and status. By only requiring a link on the
373 issue (which is stored as a single number) we reduce the chance that someone
374 mis-types a priority or status - or simply makes a new one up.
376 Class and Nodes
377 :::::::::::::::
379 A Class defines a particular class (or type) of data that will be stored in the
380 database. A class comprises one or more properties, which given the information
381 about the class items.
382 The actual data entered into the database, using class.create() are called
383 items. They have a special immutable property called id. We sometimes refer to
384 this as the itemid.
386 Properties
387 ::::::::::
389 A Class is comprised of one or more properties of the following types:
390     * String properties are for storing arbitrary-length strings.
391     * Password properties are for storing encoded arbitrary-length strings. The
392       default encoding is defined on the roundup.password.Password class.
393     * Date properties store date-and-time stamps. Their values are Timestamp
394       objects.
395     * A Link property refers to a single other item selected from a specified
396       class. The class is part of the property; the value is an integer, the id
397       of the chosen item.
398     * A Multilink property refers to possibly many items in a specified class.
399       The value is a list of integers.
401 FileClass
402 :::::::::
404 FileClasses save their "content" attribute off in a separate file from the rest
405 of the database. This reduces the number of large entries in the database,
406 which generally makes databases more efficient, and also allows us to use
407 command-line tools to operate on the files. They are stored in the files sub-
408 directory of the db directory in your tracker.
410 IssueClass
411 ::::::::::
413 IssueClasses automatically include the "messages", "files", "nosy", and
414 "superseder" properties.
415 The messages and files properties list the links to the messages and files
416 related to the issue. The nosy property is a list of links to users who wish to
417 be informed of changes to the issue - they get "CC'ed" e-mails when messages
418 are sent to or generated by the issue. The nosy reactor (in the detectors
419 directory) handles this action. The superceder link indicates an issue which
420 has superceded this one.
421 They also have the dynamically generated "creation", "activity" and "creator"
422 properties.
423 The value of the "creation" property is the date when a item was created, and
424 the value of the "activity" property is the date when any property on the item
425 was last edited (equivalently, these are the dates on the first and last
426 records in the item's journal). The "creator" property holds a link to the user
427 that created the issue.
429 setkey(property)
430 ::::::::::::::::
432 Select a String property of the class to be the key property. The key property
433 muse be unique, and allows references to the items in the class by the content
434 of the key property. That is, we can refer to users by their username, e.g.
435 let's say that there's an issue in roundup, issue 23. There's also a user,
436 richard who happens to be user 2. To assign an issue to him, we could do either
437 of::
439      roundup-admin set issue assignedto=2
441 or::
443      roundup-admin set issue assignedto=richard
445 Note, the same thing can be done in the web and e-mail interfaces.
447 create(information)
448 :::::::::::::::::::
450 Create a item in the database. This is generally used to create items in the
451 "definitional" classes like "priority" and "status".
454 Examples of adding to your schema
455 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
457 TODO
460 Detectors - adding behaviour to your tracker
461 --------------------------------------------
462 .. _detectors:
464 The detectors in your tracker fire before (*auditors*) and after (*reactors*)
465 changes to the contents of your database. They are Python modules that sit in
466 your tracker's ``detectors`` directory. You will have some installed by
467 default - have a look. You can write new detectors or modify the existing
468 ones. The existing detectors installed for you are:
470 **nosyreaction.py**
471   This provides the automatic nosy list maintenance and email sending. The nosy
472   reactor (``nosyreaction``) fires when new messages are added to issues.
473   The nosy auditor (``updatenosy``) fires when issues are changed and figures
474   what changes need to be made to the nosy list (like adding new authors etc)
475 **statusauditor.py**
476   This provides the ``chatty`` auditor which changes the issue status from
477   ``unread`` or ``closed`` to ``chatting`` if new messages appear. It also
478   provides the ``presetunread`` auditor which pre-sets the status to
479   ``unread`` on new items if the status isn't explicitly defined.
481 See the detectors section in the `design document`__ for details of the
482 interface for detectors.
484 __ design.html
486 Sample additional detectors that have been found useful will appear in the
487 ``detectors`` directory of the Roundup distribution:
489 **newissuecopy.py**
490   This detector sends an email to a team address whenever a new issue is
491   created. The address is hard-coded into the detector, so edit it before you
492   use it (look for the text 'team@team.host') or you'll get email errors!
495 Database Content
496 ----------------
498 Note: if you modify the content of definitional classes, you'll most likely
499        need to edit the tracker `detectors`_ to reflect your changes.
501 Customisation of the special "definitional" classes (eg. status, priority,
502 resolution, ...) may be done either before or after the tracker is
503 initialised. The actual method of doing so is completely different in each
504 case though, so be careful to use the right one.
506 **Changing content before tracker initialisation**
507     Edit the dbinit module in your tracker to alter the items created in using
508     the create() methods.
511 **Changing content after tracker initialisation**
512     Use the roundup-admin interface's create, set and retire methods to add,
513     alter or remove items from the classes in question.
517 Web Interface
518 -------------
520 The web interface works behind the cgi-bin/roundup.cgi or roundup-server
521 scripts. In both cases, the scripts determine which tracker is being accessed
522 (the first part of the URL path inside the scope of the CGI handler) and pass
523 control on to the tracker interfaces.Client class which handles the rest of
524 the access through its main() method. This means that you can do pretty much
525 anything you want as a web interface to your tracker.
527 Figuring out what is displayed
528 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
530 Most customisation of the web view can be done by modifying the templates in
531 the tracker **html** directory. There are several types of files in there:
533 page
534   defines the overall look of your tracker. When you
535   view an issue, it appears inside this template. When you view an index, it
536   also appears inside this template.
537 home
538   the default page displayed when no other page is indicated by the user
539 home.classlist
540   a special version of the default page that lists the classes in the tracker
541 *classname*.item
542   displays an item of the *classname* class
543 *classname*.index
544   displays a list of *classname* items
545 *classname*.search
546   displays a search page for *classname* items
547 _generic.index
548   used to display a list of items where there is no *classname*.index available
549 _generic.help
550   used to display a "class help" page where there is no *classname*.help
551 user.register
552   a special page just for the user class that renders the registration page
553 style.css
554   a static file that is served up as-is
556 How requests are processed
557 ~~~~~~~~~~~~~~~~~~~~~~~~~~
559 The basic processing of a web request proceeds as follows:
561 1. figure out who we are, defaulting to the "anonymous" user
562 2. figure out what the request is for - we call this the "context"
563 3. handle any requested action (item edit, search, ...)
564 4. render a template, resulting in HTML output
566 In some situations, exceptions occur:
568 - HTTP Redirect  (generally raised by an action)
569 - SendFile       (generally raised by determine_context)
570   here we serve up a FileClass "content" property
571 - SendStaticFile (generally raised by determine_context)
572   here we serve up a file from the tracker "html" directory
573 - Unauthorised   (generally raised by an action)
574   here the action is cancelled, the request is rendered and an error
575   message is displayed indicating that permission was not
576   granted for the action to take place
577 - NotFound       (raised wherever it needs to be)
578   this exception percolates up to the CGI interface that called the client
580 Determining web context
581 ~~~~~~~~~~~~~~~~~~~~~~~
583 To determine the "context" of a request, we look at the URL and the special
584 request variable ``:template``. The URL path after the tracker identifier
585 is examined. Typical URL paths look like:
587 1.  ``/tracker/issue``
588 2.  ``/tracker/issue1``
589 3.  ``/tracker/_file/style.css``
590 4.  ``/cgi-bin/roundup.cgi/tracker/file1``
591 5.  ``/cgi-bin/roundup.cgi/tracker/file1/kitten.png``
593 where the "tracker identifier" is "tracker" in the above cases. That means
594 we're looking at "issue", "issue1", "_file/style.css", "file1" and
595 "file1/kitten.png" in the cases above. The path is generally only one
596 entry long - longer paths are handled differently.
598 a. if there is no path, then we are in the "home" context.
599 b. if the path starts with "_file" (as in example 3,
600    "/tracker/_file/style.css"), then the additional path entry,
601    "style.css" specifies the filename of a static file we're to serve up
602    from the tracker "html" directory. Raises a SendStaticFile
603    exception.
604 c. if there is something in the path (as in example 1, "issue"), it identifies
605    the tracker class we're to display.
606 d. if the path is an item designator (as in examples 2 and 4, "issue1" and
607    "file1"), then we're to display a specific item.
608 e. if the path starts with an item designator and is longer than
609    one entry (as in example 5, "file1/kitten.png"), then we're assumed
610    to be handling an item of a
611    FileClass, and the extra path information gives the filename
612    that the client is going to label the download with (ie
613    "file1/kitten.png" is nicer to download than "file1"). This
614    raises a SendFile exception.
616 Both b. and e. stop before we bother to
617 determine the template we're going to use. That's because they
618 don't actually use templates.
620 The template used is specified by the ``:template`` CGI variable,
621 which defaults to:
623 - only classname suplied:          "index"
624 - full item designator supplied:   "item"
627 Performing actions in web requests
628 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
630 When a user requests a web page, they may optionally also request for an
631 action to take place. As described in `how requests are processed`_, the
632 action is performed before the requested page is generated. Actions are
633 triggered by using a ``:action`` CGI variable, where the value is one of:
635 login
636  Attempt to log a user in.
637 logout
638  Log the user out - make them "anonymous".
639 register
640  Attempt to create a new user based on the contents of the form and then log
641  them in.
642 edit
643  Perform an edit of an item in the database. There are some special form
644  elements you may use:
646  :link=designator:property and :multilink=designator:property
647   The value specifies an item designator and the property on that
648   item to add _this_ item to as a link or multilink.
649  :note
650   Create a message and attach it to the current item's
651   "messages" property.
652  :file
653   Create a file and attach it to the current item's
654   "files" property. Attach the file to the message created from
655   the __note if it's supplied.
656  :required=property,property,...
657   The named properties are required to be filled in the form.
659 new
660  Add a new item to the database. You may use the same special form elements
661  as in the "edit" action.
663 editCSV
664  Performs an edit of all of a class' items in one go. See also the
665  *class*.csv templating method which generates the CSV data to be edited, and
666  the "_generic.index" template which uses both of these features.
668 search
669  Mangle some of the form variables.
671  Set the form ":filter" variable based on the values of the
672  filter variables - if they're set to anything other than
673  "dontcare" then add them to :filter.
675  Also handle the ":queryname" variable and save off the query to
676  the user's query list.
678 Each of the actions is implemented by a corresponding *actionAction* (where
679 "action" is the name of the action) method on
680 the roundup.cgi.Client class, which also happens to be in your tracker as
681 interfaces.Client. So if you need to define new actions, you may add them
682 there (see `definining new web actions`_).
684 Each action also has a corresponding *actionPermission* (where
685 "action" is the name of the action) method which determines
686 whether the action is permissible given the current user. The base permission
687 checks are:
689 login
690  Determine whether the user has permission to log in.
691  Base behaviour is to check the user has "Web Access".
692 logout
693  No permission checks are made.
694 register
695  Determine whether the user has permission to register
696  Base behaviour is to check the user has "Web Registration".
697 edit
698  Determine whether the user has permission to edit this item.
699  Base behaviour is to check the user can edit this class. If we're
700  editing the "user" class, users are allowed to edit their own
701  details. Unless it's the "roles" property, which requires the
702  special Permission "Web Roles".
703 new
704  Determine whether the user has permission to create (edit) this item.
705  Base behaviour is to check the user can edit this class. No
706  additional property checks are made. Additionally, new user items
707  may be created if the user has the "Web Registration" Permission.
708 editCSV
709  Determine whether the user has permission to edit this class.
710  Base behaviour is to check the user can edit this class.
711 search
712  Determine whether the user has permission to search this class.
713  Base behaviour is to check the user can view this class.
716 Repurcussions of changing the tracker schema
717 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
719 If you choose to change the `tracker schema`_ you will need to ensure the web
720 interface knows about it:
722 1. Index, item and search pages for the relevant classes may need to have
723    properties added or removed,
724 2. The "page" template may require links to be changed, as might the "home"
725    page's content arguments.
727 Overall Look - "page" template
728 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
730 XXX
732 How the templates work
733 ~~~~~~~~~~~~~~~~~~~~~~
735 Roundup's templates consist of special attributes on your template tags. These
736 attributes form the Template Attribute Language, or TAL. The commands are:
739 tal:define="variable expression; variable expression; ..."
740    Define a new variable that is local to this tag and its contents. For
741    example::
743       <html tal:define="title request/description">
744        <head><title tal:content="title"></title></head>
745       </html>
747    In the example, the variable "title" is defined as being the result of the
748    expression "request/description". The tal:content command inside the <html>
749    tag may then use the "title" variable.
751 tal:condition="expression"
752    Only keep this tag and its contents if the expression is true. For example::
754      <p tal:condition="python:request.user.hasPermission('View', 'issue')">
755       Display some issue information.
756      </p>
758    In the example, the <p> tag and its contents are only displayed if the
759    user has the View permission for issues. We consider the number zero, a
760    blank string, an empty list, and the built-in variable nothing to be false
761    values. Nearly every other value is true, including non-zero numbers, and
762    strings with anything in them (even spaces!).
764 tal:repeat="variable expression"
765    Repeat this tag and its contents for each element of the sequence that the
766    expression returns, defining a new local variable and a special "repeat"
767    variable for each element. For example::
769      <tr tal:repeat="u user/list">
770       <td tal:content="u/id"></td>
771       <td tal:content="u/username"></td>
772       <td tal:content="u/realname"></td>
773      </tr>
775    The example would iterate over the sequence of users returned by
776    "user/list" and define the local variable "u" for each entry.
778 tal:replace="expression"
779    Replace this tag with the result of the expression. For example::
781     <span tal:replace="request/user/realname"></span>
783    The example would replace the <span> tag and its contents with the user's
784    realname. If the user's realname was "Bruce" then the resultant output
785    would be "Bruce".
787 tal:content="expression"
788    Replace the contents of this tag with the result of the expression. For
789    example::
791     <span tal:content="request/user/realname">user's name appears here</span>
793    The example would replace the contents of the <span> tag with the user's
794    realname. If the user's realname was "Bruce" then the resultant output
795    would be "<span>Bruce</span>".
797 tal:attributes="attribute expression; attribute expression; ..."
798    Set attributes on this tag to the results of expressions. For example::
800      <a tal:attributes="href string:user${request/user/id}">My Details</a>
802    In the example, the "href" attribute of the <a> tag is set to the value of
803    the "string:user${request/user/id}" expression, which will be something
804    like "user123".
806 tal:omit-tag="expression"
807    Remove this tag (but not its contents) if the expression is true. For
808    example::
810       <span tal:omit-tag="python:1">Hello, world!</span>
812    would result in output of::
814       Hello, world!
816 Note that the commands on a given tag are evaulated in the order above, so
817 *define* comes before *condition*, and so on.
819 Additionally, a tag is defined, tal:block, which is removed from output. Its
820 content is not, but the tag itself is (so don't go using any tal:attributes
821 commands on it). This is useful for making arbitrary blocks of HTML
822 conditional or repeatable (very handy for repeating multiple table rows,
823 which would othewise require an illegal tag placement to effect the repeat).
825 The expressions you may use in the attibute values may be one of the following
826 three forms:
828 Path Expressions - eg. ``item/status/checklist``
829    These are object attribute / item accesses. Roughly speaking, the path
830    ``item/status/checklist`` is broken into parts ``item``, ``status``
831    and ``checklist``. The ``item`` part is the root of the expression.
832    We then look for a ``status`` attribute on ``item``, or failing that, a
833    ``status`` item (as in ``item['status']``). If that
834    fails, the path expression fails. When we get to the end, the object we're
835    left with is evaluated to get a string - methods are called, objects are
836    stringified. Path expressions may have an optional ``path:`` prefix, though
837    they are the default expression type, so it's not necessary.
839    XXX | components of expressions
841    XXX "nothing" and "default"
843 String Expressions - eg. ``string:hello ${user/name}``
844    These expressions are simple string interpolations (though they can be just
845    plain strings with no interpolation if you want. The expression in the
846    ``${ ... }`` is just a path expression as above.
848 Python Expressions - eg. ``python: 1+1``
849    These expressions give the full power of Python. All the "root level"
850    variables are available, so ``python:item.status.checklist()`` would be
851    equivalent to ``item/status/checklist``, assuming that ``checklist`` is
852    a method.
854 Information available to templates
855 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
857 The following variables are available to templates.
859 .. taken from roundup.cgi.templating.RoundupPageTemplate docstring
861 *context*
862   The current context. This is either None, a wrapper around a
863   hyperdb class (an HTMLClass) or a wrapper around a hyperdb item (an
864   HTMLItem).
865 *request*
866   Includes information about the current request, including:
867    - the url
868    - the current index information (``filterspec``, ``filter`` args,
869      ``properties``, etc) parsed out of the form. 
870    - methods for easy filterspec link generation
871    - *user*, the current user item as an HTMLItem instance
872    - *form*
873      The current CGI form information as a mapping of form argument
874      name to value
875 *tracker*
876   The current tracker
877 *db*
878   The current database, through which db.config may be reached.
879 *nothing*
880   XXX a special variable
881 *default*
882   XXX a special variable
884 The context variable
885 ::::::::::::::::::::
887 The *context* variable is one of three things based on the current context
888 (see `determining web context`_ for how we figure this out):
890 1. if we're looking at a "home" page, then it's None
891 2. if we're looking at a specific hyperdb class, it's an HTMLClass instance
892 3. if we're looking at a specific hyperdb item, it's an HTMLItem instance
894 If the context is not None, we can access the properties of the class or item.
895 The only real difference between cases 2 and 3 above are:
897 1. the properties may have a real value behind them, and this will appear if
898    the property is displayed through ``context/property`` or
899    ``context/property/field``.
900 2. the context's "id" property will be a false value in the second case, but
901    a real, or true value in the third. Thus we can determine whether we're
902    looking at a real item from the hyperdb by testing "context/id".
905 The request variable
906 ::::::::::::::::::::
908 The request variable is packed with information about the current request.
910 .. taken from roundup.cgi.templating.HTMLRequest docstring
912 =========== ================================================================
913 Variable    Holds
914 =========== ================================================================
915 form        the CGI form as a cgi.FieldStorage
916 env         the CGI environment variables
917 url         the current URL path for this request
918 base        the base URL for this tracker
919 user        a HTMLUser instance for this user
920 classname   the current classname (possibly None)
921 template    the current template (suffix, also possibly None)
922 form        the current CGI form variables in a FieldStorage
923 =========== ================================================================
925 **Index page specific variables (indexing arguments)**
927 =========== ================================================================
928 Variable    Holds
929 =========== ================================================================
930 columns     dictionary of the columns to display in an index page
931 show        a convenience access to columns - request/show/colname will
932               be true if the columns should be displayed, false otherwise
933 sort        index sort column (direction, column name)
934 group       index grouping property (direction, column name)
935 filter      properties to filter the index on
936 filterspec  values to filter the index on
937 search_text text to perform a full-text search on for an index
938 =========== ================================================================
941 Displaying Properties
942 ~~~~~~~~~~~~~~~~~~~~~
944 Properties appear in the user interface in three contexts: in indices, in
945 editors, and as search arguments.
946 For each type of property, there are several display possibilities.
947 For example, in an index view, a string property may just be
948 printed as a plain string, but in an editor view, that property may be
949 displayed in an editable field.
952 Index Views
953 ~~~~~~~~~~~
955 This is one of the class context views. It is also the default view for
956 classes. The template used is "*classname*.index".
958 Index View Specifiers
959 :::::::::::::::::::::
961 An index view specifier (URL fragment) looks like this (whitespace has been
962 added for clarity)::
964      /issue?status=unread,in-progress,resolved&
965             topic=security,ui&
966             :group=+priority&
967             :sort=-activity&
968             :filters=status,topic&
969             :columns=title,status,fixer
971 The index view is determined by two parts of the specifier: the layout part and
972 the filter part. The layout part consists of the query parameters that begin
973 with colons, and it determines the way that the properties of selected items
974 are displayed. The filter part consists of all the other query parameters, and
975 it determines the criteria by which items are selected for display.
976 The filter part is interactively manipulated with the form widgets displayed in
977 the filter section. The layout part is interactively manipulated by clicking on
978 the column headings in the table.
980 The filter part selects the union of the sets of items with values matching any
981 specified Link properties and the intersection of the sets of items with values
982 matching any specified Multilink properties.
984 The example specifies an index of "issue" items. Only items with a "status" of
985 either "unread" or "in-progres" or "resolved" are displayed, and only items
986 with "topic" values including both "security" and "ui" are displayed. The items
987 are grouped by priority, arranged in ascending order; and within groups, sorted
988 by activity, arranged in descending order. The filter section shows filters for
989 the "status" and "topic" properties, and the table includes columns for the
990 "title", "status", and "fixer" properties.
992 Filtering of indexes
993 ::::::::::::::::::::
995 TODO
997 Searching Views
998 ~~~~~~~~~~~~~~~
1000 This is one of the class context views. The template used is typically
1001 "*classname*.search".
1003 TODO
1005 Item Views
1006 ~~~~~~~~~~
1008 The basic view of a hyperdb item is provided by the "*classname*.item"
1009 template. It generally has three sections; an "editor", a "spool" and a
1010 "history" section.
1014 Editor Section
1015 ::::::::::::::
1017 The editor section is used to manipulate the item - it may be a
1018 static display if the user doesn't have permission to edit the item.
1020 Here's an example of a basic editor template (this is the default "classic"
1021 template issue item edit form - from the "issue.item" template)::
1023  <table class="form">
1024  <tr>
1025   <th nowrap>Title</th>
1026   <td colspan=3 tal:content="structure python:context.title.field(size=60)">title</td>
1027  </tr>
1028  
1029  <tr>
1030   <th nowrap>Priority</th>
1031   <td tal:content="structure context/priority/menu">priority</td>
1032   <th nowrap>Status</th>
1033   <td tal:content="structure context/status/menu">status</td>
1034  </tr>
1035  
1036  <tr>
1037   <th nowrap>Superseder</th>
1038   <td>
1039    <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" />
1040    <span tal:replace="structure python:db.issue.classhelp('id,title', label='list', width=500)" />
1041    <span tal:condition="context/superseder">
1042     <br>View: <span tal:replace="structure python:context.superseder.link(showid=1)" />
1043    </span>
1044   </td>
1045   <th nowrap>Nosy List</th>
1046   <td>
1047    <span tal:replace="structure context/nosy/field" />
1048    <span tal:replace="structure python:db.user.classhelp('username,realname,address,phone', label='list', width=500)" />
1049   </td>
1050  </tr>
1051  
1052  <tr>
1053   <th nowrap>Assigned To</th>
1054   <td tal:content="structure context/assignedto/menu">
1055    assignedto menu
1056   </td>
1057   <td>&nbsp;</td>
1058   <td>&nbsp;</td>
1059  </tr>
1060  
1061  <tr>
1062   <th nowrap>Change Note</th>
1063   <td colspan=3>
1064    <textarea name=":note" wrap="hard" rows="5" cols="60"></textarea>
1065   </td>
1066  </tr>
1067  
1068  <tr>
1069   <th nowrap>File</th>
1070   <td colspan=3><input type="file" name=":file" size="40"></td>
1071  </tr>
1072  
1073  <tr>
1074   <td>&nbsp;</td>
1075   <td colspan=3 tal:content="structure context/submit">
1076    submit button will go here
1077   </td>
1078  </tr>
1079  </table>
1082 When a change is submitted, the system automatically generates a message
1083 describing the changed properties. As shown in the example, the editor
1084 template can use the ":note" and ":file" fields, which are added to the
1085 standard change note message generated by Roundup.
1087 Spool Section
1088 :::::::::::::
1090 The spool section lists related information like the messages and files of
1091 an issue.
1093 TODO
1096 History Section
1097 :::::::::::::::
1099 The final section displayed is the history of the item - its database journal.
1100 This is generally generated with the template::
1102  <tal:block tal:replace="structure context/history" />
1104 *To be done:*
1106 *The actual history entries of the item may be accessed for manual templating
1107 through the "journal" method of the item*::
1109  <tal:block tal:repeat="entry context/journal">
1110   a journal entry
1111  </tal:block>
1113 *where each journal entry is an HTMLJournalEntry.*
1116 Access Controls
1117 ---------------
1119 A set of Permissions are built in to the security module by default:
1121 - Edit (everything)
1122 - View (everything)
1124 The default interfaces define:
1126 - Web Registration
1127 - Web Access
1128 - Web Roles
1129 - Email Registration
1130 - Email Access
1132 These are hooked into the default Roles:
1134 - Admin (Edit everything, View everything, Web Roles)
1135 - User (Web Access, Email Access)
1136 - Anonymous (Web Registration, Email Registration)
1138 And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user
1139 gets the "Anonymous" assigned when the database is initialised on installation.
1140 The two default schemas then define:
1142 - Edit issue, View issue (both)
1143 - Edit file, View file (both)
1144 - Edit msg, View msg (both)
1145 - Edit support, View support (extended only)
1147 and assign those Permissions to the "User" Role. New users are assigned the
1148 Roles defined in the config file as:
1150 - NEW_WEB_USER_ROLES
1151 - NEW_EMAIL_USER_ROLES
1153 You may alter the configuration variables to change the Role that new web or
1154 email users get, for example to not give them access to the web interface if
1155 they register through email.
1157 You may use the ``roundup-admin`` "``security``" command to display the
1158 current Role and Permission configuration in your tracker.
1160 Adding a new Permission
1161 ~~~~~~~~~~~~~~~~~~~~~~~
1163 When adding a new Permission, you will need to:
1165 1. add it to your tracker's dbinit so it is created
1166 2. enable it for the Roles that should have it (verify with
1167    "``roundup-admin security``")
1168 3. add it to the relevant HTML interface templates
1169 4. add it to the appropriate xxxPermission methods on in your tracker
1170    interfaces module
1174 -----------------
1176 Back to `Table of Contents`_
1178 .. _`Table of Contents`: index.html