Code

More documentation.
[roundup.git] / doc / customizing.txt
1 ===================
2 Customising Roundup
3 ===================
5 :Version: $Revision: 1.20 $
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. `instance configuration`_ file changes
19 2. database, or `instance 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 instance has been initialised or not. The other two
26 may be done at any time, before or after instance initialisation. Yes, this
27 includes adding or removing properties from classes.
30 Instances in a Nutshell
31 -----------------------
33 Instances have the following structure:
35 +-------------------+--------------------------------------------------------+
36 |instance_config.py |Holds the basic instance_configuration                  |
37 +-------------------+--------------------------------------------------------+
38 |dbinit.py          |Holds the instance_schema                               |
39 +-------------------+--------------------------------------------------------+
40 |interfaces.py      |Defines the Web and E-Mail interfaces for the instance  |
41 +-------------------+--------------------------------------------------------+
42 |select_db.py       |Selects the database back-end for the instance          |
43 +-------------------+--------------------------------------------------------+
44 |db/                |Holds the instance's database                           |
45 +-------------------+--------------------------------------------------------+
46 |db/files/          |Holds the instance's upload files and messages          |
47 +-------------------+--------------------------------------------------------+
48 |detectors/         |Auditors and reactors for this instance                 |
49 +-------------------+--------------------------------------------------------+
50 |html/              |Web interface templates, images and style sheets        |
51 +-------------------+--------------------------------------------------------+
53 Instance Configuration
54 ----------------------
56 The instance_config.py located in your instance 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 instance home directory. The above default code will automatically
62  determine the instance 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 instance 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 instance home.
78 **INSTANCE_NAME** - ``'Roundup issue tracker'``
79  A descriptive name for your roundup instance. 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  instance's personal e-mail address.
86 **ISSUE_TRACKER_WEB** - ``'http://your.tracker.url.example/'``
87  The web address that the instance 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 node 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 nodes 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 instance_config.py is given below - as you
210 can see, the MAIL_DOMAIN must be edited before any interaction with the
211 instance 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 instance
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 instance 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     # Define what index links are available in the header, and what their
290     # labels are. Each key is used to look up one of the index specifications
291     # below - so 'DEFAULT' will use 'DEFAULT_INDEX'.
292     # Where the FILTERSPEC has 'assignedto' with a value of None, it will be
293     # replaced by the id of the logged-in user.
294     HEADER_INDEX_LINKS = ['DEFAULT', 'UNASSIGNED', 'USER']
296     # list the classes that users are able to add nodes to
297     HEADER_ADD_LINKS = ['issue']
299     # list the classes that users can search
300     HEADER_SEARCH_LINKS = ['issue']
302     # list search filters per class
303     SEARCH_FILTERS = ['ISSUE_FILTER', 'SUPPORT_FILTER']
305     # Now the DEFAULT display specification. TODO: describe format
306     DEFAULT_INDEX = {
307       'LABEL': 'All Issues',
308       'CLASS': 'issue',
309       'SORT': ['-activity'],
310       'GROUP': ['priority'],
311       'FILTER': ['status'],
312       'COLUMNS': ['id','activity','title','creator','assignedto'],
313       'FILTERSPEC': {
314         'status': ['-1', '1', '2', '3', '4', '5', '6', '7'],
315       },
316     }
318     # The "unsassigned issues" index
319     UNASSIGNED_INDEX = {
320       'LABEL': 'Unassigned Issues',
321       'CLASS': 'issue',
322       'SORT': ['-activity'],
323       'GROUP': ['priority'],
324       'FILTER': ['status', 'assignedto'],
325       'COLUMNS': ['id','activity','title','creator','status'],
326       'FILTERSPEC': {
327         'status': ['-1', '1', '2', '3', '4', '5', '6', '7'],
328         'assignedto': ['-1'],
329       },
330     }
332     # The "my issues" index -- note that the user's id will replace the
333     # 'CURRENT USER' value of the "assignedto" filterspec
334     USER_INDEX = {
335       'LABEL': 'My Issues',
336       'CLASS': 'issue',
337       'SORT': ['-activity'],
338       'GROUP': ['priority'],
339       'FILTER': ['status', 'assignedto'],
340       'COLUMNS': ['id','activity','title','creator','status'],
341       'FILTERSPEC': {
342         'status': ['-1', '1', '2', '3', '4', '5', '6', '7'],
343         'assignedto': 'CURRENT USER',
344       },
345     }
347     ISSUE_FILTER = {
348       'CLASS': 'issue',
349       'FILTER': ['status', 'priority', 'assignedto', 'creator']
350     }
352     SUPPORT_FILTER = {
353       'CLASS': 'issue',
354       'FILTER': ['status', 'priority', 'assignedto', 'creator']
355     }
358 Instance Schema
359 ---------------
361 Note: if you modify the schema, you'll most likely need to edit the
362       `web interface`_ HTML template files and `detectors`_ to reflect
363       your changes.
365 An instance schema defines what data is stored in the instance's database. The
366 two schemas shipped with Roundup turn it into a typical software bug tracker
367 (the extended schema allowing for support issues as well as bugs). Schemas are
368 defined using Python code. The "classic" schema looks like this::
370     pri = Class(db, "priority", name=String(), order=String())
371     pri.setkey("name")
372     pri.create(name="critical", order="1")
373     pri.create(name="urgent", order="2")
374     pri.create(name="bug", order="3")
375     pri.create(name="feature", order="4")
376     pri.create(name="wish", order="5")
378     stat = Class(db, "status", name=String(), order=String())
379     stat.setkey("name")
380     stat.create(name="unread", order="1")
381     stat.create(name="deferred", order="2")
382     stat.create(name="chatting", order="3")
383     stat.create(name="need-eg", order="4")
384     stat.create(name="in-progress", order="5")
385     stat.create(name="testing", order="6")
386     stat.create(name="done-cbb", order="7")
387     stat.create(name="resolved", order="8")
389     keyword = Class(db, "keyword", name=String())
390     keyword.setkey("name")
392     user = Class(db, "user", username=String(), password=String(),
393         address=String(), realname=String(), phone=String(),
394         organisation=String())
395     user.setkey("username")
396     user.create(username="admin", password=adminpw,
397         address=instance_config.ADMIN_EMAIL)
399     msg = FileClass(db, "msg", author=Link("user"), recipients=Multilink
400         ("user"), date=Date(), summary=String(), files=Multilink("file"))
402     file = FileClass(db, "file", name=String(), type=String())
404     issue = IssueClass(db, "issue", assignedto=Link("user"),
405         topic=Multilink("keyword"), priority=Link("priority"), status=Link
406         ("status"))
407     issue.setkey('title')
409 Classes and Properties - creating a new information store
410 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
412 In the instance above, we've defined 7 classes of information:
414   priority
415       Defines the possible levels of urgency for issues.
417   status
418       Defines the possible states of processing the issue may be in.
420   keyword
421       Initially empty, will hold keywords useful for searching issues.
423   user
424       Initially holding the "admin" user, will eventually have an entry for all
425       users using roundup.
427   msg
428       Initially empty, will all e-mail messages sent to or generated by
429       roundup.
431   file
432       Initially empty, will all files attached to issues.
434   issue
435       Initially emtyp, this is where the issue information is stored.
437 We define the "priority" and "status" classes to allow two things: reduction in
438 the amount of information stored on the issue and more powerful, accurate
439 searching of issues by priority and status. By only requiring a link on the
440 issue (which is stored as a single number) we reduce the chance that someone
441 mis-types a priority or status - or simply makes a new one up.
443 Class and Nodes
444 :::::::::::::::
446 A Class defines a particular class (or type) of data that will be stored in the
447 database. A class comprises one or more properties, which given the information
448 about the class nodes.
449 The actual data entered into the database, using class.create() are called
450 nodes. They have a special immutable property called id. We sometimes refer to
451 this as the nodeid.
453 Properties
454 ::::::::::
456 A Class is comprised of one or more properties of the following types:
457     * String properties are for storing arbitrary-length strings.
458     * Password properties are for storing encoded arbitrary-length strings. The
459       default encoding is defined on the roundup.password.Password class.
460     * Date properties store date-and-time stamps. Their values are Timestamp
461       objects.
462     * A Link property refers to a single other node selected from a specified
463       class. The class is part of the property; the value is an integer, the id
464       of the chosen node.
465     * A Multilink property refers to possibly many nodes in a specified class.
466       The value is a list of integers.
468 FileClass
469 :::::::::
471 FileClasses save their "content" attribute off in a separate file from the rest
472 of the database. This reduces the number of large entries in the database,
473 which generally makes databases more efficient, and also allows us to use
474 command-line tools to operate on the files. They are stored in the files sub-
475 directory of the db directory in your instance.
477 IssueClass
478 ::::::::::
480 IssueClasses automatically include the "messages", "files", "nosy", and
481 "superseder" properties.
482 The messages and files properties list the links to the messages and files
483 related to the issue. The nosy property is a list of links to users who wish to
484 be informed of changes to the issue - they get "CC'ed" e-mails when messages
485 are sent to or generated by the issue. The nosy reactor (in the detectors
486 directory) handles this action. The superceder link indicates an issue which
487 has superceded this one.
488 They also have the dynamically generated "creation", "activity" and "creator"
489 properties.
490 The value of the "creation" property is the date when a node was created, and
491 the value of the "activity" property is the date when any property on the node
492 was last edited (equivalently, these are the dates on the first and last
493 records in the node's journal). The "creator" property holds a link to the user
494 that created the issue.
496 setkey(property)
497 ::::::::::::::::
499 Select a String property of the class to be the key property. The key property
500 muse be unique, and allows references to the nodes in the class by the content
501 of the key property. That is, we can refer to users by their username, e.g.
502 let's say that there's an issue in roundup, issue 23. There's also a user,
503 richard who happens to be user 2. To assign an issue to him, we could do either
504 of::
506      roundup-admin set issue assignedto=2
508 or::
510      roundup-admin set issue assignedto=richard
512 Note, the same thing can be done in the web and e-mail interfaces.
514 create(information)
515 :::::::::::::::::::
517 Create a node in the database. This is generally used to create nodes in the
518 "definitional" classes like "priority" and "status".
521 Examples of adding to your schema
522 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
524 TODO
527 Detectors - adding behaviour to your tracker
528 --------------------------------------------
529 .. _detectors:
531 The detectors in your instance fire before (*auditors*) and after (*reactors*)
532 changes to the contents of your database. They are Python modules that sit in
533 your instance's ``detectors`` directory. You will have some installed by
534 default - have a look. You can write new detectors or modify the existing
535 ones. The existing detectors installed for you are:
537 **nosyreaction.py**
538   This provides the automatic nosy list maintenance and email sending. The nosy
539   reactor (``nosyreaction``) fires when new messages are added to issues.
540   The nosy auditor (``updatenosy``) fires when issues are changed and figures
541   what changes need to be made to the nosy list (like adding new authors etc)
542 **statusauditor.py**
543   This provides the ``chatty`` auditor which changes the issue status from
544   ``unread`` or ``closed`` to ``chatting`` if new messages appear. It also
545   provides the ``presetunread`` auditor which pre-sets the status to
546   ``unread`` on new nodes if the status isn't explicitly defined.
548 See the detectors section in the `design document`__ for details of the
549 interface for detectors.
551 __ design.html
553 Sample additional detectors that have been found useful will appear in the
554 ``detectors`` directory of the Roundup distribution:
556 **newissuecopy.py**
557   This detector sends an email to a team address whenever a new issue is
558   created. The address is hard-coded into the detector, so edit it before you
559   use it (look for the text 'team@team.host') or you'll get email errors!
562 Database Content
563 ----------------
565 Note: if you modify the content of definitional classes, you'll most likely
566        need to edit the instance `detectors`_ to reflect your changes.
568 Customisation of the special "definitional" classes (eg. status, priority,
569 resolution, ...) may be done either before or after the instance is
570 initialised. The actual method of doing so is completely different in each
571 case though, so be careful to use the right one.
573 **Changing content before instance initialisation**
574     Edit the dbinit module in your instance to alter the nodes created in using
575     the create() methods.
578 **Changing content after instance initialisation**
579     Use the roundup-admin interface's create, set and retire methods to add,
580     alter or remove nodes from the classes in question.
584 Web Interface
585 -------------
587 The web interface works behind the cgi-bin/roundup.cgi or roundup-server
588 scripts. In both cases, the scripts determine which instance is being accessed
589 (the first part of the URL path inside the scope of the CGI handler) and pass
590 control on to the instance interfaces.Client class which handles the rest of
591 the access through its main() method. This means that you can do pretty much
592 anything you want as a web interface to your instance.
594 Figuring out what is displayed
595 ::::::::::::::::::::::::::::::
597 Most customisation of the web view can be done by modifying the templates in
598 the instance **html** directory. There are several types of files in there:
600 page
601   defines the overall look of your tracker. When you
602   view an issue, it appears inside this template. When you view an index, it
603   also appears inside this template.
604 home
605   the default page displayed when no other page is indicated by the user
606 home.classlist
607   a special version of the default page that lists the classes in the tracker
608 *classname*.item
609   displays an item of the *classname* class
610 *classname*.index
611   displays a list of *classname* items
612 *classname*.search
613   displays a search page for *classname* items
614 _generic.index
615   used to display a list of items where there is no *classname*.index available
616 user.register
617   a special page just for the user class that renders the registration page
618 style.css
619   a static file that is served up as-is
621 How requests are processed
622 ::::::::::::::::::::::::::
624 The basic processing of a web request proceeds as follows:
626 1. figure out who we are, defaulting to the "anonymous" user
627 2. figure out what the request is for - we call this the "context"
628 3. handle any requested action (item edit, search, ...)
629 4. render a template, resulting in HTML output
631 In some situations, exceptions occur:
633 - HTTP Redirect  (generally raised by an action)
634 - SendFile       (generally raised by determine_context)
635   here we serve up a FileClass "content" property
636 - SendStaticFile (generally raised by determine_context)
637   here we serve up a file from the tracker "html" directory
638 - Unauthorised   (generally raised by an action)
639   here the action is cancelled, the request is rendered and an error
640   message is displayed indicating that permission was not
641   granted for the action to take place
642 - NotFound       (raised wherever it needs to be)
643   this exception percolates up to the CGI interface that called the client
645 Determining web context
646 :::::::::::::::::::::::
648 To determine the "context" of a request, we look at the URL and the special
649 request variable ``:template``. The URL path after the instance identifier
650 is examined. Typical URL paths look like:
652 1.  ``/tracker/issue``
653 2.  ``/tracker/issue1``
654 3.  ``/tracker/_file/style.css``
655 4.  ``/cgi-bin/roundup.cgi/tracker/file1``
656 5.  ``/cgi-bin/roundup.cgi/tracker/file1/kitten.png``
658 where the "instance identifier" is "tracker" in the above cases. That means
659 we're looking at "issue", "issue1", "_file/style.css", "file1" and
660 "file1/kitten.png" in the cases above. The path is generally only one
661 entry long - longer paths are handled differently.
663 a. if there is no path, then we are in the "home" context.
664 b. if the path starts with "_file" (as in example 3,
665    "/tracker/_file/style.css"), then the additional path entry,
666    "style.css" specifies the filename of a static file we're to serve up
667    from the instance "html" directory. Raises a SendStaticFile
668    exception.
669 c. if there is something in the path (as in example 1, "issue"), it identifies
670    the tracker class we're to display.
671 d. if the path is an item designator (as in examples 2 and 4, "issue1" and
672    "file1"), then we're to display a specific item.
673 e. if the path starts with an item designator and is longer than
674    one entry (as in example 5, "file1/kitten.png"), then we're assumed
675    to be handling an item of a
676    FileClass, and the extra path information gives the filename
677    that the client is going to label the download with (ie
678    "file1/kitten.png" is nicer to download than "file1"). This
679    raises a SendFile exception.
681 Both b. and e. stop before we bother to
682 determine the template we're going to use. That's because they
683 don't actually use templates.
685 The template used is specified by the ``:template`` CGI variable,
686 which defaults to:
688 - only classname suplied:          "index"
689 - full item designator supplied:   "item"
692 Performing actions in web requests
693 ::::::::::::::::::::::::::::::::::
695 When a user requests a web page, they may optionally also request for an
696 action to take place. As described in `how requests are processed`_, the
697 action is performed before the requested page is generated. Actions are
698 triggered by using a ``:action`` CGI variable, where the value is one of:
700 login
701  Attempt to log a user in.
702 logout
703  Log the user out - make them "anonymous".
704 register
705  Attempt to create a new user based on the contents of the form and then log
706  them in.
707 edit
708  Perform an edit of an item in the database. There are some special form
709  elements you may use:
711  :link=designator:property and :multilink=designator:property
712   The value specifies a node designator and the property on that
713   node to add _this_ node to as a link or multilink.
714  __note
715   Create a message and attach it to the current node's
716   "messages" property.
717  __file
718   Create a file and attach it to the current node's
719   "files" property. Attach the file to the message created from
720   the __note if it's supplied.
721  :required=property,property,...
722   The named properties are required to be filled in the form.
724 new
725  Add a new item to the database. You may use the same special form elements
726  as in the "edit" action.
728 editCSV
729  Performs an edit of all of a class' items in one go. See also the
730  *class*.csv templating method which generates the CSV data to be edited, and
731  the "_generic.index" template which uses both of these features.
733 search
734  Mangle some of the form variables.
736  Set the form ":filter" variable based on the values of the
737  filter variables - if they're set to anything other than
738  "dontcare" then add them to :filter.
740  Also handle the ":queryname" variable and save off the query to
741  the user's query list.
743 Each of the actions is implemented by a corresponding *actionAction* (where
744 "action" is the name of the action) method on
745 the roundup.cgi.Client class, which also happens to be in your instance as
746 interfaces.Client. So if you need to define new actions, you may add them
747 there (see `definining new web actions`_).
749 Each action also has a corresponding *actionPermission* (where
750 "action" is the name of the action) method which determines
751 whether the action is permissible given the current user. The base permission
752 checks are:
754 login
755  Determine whether the user has permission to log in.
756  Base behaviour is to check the user has "Web Access".
757 logout
758  No permission checks are made.
759 register
760  Determine whether the user has permission to register
761  Base behaviour is to check the user has "Web Registration".
762 edit
763  Determine whether the user has permission to edit this item.
764  Base behaviour is to check the user can edit this class. If we're
765  editing the "user" class, users are allowed to edit their own
766  details. Unless it's the "roles" property, which requires the
767  special Permission "Web Roles".
768 new
769  Determine whether the user has permission to create (edit) this item.
770  Base behaviour is to check the user can edit this class. No
771  additional property checks are made. Additionally, new user items
772  may be created if the user has the "Web Registration" Permission.
773 editCSV
774  Determine whether the user has permission to edit this class.
775  Base behaviour is to check the user can edit this class.
776 search
777  Determine whether the user has permission to search this class.
778  Base behaviour is to check the user can view this class.
781 Repurcussions of changing the instance schema
782 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
784 If you choose to change the `instance schema`_ you will need to ensure the web
785 interface knows about it:
787 1. Index, item and search pages for the relevant classes may need to have
788    properties added or removed,
789 2. The "page" template may require links to be changed, as might the "home"
790    page's content arguments.
792 Overall Look - "page" template
793 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
795 The "page" template in your instances
796 roundup.cgi_client.Class. This class is mixed-in to your instance through the
797 instance's interfaces module. This means you can override the header and
798 footer with your own code. This allows you to use a sidebar navigation scheme,
799 for example.
802 How the templates work
803 ~~~~~~~~~~~~~~~~~~~~~~
805 Roundup's templates consist of two core technologies:
807 TAL - Template Attribute Language
808   This is the syntax which is woven into the HTML using the ``tal:`` tag
809   attributes. A TAL parser pulls out the TAL commands from the attributes
810   runs them using some expression engine. TAL gives us the following commands:
812   tal:define="variable expression; variable expression; ..."
813    Define a new variable that is local to this tag and its contents. For
814    example::
816       <html tal:define="title request/description">
817        <head><title tal:content="title"></title></head>
818       </html>
820    In the example, the variable "title" is defined as being the result of the
821    expression "request/description". The tal:content command inside the <html>
822    tag may then use the "title" variable.
824   tal:condition="expression"
825    Only keep this tag and its contents if the expression is true. For example::
827      <p tal:condition="python:request.user.hasPermission('View', 'issue')">
828       Display some issue information.
829      </p>
831    In the example, the <p> tag and its contents are only displayed if the
832    user has the View permission for issues. We consider the number zero, a
833    blank string, an empty list, and the built-in variable nothing to be false
834    values. Nearly every other value is true, including non-zero numbers, and
835    strings with anything in them (even spaces!).
837   tal:repeat="variable expression"
838    Repeat this tag and its contents for each element of the sequence that the
839    expression returns, defining a new local variable and a special "repeat"
840    variable for each element. For example::
842      <tr tal:repeat="u user/list">
843       <td tal:content="u/id"></td>
844       <td tal:content="u/username"></td>
845       <td tal:content="u/realname"></td>
846      </tr>
848    The example would iterate over the sequence of users returned by
849    "user/list" and define the local variable "u" for each entry.
851   tal:replace="expression"
852    Replace this tag with the result of the expression. For example::
854     <span tal:replace="request/user/realname"></span>
856    The example would replace the <span> tag and its contents with the user's
857    realname. If the user's realname was "Bruce" then the resultant output
858    would be "Bruce".
860   tal:content="expression"
861    Replace the contents of this tag with the result of the expression. For
862    example::
864     <span tal:content="request/user/realname">user's name appears here</span>
866    The example would replace the contents of the <span> tag with the user's
867    realname. If the user's realname was "Bruce" then the resultant output
868    would be "<span>Bruce</span>".
870   tal:attributes="attribute expression; attribute expression; ..."
871    Set attributes on this tag to the results of expressions. For example::
873      <a tal:attributes="href string:user${request/user/id}">My Details</a>
875    In the example, the "href" attribute of the <a> tag is set to the value of
876    the "string:user${request/user/id}" expression, which will be something
877    like "user123".
879   tal:omit-tag="expression"
880    Remove this tag (but not its contents) if the expression is true. For
881    example::
883       <span tal:omit-tag="python:1">Hello, world!</span>
885    would result in output of::
887       Hello, world!
889   Note that the commands on a given tag are evaulated in the order above, so
890   *define* comes before *condition*, and so on.
892   Additionally, a tag is defined, tal:block, which is removed from output. Its
893   content is not, but the tag itself is (so don't go using any tal:attributes
894   commands on it). This is useful for making arbitrary blocks of HTML
895   conditional or repeatable (very handy for repeating multiple table rows,
896   which would othewise require an illegal tag placement to effect the repeat).
898 TALES - TAL Expression Syntax
899   The expression engine used in this case is TALES, which runs the expressions
900   that form the tag attribute values. TALES expressions come in three
901   flavours:
903   Path Expressions - eg. ``item/status/checklist``
904    These are object attribute / item accesses. Roughly speaking, the path
905    ``item/status/checklist`` is broken into parts ``item``, ``status``
906    and ``checklist``. The ``item`` part is the root of the expression.
907    We then look for a ``status`` attribute on ``item``, or failing that, a
908    ``status`` item (as in ``item['status']``). If that
909    fails, the path expression fails. When we get to the end, the object we're
910    left with is evaluated to get a string - methods are called, objects are
911    stringified. Path expressions may have an optional ``path:`` prefix, though
912    they are the default expression type, so it's not necessary.
914    XXX | components of expressions
915    XXX "nothing" and "default"
917   String Expressions - eg. ``string:hello ${user/name}``
918    These expressions are simple string interpolations (though they can be just
919    plain strings with no interpolation if you want. The expression in the
920    ``${ ... }`` is just a path expression as above.
922   Python Expressions - eg. ``python: 1+1``
923    These expressions give the full power of Python. All the "root level"
924    variables are available, so ``python:item.status.checklist()`` would be
925    equivalent to ``item/status/checklist``, assuming that ``checklist`` is
926    a method.
928 Information available to templates
929 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
931 The following variables are available to templates.
933 .. taken from roundup.cgi.templating.RoundupPageTemplate docstring
935 *context*
936   The current context. This is either None, a wrapper around a
937   hyperdb class (an HTMLClass) or a wrapper around a hyperdb item (an
938   HTMLItem).
939 *request*
940   Includes information about the current request, including:
941    - the url
942    - the current index information (``filterspec``, ``filter`` args,
943      ``properties``, etc) parsed out of the form. 
944    - methods for easy filterspec link generation
945    - *user*, the current user node as an HTMLItem instance
946    - *form*
947      The current CGI form information as a mapping of form argument
948      name to value
949 *instance*
950   The current instance
951 *db*
952   The current database, through which db.config may be reached.
954 The context variable
955 ::::::::::::::::::
957 The *context* variable is one of three things based on the current context
958 (see `determining web context`_ for how we figure this out):
960 1. if we're looking at a "home" page, then it's None
961 2. if we're looking at a specific hyperdb class, it's an HTMLClass instance
962 3. if we're looking at a specific hyperdb item, it's an HTMLItem instance
964 If the context is not None, we can access the properties of the class or item.
965 The only real difference between cases 2 and 3 above are:
967 1. the properties may have a real value behind them, and this will appear if
968    the property is displayed through ``context/property`` or
969    ``context/property/field``.
970 2. the context's "id" property will be a false value in the second case, but
971    a real, or true value in the third. Thus we can determine whether we're
972    looking at a real item from the hyperdb by testing "context/id".
975 The request variable
976 ::::::::::::::::::::
978 The request variable is packed with information about the current request.
980 .. taken from roundup.cgi.templating.HTMLRequest docstring
982 =========== ================================================================
983 Variable    Holds
984 =========== ================================================================
985 form        the CGI form as a cgi.FieldStorage
986 env         the CGI environment variables
987 url         the current URL path for this request
988 base        the base URL for this instance
989 user        a HTMLUser instance for this user
990 classname   the current classname (possibly None)
991 template    the current template (suffix, also possibly None)
992 form        the current CGI form variables in a FieldStorage
993 **Index  page specific variables (indexing arguments)**
994 columns     dictionary of the columns to display in an index page
995 show        a convenience access to columns - request/show/colname will
996               be true if the columns should be displayed, false otherwise
997 sort        index sort column (direction, column name)
998 group       index grouping property (direction, column name)
999 filter      properties to filter the index on
1000 filterspec  values to filter the index on
1001 search_text text to perform a full-text search on for an index
1002 ----------- ----------------------------------------------------------------
1005 Displaying Properties
1006 ~~~~~~~~~~~~~~~~~~~~~
1008 Properties appear in the user interface in three contexts: in indices, in
1009 editors, and as search arguments.
1010 For each type of property, there are several display possibilities.
1011 For example, in an index view, a string property may just be
1012 printed as a plain string, but in an editor view, that property may be
1013 displayed in an editable field.
1016 Index Views
1017 ~~~~~~~~~~~
1019 Index View Specifiers
1020 :::::::::::::::::::::
1022 An index view specifier (URL fragment) looks like this (whitespace has been
1023 added for clarity)::
1025      /issue?status=unread,in-progress,resolved&
1026             topic=security,ui&
1027             :group=+priority&
1028             :sort=-activity&
1029             :filters=status,topic&
1030             :columns=title,status,fixer
1032 The index view is determined by two parts of the specifier: the layout part and
1033 the filter part. The layout part consists of the query parameters that begin
1034 with colons, and it determines the way that the properties of selected nodes
1035 are displayed. The filter part consists of all the other query parameters, and
1036 it determines the criteria by which nodes are selected for display.
1037 The filter part is interactively manipulated with the form widgets displayed in
1038 the filter section. The layout part is interactively manipulated by clicking on
1039 the column headings in the table.
1041 The filter part selects the union of the sets of items with values matching any
1042 specified Link properties and the intersection of the sets of items with values
1043 matching any specified Multilink properties.
1045 The example specifies an index of "issue" nodes. Only items with a "status" of
1046 either "unread" or "in-progres" or "resolved" are displayed, and only items
1047 with "topic" values including both "security" and "ui" are displayed. The items
1048 are grouped by priority, arranged in ascending order; and within groups, sorted
1049 by activity, arranged in descending order. The filter section shows filters for
1050 the "status" and "topic" properties, and the table includes columns for the
1051 "title", "status", and "fixer" properties.
1053 Filtering of indexes
1054 ::::::::::::::::::::
1056 TODO
1058 Searching Views
1059 ~~~~~~~~~~~~~~~
1061 TODO
1063 Item Views
1064 ~~~~~~~~~~
1066 An item view contains an editor section and a spool section. At the top of an
1067 item view, links to superseding and superseded items are always displayed.
1069 Editor Section
1070 ::::::::::::::
1072 The editor section is generated from a template containing <display> tags to
1073 insert the appropriate widgets for editing properties.
1075 Here's an example of a basic editor template.::
1077      <table>
1078      <tr>
1079          <td colspan=2>
1080              <display call="field('title', size=60)">
1081          </td>
1082      </tr>
1083      <tr>
1084          <td>
1085              <display call="field('fixer', size=30)">
1086          </td>
1087          <td>
1088              <display call="menu('status')>
1089          </td>
1090      </tr>
1091      <tr>
1092          <td>
1093              <display call="field('nosy', size=30)">
1094          </td>
1095          <td>
1096              <display call="menu('priority')>
1097          </td>
1098      </tr>
1099      <tr>
1100          <td colspan=2>
1101              <display call="note()">
1102          </td>
1103      </tr>
1104      </table>
1106 As shown in the example, the editor template can also request the display of a
1107 "note" field, which is a text area for entering a note to go along with a
1108 change.
1110 The <property> tag used in the index may also be used here - it checks to see
1111 if the nominated Multilink property has any entries. This can be used to
1112 eliminate sections of the editor section if the property has no entries::
1114   <td class="form-text">
1115     <display call="field('superseder', size=40, showid=1)">
1116     <display call="classhelp('issue', 'id,title', label='list', width=500)">
1117     <property name="superseder">
1118       <br>View: <display call="link('superseder', showid=1)">
1119     </property>
1120   </td>
1122 The "View: " part with the links will only display if the superseder property
1123 has values.
1125 When a change is submitted, the system automatically generates a message
1126 describing the changed properties.
1128 If a note is given in the "note" field, the note is appended to the
1129 description. The message is then added to the item's message spool (thus
1130 triggering the standard detector to react by sending out this message to the
1131 nosy list).
1133 The message also displays all of the property values on the item and indicates
1134 which ones have changed. An example of such a message might be this::
1136      Polly's taken a turn for the worse - this is now really important!
1137      -----
1138      title: Polly Parrot is dead
1139      priority: critical
1140      status: unread -> in-progress
1141      fixer: terry
1142      keywords: parrot,plumage,perch,nailed,dead
1144 Spool Section
1145 :::::::::::::
1147 The spool section lists messages in the item's "messages" property. The index
1148 of messages displays the "date", "author", and "summary" properties on the
1149 message nodes, and selecting a message takes you to its content.
1151 The <property> tag used in the index may also be used here - it checks to see
1152 if the nominated Multilink property has any entries. This can be used to
1153 eliminate sections of the spool section if the property has no entries::
1155      <property name="files">
1156       <tr class="strong-header">
1157        <td><b>Files</b></td>
1158       </tr>
1160       <tr>
1161        <td><display call="list('files')"></td>
1162       </tr>
1163      </property>
1166 Access Controls
1167 ---------------
1169 A set of Permissions are built in to the security module by default:
1171 - Edit (everything)
1172 - View (everything)
1174 The default interfaces define:
1176 - Web Registration
1177 - Web Access
1178 - Web Roles
1179 - Email Registration
1180 - Email Access
1182 These are hooked into the default Roles:
1184 - Admin (Edit everything, View everything, Web Roles)
1185 - User (Web Access, Email Access)
1186 - Anonymous (Web Registration, Email Registration)
1188 And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user
1189 gets the "Anonymous" assigned when the database is initialised on installation.
1190 The two default schemas then define:
1192 - Edit issue, View issue (both)
1193 - Edit file, View file (both)
1194 - Edit msg, View msg (both)
1195 - Edit support, View support (extended only)
1197 and assign those Permissions to the "User" Role. New users are assigned the
1198 Roles defined in the config file as:
1200 - NEW_WEB_USER_ROLES
1201 - NEW_EMAIL_USER_ROLES
1203 You may alter the configuration variables to change the Role that new web or
1204 email users get, for example to not give them access to the web interface if
1205 they register through email.
1207 You may use the ``roundup-admin`` "``security``" command to display the
1208 current Role and Permission configuration in your instance.
1210 Adding a new Permission
1211 ~~~~~~~~~~~~~~~~~~~~~~~
1213 When adding a new Permission, you will need to:
1215 1. add it to your instance's dbinit so it is created
1216 2. enable it for the Roles that should have it (verify with
1217    "``roundup-admin security``")
1218 3. add it to the relevant HTML interface templates
1219 4. add it to the appropriate xxxPermission methods on in your instance
1220    interfaces module
1224 -----------------
1226 Back to `Table of Contents`_
1228 .. _`Table of Contents`: index.html