diff doc/reference.txt @ 7464:82bbb95e5690 issue2550923_computed_property

merge from tip into issue2550923_computed_property
author John Rouillard <rouilj@ieee.org>
date Thu, 08 Jun 2023 00:10:32 -0400
parents b4ac699c6282
children cdb81976b8bc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/reference.txt	Thu Jun 08 00:10:32 2023 -0400
@@ -0,0 +1,4016 @@
+.. meta::
+    :description:
+        Reference for the internals of Roundup. Includes background
+        information for cookbook and how-to examples.
+        Reference for the design and internals needed to understand
+        and extend the examples to meet new needs.
+
+:tocdepth: 2
+
+=================
+Roundup Reference
+=================
+
+.. admonition:: Welcome
+
+  This document used to be part of the `customization document`_.  The
+  customization document was getting large and unwieldy.  It was a
+  combination of examples and internal information that made finding
+  information difficult. We often had questions on the mailing list that
+  were well answered in the customization document, but finding the info
+  was difficult.
+
+  The documentation is slowly being reorganized using the `Diataxis
+  framework`_. Help with the reorganization is welcome.
+
+  This is the document you should look at for background on a tutorial
+  or how-to guide in the customization document.
+
+  .. _customization document: customizing.html
+  .. _diataxis framework: https://diataxis.fr/
+
+.. This document borrows from the ZopeBook section on ZPT. The original is at:
+   http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
+
+.. contents::
+   :depth: 2
+   :local:
+
+Trackers in a Nutshell
+======================
+
+Trackers have the following structure:
+
+.. index::
+   single: tracker; structure db directory
+   single: tracker; structure detectors directory
+   single: tracker; structure extensions directory
+   single: tracker; structure html directory
+   single: tracker; structure html directory
+   single: tracker; structure lib directory
+
+=================== ========================================================
+Tracker File        Description
+=================== ========================================================
+config.ini          Holds the basic `tracker configuration`_
+schema.py           Holds the `tracker schema`_
+initial_data.py     Holds any data to be entered into the database when the
+                    tracker is initialised (optional)
+interfaces.py       Allows `modifying the core of Roundup`_ (optional)
+db/                 Holds the tracker's database
+db/files/           Holds the tracker's upload files and messages
+db/backend_name     Names the database back-end for the tracker (obsolete).
+                    Use the ``backend`` setting in the ``[rdbms]``
+		    section of ``config.ini`` instead.
+detectors/          `Auditors and reactors`_ for this tracker
+extensions/         Additional `actions`_ and `templating utilities`_
+html/               Web interface templates, images and style sheets
+lib/                optional common imports for detectors and extensions
+=================== ======================================================== 
+
+
+.. index:: config.ini
+.. index:: configuration; see config.ini 
+
+Tracker Configuration
+=====================
+
+The ``config.ini`` located in your tracker home contains the basic
+configuration for the web and e-mail components of Roundup's interfaces.
+
+Changes to the data captured by your tracker is controlled by the `tracker
+schema`_.  Some configuration is also performed using permissions - see the 
+`security / access controls`_ section. For example, to allow users to
+automatically register through the email interface, you must grant the
+"Anonymous" Role the "Email Access" Permission.
+
+.. index::
+   single: config.ini; sections
+   see: configuration; config.ini
+
+The following is taken from the `Python Library Reference`__ (July 18, 2018)
+section "ConfigParser -- Configuration file parser":
+
+ The configuration file consists of sections, led by a [section] header
+ and followed by name: value entries, with continuations in the style
+ of RFC 822 (see section 3.1.1, “LONG HEADER FIELDS”); name=value is
+ also accepted. Note that leading whitespace is removed from
+ values. The optional values can contain format strings which refer to
+ other values in the same section, or values in a special DEFAULT
+ section. Additional defaults can be provided on initialization and
+ retrieval. Lines beginning with '#' or ';' are ignored and may be
+ used to provide comments.
+
+ For example::
+
+   [My Section]
+   foodir = %(dir)s/whatever
+   dir = frob
+
+ would resolve the "%(dir)s" to the value of "dir" ("frob" in this case)
+ resulting in "foodir" being "frob/whatever".
+
+__ https://docs.python.org/2/library/configparser.html
+
+Example configuration settings are below. This is a partial
+list. Documentation on all the settings is included in the
+``config.ini`` file.
+ 
+.. .comment out. file generation needs more work include:: tracker_config.txt
+
+.. index:: config.ini; sections main
+
+Section **main**
+ database -- ``db``
+  Database directory path. The path may be either absolute or relative
+  to the directory containig this config file.
+
+ templates -- ``html``
+  Path to the HTML templates directory. The path may be either absolute
+  or relative to the directory containing this config file.
+
+ static_files -- default *blank*
+  A list of space separated directory paths (or a single directory).
+  These directories hold additional static files available via Web UI.
+  These directories may contain sitewide images, CSS stylesheets etc. If
+  a '-' is included, the list processing ends and the TEMPLATES
+  directory is not searched after the specified directories.  If this
+  option is not set, all static files are taken from the TEMPLATES
+  directory.
+
+ admin_email -- ``roundup-admin``
+  Email address that roundup will complain to if it runs into trouble. If
+  the email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined
+  below is used.
+
+ dispatcher_email -- ``roundup-admin``
+  The 'dispatcher' is a role that can get notified of new items to the
+  database. It is used by the ERROR_MESSAGES_TO config setting. If the
+  email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined
+  below is used.
+
+ email_from_tag -- default *blank*
+  Additional text to include in the "name" part of the From: address used
+  in nosy messages. If the sending user is "Foo Bar", the From: line
+  is usually: ``"Foo Bar" <issue_tracker@tracker.example>``
+  the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so:
+  ``"Foo Bar EMAIL_FROM_TAG" <issue_tracker@tracker.example>``
+
+ new_web_user_roles -- ``User``
+  Roles that a user gets when they register with Web User Interface.
+  This is a comma-separated list of role names (e.g. ``Admin,User``).
+
+ new_email_user_roles -- ``User``
+  Roles that a user gets when they register with Email Gateway.
+  This is a comma-separated string of role names (e.g. ``Admin,User``).
+
+ error_messages_to -- ``user``
+  Send error message emails to the ``dispatcher``, ``user``, or ``both``?
+  The dispatcher is configured using the DISPATCHER_EMAIL setting.
+  Allowed values: ``dispatcher``, ``user``, or ``both``
+
+ html_version -- ``html4``
+  HTML version to generate. The templates are ``html4`` by default.
+  If you wish to make them xhtml, then you'll need to change this
+  var to ``xhtml`` too so all auto-generated HTML is compliant.
+  Allowed values: ``html4``, ``xhtml``
+
+ timezone -- ``0``
+  Numeric timezone offset used when users do not choose their own
+  in their settings.
+
+ instant_registration -- ``yes``
+  Register new users instantly, or require confirmation via
+  email?
+  Allowed values: ``yes``, ``no``
+
+ email_registration_confirmation -- ``yes``
+  Offer registration confirmation by email or only through the web?
+  Allowed values: ``yes``, ``no``
+
+ indexer_stopwords -- default *blank*
+  Additional stop-words for the full-text indexer specific to
+  your tracker. See the indexer source for the default list of
+  stop-words (e.g. ``A,AND,ARE,AS,AT,BE,BUT,BY, ...``).
+
+ umask -- ``02``
+  Defines the file creation mode mask.
+
+ csv_field_size -- ``131072``
+  Maximum size of a csv-field during import. Roundup's export
+  format is a csv (comma separated values) variant. The csv
+  reader has a limit on the size of individual fields
+  starting with python 2.5. Set this to a higher value if you
+  get the error 'Error: field larger than field limit' during
+  import.
+
+.. index:: config.ini; sections tracker
+
+Section **tracker**
+ name -- ``Roundup issue tracker``
+  A descriptive name for your Roundup instance.
+
+ web -- ``http://host.example/demo/``
+  The web address that the tracker is viewable at.
+  This will be included in information sent to users of the tracker.
+  The URL MUST include the cgi-bin part or anything else
+  that is required to get to the home page of the tracker.
+  You MUST include a trailing '/' in the URL.
+
+ email -- ``issue_tracker``
+  Email address that mail to Roundup should go to.
+
+ language -- default *blank*
+  Default locale name for this tracker. If this option is not set, the
+  language is determined by the environment variable LANGUAGE, LC_ALL,
+  LC_MESSAGES, or LANG, in that order of preference.
+
+.. index:: config.ini; sections web
+
+Section **web**
+ allow_html_file -- ``no``
+  Setting this option enables Roundup to serve uploaded HTML
+  file content *as HTML*. This is a potential security risk
+  and is therefore disabled by default. Set to 'yes' if you
+  trust *all* users uploading content to your tracker.
+
+ http_auth -- ``yes``
+  Whether to use HTTP Basic Authentication, if present.
+  Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION
+  variables supplied by your web server (in that order).
+  Set this option to 'no' if you do not wish to use HTTP Basic
+  Authentication in your web interface.
+
+ use_browser_language -- ``yes``
+  Whether to use HTTP Accept-Language, if present.
+  Browsers send a language-region preference list.
+  It's usually set in the client's browser or in their
+  Operating System.
+  Set this option to 'no' if you want to ignore it.
+
+ debug -- ``no``
+  Setting this option makes Roundup display error tracebacks
+  in the user's browser rather than emailing them to the
+  tracker admin."),
+
+.. index:: config.ini; sections rdbms
+   single: config.ini; database settings
+
+Section **rdbms**
+ Settings in this section are used to set the backend and configure
+ addition settings needed by RDBMs like SQLite, Postgresql and
+ MySQL backends.
+
+ .. index::
+    single: postgres; select backend in config.ini 
+    single: mysql; select backend in config.ini
+    single: sqlite; select backend in config.ini
+    single: anydbm; select backend in config.ini
+    see: database; postgres
+    see: database; mysql
+    see: database; sqlite
+    see: database; anydbm
+
+ backend -- set to value by init
+  The database backend such as anydbm, sqlite, mysql or postgres.
+
+ name -- ``roundup``
+  Name of the database to use.
+
+ host -- ``localhost``
+  Database server host.
+
+ port -- default *blank*
+  TCP port number of the database server. Postgresql usually resides on
+  port 5432 (if any), for MySQL default port number is 3306. Leave this
+  option empty to use backend default.
+
+ user -- ``roundup``
+  Database user name that Roundup should use.
+
+ password -- ``roundup``
+  Database user password.
+
+ read_default_file -- ``~/.my.cnf``
+  Name of the MySQL defaults file. Only used in MySQL connections.
+
+ read_default_group -- ``roundup``
+  Name of the group to use in the MySQL defaults file. Only used in
+  MySQL connections.
+
+ .. index::
+    single: sqlite; lock timeout
+
+ sqlite_timeout -- ``30``
+  Number of seconds to wait when the SQLite database is locked.
+  Used only for SQLite.
+
+ cache_size -- `100`
+  Size of the node cache (in elements) used to keep most recently used
+  data in memory.
+
+.. index:: config.ini; sections logging
+   see: logging; config.ini, sections logging
+
+Section **logging**
+ config -- default *blank*
+  Path to configuration file for standard Python logging module. If this
+  option is set, logging configuration is loaded from specified file;
+  options 'filename' and 'level' in this section are ignored. The path may
+  be either absolute or relative to the directory containig this config file.
+
+ filename -- default *blank*
+  Log file name for minimal logging facility built into Roundup.  If no file
+  name specified, log messages are written on stderr. If above 'config'
+  option is set, this option has no effect. The path may be either absolute
+  or relative to the directory containig this config file.
+
+ level -- ``ERROR``
+  Minimal severity level of messages written to log file. If above 'config'
+  option is set, this option has no effect.
+  Allowed values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``
+
+.. index:: config.ini; sections mail
+
+Section **mail**
+ Outgoing email options. Used for nosy messages, password reset and
+ registration approval requests.
+
+ domain -- ``localhost``
+  Domain name used for email addresses.
+
+ host -- default *blank*
+  SMTP mail host that Roundup will use to send mail
+
+ username -- default *blank*
+  SMTP login name. Set this if your mail host requires authenticated access.
+  If username is not empty, password (below) MUST be set!
+
+ password -- default *blank*
+  SMTP login password.
+  Set this if your mail host requires authenticated access.
+
+ port -- default *25*
+  SMTP port on mail host.
+  Set this if your mail host runs on a different port.
+
+ local_hostname -- default *blank*
+  The fully qualified domain name (FQDN) to use during SMTP sessions. If left
+  blank, the underlying SMTP library will attempt to detect your FQDN. If your
+  mail host requires something specific, specify the FQDN to use.
+
+ tls -- ``no``
+  If your SMTP mail host provides or requires TLS (Transport Layer Security)
+  then you may set this option to 'yes'.
+  Allowed values: ``yes``, ``no``
+
+ tls_keyfile -- default *blank*
+  If TLS is used, you may set this option to the name of a PEM formatted
+  file that contains your private key. The path may be either absolute or
+  relative to the directory containig this config file.
+
+ tls_certfile -- default *blank*
+  If TLS is used, you may set this option to the name of a PEM formatted
+  certificate chain file. The path may be either absolute or relative
+  to the directory containig this config file.
+
+ charset -- utf-8
+  Character set to encode email headers with. We use utf-8 by default, as
+  it's the most flexible. Some mail readers (eg. Eudora) can't cope with
+  that, so you might need to specify a more limited character set
+  (eg. iso-8859-1).
+
+ debug -- default *blank*
+  Setting this option makes Roundup to write all outgoing email messages
+  to this file *instead* of sending them. This option has the same effect
+  as environment variable SENDMAILDEBUG. Environment variable takes
+  precedence. The path may be either absolute or relative to the directory
+  containig this config file.
+
+ add_authorinfo -- ``yes``
+  Add a line with author information at top of all messages send by
+  Roundup.
+
+ add_authoremail -- ``yes``
+  Add the mail address of the author to the author information at the
+  top of all messages.  If this is false but add_authorinfo is true,
+  only the name of the actor is added which protects the mail address
+  of the actor from being exposed at mail archives, etc.
+
+.. index:: config.ini; sections mailgw
+   single: mailgw; config
+   see: mail gateway; mailgw
+
+Section **mailgw**
+ Roundup Mail Gateway options
+
+ keep_quoted_text -- ``yes``
+  Keep email citations when accepting messages. Setting this to ``no`` strips
+  out "quoted" text from the message. Signatures are also stripped.
+  Allowed values: ``yes``, ``no``
+
+ leave_body_unchanged -- ``no``
+  Preserve the email body as is - that is, keep the citations *and*
+  signatures.
+  Allowed values: ``yes``, ``no``
+
+ default_class -- ``issue``
+  Default class to use in the mailgw if one isn't supplied in email subjects.
+  To disable, leave the value blank.
+
+ language -- default *blank*
+  Default locale name for the tracker mail gateway.  If this option is
+  not set, mail gateway will use the language of the tracker instance.
+
+ subject_prefix_parsing -- ``strict``
+  Controls the parsing of the [prefix] on subject lines in incoming emails.
+  ``strict`` will return an error to the sender if the [prefix] is not
+  recognised. ``loose`` will attempt to parse the [prefix] but just
+  pass it through as part of the issue title if not recognised. ``none``
+  will always pass any [prefix] through as part of the issue title.
+
+ subject_suffix_parsing -- ``strict``
+  Controls the parsing of the [suffix] on subject lines in incoming emails.
+  ``strict`` will return an error to the sender if the [suffix] is not
+  recognised. ``loose`` will attempt to parse the [suffix] but just
+  pass it through as part of the issue title if not recognised. ``none``
+  will always pass any [suffix] through as part of the issue title.
+
+ subject_suffix_delimiters -- ``[]``
+  Defines the brackets used for delimiting the commands suffix in a subject
+  line.
+
+ subject_content_match -- ``always``
+  Controls matching of the incoming email subject line against issue titles
+  in the case where there is no designator [prefix]. ``never`` turns off
+  matching. ``creation + interval`` or ``activity + interval`` will match
+  an issue for the interval after the issue's creation or last activity.
+  The interval is a standard Roundup interval.
+
+ subject_updates_title -- ``yes``
+  Update issue title if incoming subject of email is different.
+  Setting this to ``no`` will ignore the title part of
+  the subject of incoming email messages.
+
+ refwd_re -- ``(\s*\W?\s*(fw|fwd|re|aw|sv|ang)\W)+``
+  Regular expression matching a single reply or forward prefix
+  prepended by the mailer. This is explicitly stripped from the
+  subject during parsing.  Value is Python Regular Expression
+  (UTF8-encoded).
+
+ origmsg_re -- `` ^[>|\s]*-----\s?Original Message\s?-----$``
+  Regular expression matching start of an original message if quoted
+  in the body.  Value is Python Regular Expression (UTF8-encoded).
+
+ sign_re -- ``^[>|\s]*-- ?$``
+  Regular expression matching the start of a signature in the message
+  body.  Value is Python Regular Expression (UTF8-encoded).
+
+ eol_re -- ``[\r\n]+``
+  Regular expression matching end of line.  Value is Python Regular
+  Expression (UTF8-encoded).
+
+ blankline_re -- ``[\r\n]+\s*[\r\n]+``
+  Regular expression matching a blank line.  Value is Python Regular
+  Expression (UTF8-encoded).
+
+ ignore_alternatives -- ``no``
+  When parsing incoming mails, Roundup uses the first
+  text/plain part it finds. If this part is inside a
+  multipart/alternative, and this option is set, all other
+  parts of the multipart/alternative are ignored. The default
+  is to keep all parts and attach them to the issue.
+
+.. index:: config.ini; sections php
+
+Section **pgp**
+ OpenPGP mail processing options
+
+ enable -- ``no``
+  Enable PGP processing. Requires gpg.
+
+ roles -- default *blank*
+  If specified, a comma-separated list of roles to perform PGP
+  processing on. If not specified, it happens for all users.
+
+ homedir -- default *blank*
+  Location of PGP directory. Defaults to $HOME/.gnupg if not
+  specified.
+
+
+.. index:: config.ini; sections nosy
+
+Section **nosy**
+ Nosy messages sending
+
+ messages_to_author -- ``no``
+  Send nosy messages to the author of the message.
+  If ``yes`` is used, then messages are sent to the author
+  even if not on the nosy list, same for ``new`` (but only for new messages).
+  When set to ``nosy``, the nosy list controls sending messages to the author.
+  Allowed values: ``yes``, ``no``, ``new``, ``nosy``
+
+ signature_position -- ``bottom``
+  Where to place the email signature.
+  Allowed values: ``top``, ``bottom``, ``none``
+
+ add_author -- ``new``
+  Does the author of a message get placed on the nosy list automatically?
+  If ``new`` is used, then the author will only be added when a message
+  creates a new issue. If ``yes``, then the author will be added on
+  followups too. If ``no``, they're never added to the nosy.
+  Allowed values: ``yes``, ``no``, ``new``
+  
+ add_recipients -- ``new``
+  Do the recipients (``To:``, ``Cc:``) of a message get placed on the nosy
+  list?  If ``new`` is used, then the recipients will only be added when a
+  message creates a new issue. If ``yes``, then the recipients will be added
+  on followups too. If ``no``, they're never added to the nosy.
+  Allowed values: ``yes``, ``no``, ``new``
+
+ email_sending -- ``single``
+  Controls the email sending from the nosy reactor. If ``multiple`` then
+  a separate email is sent to each recipient. If ``single`` then a single
+  email is sent with each recipient as a CC address.
+
+ max_attachment_size -- ``2147483647``
+  Attachments larger than the given number of bytes won't be attached
+  to nosy mails. They will be replaced by a link to the tracker's
+  download page for the file.
+
+
+.. index:: single: roundup-admin; config.ini update
+           single: roundup-admin; config.ini create
+           single: config.ini; create
+           single: config.ini; update
+
+You may generate a new default config file using the ``roundup-admin
+genconfig`` command. You can generate a new config file merging in
+existing settings using the ``roundup-admin updateconfig`` command.
+
+Configuration variables may be referred to in lower or upper case. In code,
+variables not in the "main" section are referred to using their section and
+name, so "domain" in the section "mail" becomes MAIL_DOMAIN.
+
+.. index:: pair: configuration; extensions
+   pair: configuration; detectors
+
+Extending the configuration file
+--------------------------------
+
+You can't add new variables to the config.ini file in the tracker home but
+you can add two new config.ini files:
+
+- a config.ini in the ``extensions`` directory will be loaded and attached
+  to the config variable as "ext".
+- a config.ini in the ``detectors`` directory will be loaded and attached
+  to the config variable as "detectors".
+
+For example, the following in ``detectors/config.ini``::
+
+    [main]
+    qa_recipients = email@example.com
+
+is accessible as::
+
+    db.config.detectors['QA_RECIPIENTS']
+
+Note that the name grouping applied to the main configuration file is
+applied to the extension config files, so if you instead have::
+
+    [qa]
+    recipients = email@example.com
+
+then the above ``db.config.detectors['QA_RECIPIENTS']`` will still work.
+
+Unlike values in the tracker's main ``config.ini``, the values defined
+in these config files are not validated. For example: a setting that
+is supposed to be an integer value (e.g. 4) could be the word
+"foo". If you are writing Python code that uses these settings, you
+should expect to handle invalid values.
+
+Also, incorrect values aren't discovered until the config setting is
+used. This can be long after the tracker is started and the error may
+not be seen in the logs.
+
+It is possible to validate these settings. Validation involves calling
+the ``update_options`` method on the configuration option. This can be
+done from the ``init()`` function in the Python files implementing
+extensions_ or detectors_.
+
+As an example, adding the following to an extension::
+
+   from roundup.configuration import SecretMandatoryOption
+
+   def init(instance):
+       instance.config.ext.update_option('RECAPTCHA_SECRET',
+           SecretMandatoryOption,description="Secret securing reCaptcha.")
+
+similarly for a detector::
+
+   from roundup.configuration import MailAddressOption
+
+   def init(db):
+       try:
+           db.config.detectors.update_option('QA_RECIPIENTS',
+               MailAddressOption,
+	       description="Email used for QA comment followup.")
+       except KeyError:
+           # COMMENT_EMAIL setting is not found, but it's optional
+	   # so continue
+           pass
+
+will allow reading the secret from a file or append the tracker domain
+to an email address if it does not have a domain.
+
+Running ``roundup-admin -i tracker_home display user1`` will validate
+the settings for both config.ini`s. Otherwise detector options are not
+validated until the first request to the web interface (or email
+gateway).
+
+There are 4 arguments for ``update_option``:
+
+1. config setting name - string (positional, mandatory)
+2. option type - Option derived class from configuration.py
+   (positional, mandatory)
+3. default value - string (optional, named default) 
+4. description - string (optional, named description) 
+
+The first argument is the config setting name as described at the
+beginning of this section.
+
+The second argument is a class in the roundup.configuration module.
+There are a number of these classes: BooleanOption,
+IntegerNumberOption, RegExpOption.... Please see the configuration
+module for all Option validators and their descriptions. You can also
+define your own custom validator in `interfaces.py`_.
+
+The third and fourth arguments are strings and are optional. They are
+printed if there is an error and may help the user correct the problem.
+
+.. index:: ! schema
+
+Tracker Schema
+==============
+
+.. note::
+   if you modify the schema, you'll most likely need to edit the
+   `web interface`_ HTML template files and `detectors`_ to reflect
+   your changes.
+
+A tracker schema defines what data is stored in the tracker's database.
+Schemas are defined using Python code in the ``schema.py`` module of your
+tracker.
+
+What you can/can't do to the schema
+-----------------------------------
+
+Your schema may be changed at any time before or after the tracker has been
+initialised (or used). You may:
+
+**Add new properties to classes, or add whole new classes**
+  This is painless and easy to do - there are generally no repercussions
+  from adding new information to a tracker's schema.
+
+**Remove properties**
+  Removing properties is a little more tricky - you need to make sure that
+  the property is no longer used in the `web interface`_ *or* by the
+  detectors_.
+
+You must never:
+
+**Remove the user class**
+  This class is the only *required* class in Roundup.
+
+**Remove the "username", "address", "password" or "realname" user properties**
+  Various parts of Roundup require these properties. Don't remove them.
+
+**Change the type of a property**
+  Property types must *never* [1]_ be changed - the database simply
+  doesn't take this kind of action into account. Note that you can't
+  just remove a property and re-add it as a new type either. If you
+  wanted to make the assignedto property a Multilink, you'd need to
+  create a new property assignedto_list and remove the old assignedto
+  property.
+
+.. [1] If you shut down the tracker, `export the database`_, modify the
+       exported csv property data to be compatible with the new type,
+       change the property type in the schema, and finally import the
+       changed exported data, you can change the property type. It is
+       not trivial nor for the faint of heart. But it can be done.
+
+.. _export the database: admin_guide.html#using-roundup-admin
+
+The ``schema.py`` and ``initial_data.py`` modules
+-------------------------------------------------
+
+The schema.py module is used to define what your tracker looks like
+on the inside, the schema of the tracker. It defines the Classes
+and properties on each class. It also defines the security for
+those Classes. The next few sections describe how schemas work
+and what you can do with them.
+
+The initial_data.py module sets up the initial state of your
+tracker. It’s called exactly once - by the ``roundup-admin initialise``
+command. See the start of the section on `database content`_ for more
+info about how this works.
+
+.. index:: schema; classic - description of
+
+The "classic" schema
+--------------------
+
+The "classic" schema looks like this (see section `setkey(property)`_
+below for the meaning of ``'setkey'`` -- you may also want to look into
+the sections `setlabelprop(property)`_ and `setorderprop(property)`_ for
+specifying (default) labelling and ordering of classes.)::
+
+    pri = Class(db, "priority", name=String(), order=String())
+    pri.setkey("name")
+
+    stat = Class(db, "status", name=String(), order=String())
+    stat.setkey("name")
+
+    keyword = Class(db, "keyword", name=String())
+    keyword.setkey("name")
+
+    user = Class(db, "user", username=String(), organisation=String(),
+        password=String(), address=String(), realname=String(),
+        phone=String(), alternate_addresses=String(),
+        queries=Multilink('query'), roles=String(), timezone=String())
+    user.setkey("username")
+
+    msg = FileClass(db, "msg", author=Link("user"), summary=String(),
+        date=Date(), recipients=Multilink("user"),
+        files=Multilink("file"), messageid=String(), inreplyto=String())
+
+    file = FileClass(db, "file", name=String())
+
+    issue = IssueClass(db, "issue", keyword=Multilink("keyword"),
+        status=Link("status"), assignedto=Link("user"),
+        priority=Link("priority"))
+    issue.setkey('title')
+
+.. index:: schema; allowed changes
+
+Classes and Properties - creating a new information store
+---------------------------------------------------------
+
+In the tracker above, we've defined 7 classes of information:
+
+  priority
+      Defines the possible levels of urgency for issues.
+
+  status
+      Defines the possible states of processing the issue may be in.
+
+  keyword
+      Initially empty, will hold keywords useful for searching issues.
+
+  user
+      Initially holding the "admin" user, will eventually have an entry
+      for all users using Roundup.
+
+  msg
+      Initially empty, will hold all e-mail messages sent to or
+      generated by Roundup.
+
+  file
+      Initially empty, will hold all files attached to issues.
+
+  issue
+      Initially empty, this is where the issue information is stored.
+
+We define the "priority" and "status" classes to allow two things:
+
+  1. reduction in the amount of information stored on the issue
+  2. more powerful, accurate searching of issues by priority and status
+
+By only requiring a link on the issue (which is stored as a single
+number) we reduce the chance that someone mis-types a priority or
+status - or simply makes a new one up.
+
+Class names are used to access items of that class in the `REST api`_
+interface. The classic tracker was created before the REST interface
+was added. It uses the single form (i.e. issue and user not issues and
+users) for its classes. Most REST documentation suggests using plural
+forms. However, to make your API consistent, use singular forms for
+classes that you add.
+
+Class and Items
+~~~~~~~~~~~~~~~
+
+A Class defines a particular class (or type) of data that will be stored
+in the database. A class comprises one or more properties, which gives
+the information about the class items.
+
+The actual data entered into the database, using ``class.create()``, are
+called items. They have a special immutable property called ``'id'``. We
+sometimes refer to this as the *itemid*.
+
+
+.. index:: schema; property types
+
+Properties
+~~~~~~~~~~
+
+A Class is comprised of one or more properties of the following types:
+
+String
+     properties are for storing arbitrary-length strings.
+Password
+     properties are for storing encoded arbitrary-length strings.
+     The default encoding is defined on the ``roundup.password.Password``
+     class.
+Date
+     properties store date-and-time stamps. Their values are Timestamp
+     objects.
+Interval
+     properties store time periods rather than absolute dates. For
+     example 2 hours.
+Integer
+     properties store integer values. (Number can store real/float values.)
+Number
+     properties store numeric values. There is an option to use
+     double-precision floating point numbers.
+Boolean
+     properties store on/off, yes/no, true/false values.
+Link
+     properties refers to a single other item selected from a
+     specified class. The class is part of the property; the value is an
+     integer, the id of the chosen item.
+Multilink
+     properties refer to possibly many items in a specified
+     class. The value is a list of integers.
+
+Properties can have additional attributes to change the default
+behaviour:
+
+.. index:: triple: schema; property attributes; required
+   triple: schema; property attributes; default_value
+   triple: schema; property attributes; quiet
+
+* All properties support the following attributes:
+
+  - ``required``: see `design documentation`_. Adds the property to
+    the list returned by calling get_required_props for the class.
+  - ``default_value``: see `design documentation`_ Sets the default
+    value if the property is not set.
+  - ``quiet``: see `design documentation`_. Suppresses user visible
+    to changes to this property. The property change is not reported:
+
+    - in the change feedback/confirmation message in the web
+      interface
+    - the property change section of the nosy email
+    - the web history at the bottom of an item's page
+
+    This can be used to store state of the user interface (e.g. the
+    names of elements that are collapsed or hidden from the
+    user). Making properties that are updated as an indirect result of
+    a user's change (e.g. updating a blockers property, counting
+    number of times an issue was reopened or reassigned etc.) should
+    not be displayed to the user as they can be confusing.
+
+.. index:: triple: schema; property attributes; indexme
+
+* String properties can have an ``indexme`` attribute that defines if the
+  property should be part of the full text index. The default is 'no' but this
+  can be set to 'yes' to allow a property's contents to be in the full
+  text index.
+
+.. index:: triple: schema; property attributes; use_double
+
+* Number properties can have a ``use_double`` attribute that, when set
+  to ``True``, will use double precision floating point in the database.
+* Link and Multilink properties can have several attributes:
+
+  .. index:: triple: schema; property attributes; do_journal  
+
+  - ``do_journal``: By default, every change of a link property is
+    recorded in the item being linked to (or being unlinked). A typical
+    use-case for setting ``do_journal='no'`` would be to turn off
+    journalling of nosy list, message author and message recipient link
+    and unlink events to prevent the journal from clogged with these
+    events.
+
+  .. index:: triple: schema; property attributes; try_id_parsing
+
+  - ``try_id_parsing`` is turned on by default. If entering a number
+    into a Link or Multilink field, Roundup interprets this number as an
+    ID of the item to link to. Sometimes items can have numeric names
+    (like, e.g., product codes). For these Roundup needs to match the
+    numeric name and should never match an ID. In this case you can set
+    ``try_id_parsing='no'``.
+
+  .. index:: triple: schema; property attributes; rev_multilink
+
+  - The ``rev_multilink`` option takes a property name to be inserted
+    into the linked-to class. This property is a Multilink property that
+    links back to the current class. The new Multilink is read-only (it
+    is automatically modified if the Link or Multilink property defining
+    it is modified). The new property can be used in normal searches
+    using the "filter" method of the Class. This means it can be used
+    like other Multilink properties when searching (in an index
+    template) or via the REST and XMLRPC APIs.
+
+    As a example, suppose you want to group multiple issues into a
+    super issue. Each issue can be part of only one super issue. It is
+    inefficient to find all of the issues that are part of the
+    super issue by searching through all issues in the system looking
+    at the part_of link property. To make this more efficient, you
+    can declare an issue's part_of property as::
+
+       issue = IssueClass(db, "issue",
+                 ...
+		 part_of = Link("issue", rev_multilink="components"),
+		 ... )
+
+    This automatically creates the ``components`` multilink on the issue
+    class. The ``components`` multilink is never explicitly declared in
+    the issue class, but it has the same effect as though you had
+    declared the class as::
+
+       issue = IssueClass(db, "issue",
+                 ...
+		 part_of = Link("issue"),
+		 components = Multilink("issue"),
+		 ... )
+
+    Then wrote a detector to update the components property on the
+    corresponding issue. Writing this detector can be tricky. There is
+    one other difference, you can not explicitly set/modify the
+    ``components`` multilink.
+
+    The effect of setting ``part_of = 3456`` on issue1234
+    automatically adds "1234" to the ``components`` property on
+    issue3456. You can search the ``components`` multilink just like a
+    regular multilink, but you can't explicitly assign to it.
+    Another difference of reverse multilinks to normal multilinks
+    is that when a linked node is retired, the node vanishes from the
+    multilink, e.g. in the example above, if an issue with ``part_of``
+    set to another issue is retired this issue vanishes from the
+    ``components`` multilink of the other issue.
+
+    You can also link between different classes. So you can modify
+    the issue definition to include::
+
+       issue = IssueClass(db, "issue",
+                 ...
+		 assigned_to = Link("user", rev_multilink="responsibleFor"),
+		 ... )
+   
+    This makes it easy to list all issues that the user is responsible
+    for (aka assigned_to).
+
+  .. index:: triple: schema; property attributes; msg_header_property
+
+  - The ``msg_header_property`` is used by the mail gateway when sending
+    out messages. When a link or multilink property of an issue changes,
+    Roundup creates email headers of the form::
+
+        X-Roundup-issue-prop: value
+
+    where ``value`` is the ``name`` property for the linked item(s).
+    For example, if you have a multilink for attached_files in your
+    issue, you will see a header::
+
+        X-Roundup-issue-attached_files: MySpecialFile.doc, HisResume.txt
+
+    when the class for attached files is defined as::
+
+       file = FileClass(db, "file", name=String())
+
+    ``MySpecialFile.doc`` is the name for the file object.
+
+    If you have an ``assigned_to`` property in your issue class that
+    links to the user class and you want to add a header::
+
+        X-Roundup-issue-assigned_to: ...
+
+    so that the mail recipients can filter emails where
+    ``X-Roundup-issue-assigned_to: name`` that contains their
+    username. The user class is defined as::
+
+       user = Class(db, "user",
+                username=String(),
+                password=Password(),
+                address=String(),
+                realname=String(),
+                phone=String(),
+                organisation=String(),
+                alternate_addresses=String(),
+                queries=Multilink('query'),
+                roles=String(),     # comma-separated string of Role names
+                timezone=String())
+
+    Because there is no ``name`` parameter for the user class, there
+    will be no header. However setting::
+
+       assigned_to=Link("user", msg_header_property="username")
+
+    will make the mail gateway generate an ``X-Roundup-issue-assigned_to``
+    using the username property of the linked user.
+
+    Assume assigned_to for an issue is linked to the user with
+    username=joe_user, setting::
+
+        msg_header_property="username"
+
+    for the assigned_to property will generated message headers of the
+    form::
+
+        X-Roundup-issue-assigned_to: joe_user
+
+    for emails sent on issues where joe_user has been assigned to the issue.
+
+    If this property is set to the empty string "", it will prevent
+    the header from being generated on outgoing mail.
+
+.. index:: triple: schema; class property; creator
+   triple: schema; class property; creation
+   triple: schema; class property; actor
+   triple: schema; class property; activity
+
+All Classes automatically have a number of properties by default:
+
+*creator*
+  Link to the user that created the item.
+*creation*
+  Date the item was created.
+*actor*
+  Link to the user that last modified the item.
+*activity*
+  Date the item was last modified.
+
+
+.. index:: double: schema; class methods
+
+Methods
+~~~~~~~
+
+All classes have the following methods.
+
+.. index:: triple: schema; class method; setkey
+
+setkey(property)
+::::::::::::::::
+
+.. index:: roundup-admin; setting assignedto on an issue
+
+Select a String property of the class to be the key property. The key
+property must be unique, and allows references to the items in the class
+by the content of the key property. That is, we can refer to users by
+their username: for example, let's say that there's an issue in Roundup,
+issue 23. There's also a user, richard, who happens to be user 2. To
+assign an issue to him, we could do either of::
+
+     roundup-admin set issue23 assignedto=2
+
+or::
+
+     roundup-admin set issue23 assignedto=richard
+
+Note, the same thing can be done in the web and e-mail interfaces. 
+
+.. index:: triple: schema; class method; setlabelprop
+
+setlabelprop(property)
+::::::::::::::::::::::
+
+Select a property of the class to be the label property. The label
+property is used whereever an item should be uniquely identified, e.g.,
+when displaying a link to an item. If setlabelprop is not specified for
+a class, the following values are tried for the label: 
+
+* the key of the class (see the `setkey(property)`_ section above)
+* the "name" property
+* the "title" property
+* the first property from the sorted property name list
+
+So in most cases you can get away without specifying setlabelprop
+explicitly.
+
+You should make sure that users have View access to this property or
+the id property for a class. If the property can not be viewed by a
+user, looping over items in the class (e.g. messages attached to an
+issue) will not work.
+
+.. index:: triple: schema; class method; setorderprop
+
+setorderprop(property)
+::::::::::::::::::::::
+
+Select a property of the class to be the order property. The order
+property is used whenever using a default sort order for the class,
+e.g., when grouping or sorting class A by a link to class B in the user
+interface, the order property of class B is used for sorting.  If
+setorderprop is not specified for a class, the following values are tried
+for the order property:
+
+* the property named "order"
+* the label property (see `setlabelprop(property)`_ above)
+
+So in most cases you can get away without specifying setorderprop
+explicitly.
+
+.. index:: triple: schema; class method; create
+
+create(information)
+:::::::::::::::::::
+
+Create an item in the database. This is generally used to create items
+in the :term:`definitional class` like "priority" and "status".
+
+IssueClass
+~~~~~~~~~~
+
+IssueClasses automatically include the "messages", "files", "nosy", and
+"superseder" properties.
+
+The messages and files properties list the links to the messages and
+files related to the issue. The nosy property is a list of links to
+users who wish to be informed of changes to the issue - they get "CC'ed"
+e-mails when messages are sent to or generated by the issue. The nosy
+reactor (in the ``'detectors'`` directory) handles this action. The
+superseder link indicates an issue which has superseded this one.
+
+They also have the dynamically generated "creation", "activity" and
+"creator" properties.
+
+The value of the "creation" property is the date when an item was
+created, and the value of the "activity" property is the date when any
+property on the item was last edited (equivalently, these are the dates
+on the first and last records in the item's journal). The "creator"
+property holds a link to the user that created the issue.
+
+.. index:: triple: schema; class property; content
+   triple: schema; class property; type
+
+FileClass
+~~~~~~~~~
+
+FileClasses save their "content" attribute off in a separate file from
+the rest of the database. This reduces the number of large entries in
+the database, which generally makes databases more efficient, and also
+allows us to use command-line tools to operate on the files. They are
+stored in the files sub-directory of the ``'db'`` directory in your
+tracker. FileClasses also have a "type" attribute to store the MIME
+type of the file.
+
+Roundup by default considers the contents of the file immutable. This
+is to assist in maintaining an accurate record of correspondence.  The
+distributed tracker templates do not enforce this. So if you have
+access to the Roundup tracker directory, you can edit the files (make
+sure to preserve mode, owner and group) to remove information (e.g. if
+somebody includes a password or you need to redact proprietary
+information). Obviously the journal for the message/file will not
+report that the file has changed.
+
+Best practice is to remove offending material and leave a
+placeholder. E.G. replace a password with the text::
+
+    [password has been deleted 2020-12-02 --myname]
+
+If you need to delete an entire file, replace the file contents with::
+
+    [file contents deleted due to spam 2020-10-21 --myname]
+
+rather than deleting the file. If you actually delete the file Roundup
+will report an error to the user and email the administrator. If you
+empty the file, a user downloading the file using the direct URL
+(e.g. ``tracker/msg22``) may be confused and think something is broken
+when they receive an empty file. Retiring a file/msg does not prevent
+access to the file using the direct URL. Retiring an item only removes
+it when requesting a list of all items in the class. If you are
+replacing the contents, you probably want to change the content type
+of the file. E.G. from ``image/jpeg`` to ``text/plain``. You can do
+this easily through the web interface, or using the ``roundup-admin``
+command line interface.
+
+You can also change the contents of a file or message using the REST
+interface. Note that this will NOT result in an entry in the journal,
+so again it allows a silent change. To do this you need to make two
+rest requests. An example using curl is::
+
+   $ curl -u demo:demo -s
+          -H "X-requested-with: rest" \
+          -H "Referer:  https://tracker.example.com/demo/" \
+          -X GET \
+          https://tracker.example.com/demo/rest/data/file/30/content
+   {
+      "data": {
+          "id": "30",
+          "type": "<class 'str'>",
+          "link": "https://tracker.example.com/demo/rest/data/file/30/content",
+          "data": "hello3",
+          "@etag": "\"3f2f8063dbce5b6bd43567e6f4f3c671\""
+      }
+   }
+
+using the etag, overwrite the content with::
+
+    $ curl -u demo:demo -s
+          -H "X-requested-with: rest" \
+          -H "Referer:  https://tracker.example.com/demo/" \
+          -H 'If-Match: "3f2f8063dbce5b6bd43567e6f4f3c671"' \
+          -X PUT \
+          -F "data=@hello" \
+          https://tracker.example.com/demo/rest/data/file/30/content
+
+where ``hello`` is a file on local disk.
+
+You can enforce immutability in your tracker by adding an auditor (see
+detectors_) for the file/msg class that rejects changes to the content
+property. The auditor could also add a journal entry so that a change
+via the Roundup mechanism is reported.  Using a mixin (see:
+https://wiki.roundup-tracker.org/MixinClassFileClass) to augment the
+file class allows for other possibilities including signing the file, or
+recording a checksum in the database and validating the file contents
+at the time it gets read. This allows detection of changes done on the
+filesystem outside of the Roundup mechanism.
+
+.. index:: triple: schema; class property; messages
+   triple: schema; class property; files
+   triple: schema; class property; nosy
+   triple: schema; class property; superseder
+
+.. index:: schema; item ordering
+
+A note about ordering
+~~~~~~~~~~~~~~~~~~~~~
+
+When we sort items in the hyperdb, we use one of a number of methods,
+depending on the properties being sorted on:
+
+1. If it's a String, Integer, Number, Date or Interval property, we
+   just sort the scalar value of the property. Strings are sorted
+   case-sensitively.
+2. If it's a Link property, we sort by either the linked item's "order"
+   property (if it has one) or the linked item's "id".
+3. Mulitlinks sort similar to #2, but we start with the first Multilink
+   list item, and if they're the same, we sort by the second item, and
+   so on.
+
+Note that if an "order" property is defined on a Class that is used for
+sorting, all items of that Class *must* have a value against the "order"
+property, or sorting will result in random ordering.
+
+
+Examples of adding to your schema
+---------------------------------
+
+Some examples are in the :ref:`CustomExamples` section.
+
+Also you can start with `Roundup wiki CategorySchema`_ to see a list
+of additional examples of how schemas can be customised to add new
+functionality.
+
+.. _Roundup wiki CategorySchema:
+   https://wiki.roundup-tracker.org/CategorySchema
+
+.. index:: !detectors
+.. _detectors:
+.. _Auditors and reactors:
+
+Detectors - adding behaviour to your tracker
+============================================
+
+Detectors are initialised every time you open your tracker database, so
+you're free to add and remove them any time, even after the database is
+initialised via the ``roundup-admin initialise`` command.
+
+The detectors in your tracker fire *before* (**auditors**) and *after*
+(**reactors**) changes to the contents of your database. They are Python
+modules that sit in your tracker's ``detectors`` directory. You will
+have some installed by default - have a look. You can write new
+detectors or modify the existing ones. The existing detectors installed
+for you are:
+
+.. index:: detectors; installed
+
+**nosyreaction.py**
+  This provides the automatic nosy list maintenance and email sending.
+  The nosy reactor (``nosyreaction``) fires when new messages are added
+  to issues. The nosy auditor (``updatenosy``) fires when issues are
+  changed, and figures out what changes need to be made to the nosy list
+  (such as adding new authors, etc.)
+**statusauditor.py**
+  This provides the ``chatty`` auditor which changes the issue status
+  from ``unread`` or ``closed`` to ``chatting`` if new messages appear.
+  It also provides the ``presetunread`` auditor which pre-sets the
+  status to ``unread`` on new items if the status isn't explicitly
+  defined.
+**messagesummary.py**
+  Generates the ``summary`` property for new messages based on the message
+  content.
+**userauditor.py**
+  Verifies the content of some of the user fields (email addresses and
+  roles lists).
+
+If you don't want this default behaviour, you're completely free to change
+or remove these detectors.
+
+See the detectors section in the `design document`__ for details of the
+interface for detectors.
+
+__ design.html
+
+
+.. index:: detectors; writing api
+
+Detector API
+------------
+
+.. index:: pair: detectors; auditors
+   single: auditors; function signature
+   single: auditors; defining
+   single: auditors; arguments
+
+Auditors are called with the arguments::
+
+    audit(db, cl, itemid, newdata)
+
+where ``db`` is the database, ``cl`` is an instance of Class or
+IssueClass within the database, and ``newdata`` is a dictionary mapping
+property names to values.
+
+For a ``create()`` operation, the ``itemid`` argument is None and
+newdata contains all of the initial property values with which the item
+is about to be created.
+
+For a ``set()`` operation, newdata contains only the names and values of
+properties that are about to be changed.
+
+For a ``retire()`` or ``restore()`` operation, newdata is None.
+
+.. index:: pair: detectors; reactor
+   single: reactors; function signature
+   single: reactors; defining
+   single: reactors; arguments
+
+Reactors are called with the arguments::
+
+    react(db, cl, itemid, olddata)
+
+where ``db`` is the database, ``cl`` is an instance of Class or
+IssueClass within the database, and ``olddata`` is a dictionary mapping
+property names to values.
+
+For a ``create()`` operation, the ``itemid`` argument is the id of the
+newly-created item and ``olddata`` is None.
+
+For a ``set()`` operation, ``olddata`` contains the names and previous
+values of properties that were changed.
+
+For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
+the retired or restored item and ``olddata`` is None.
+
+.. index:: detectors; additional
+
+Additional Detectors Ready For Use
+----------------------------------
+
+Sample additional detectors that have been found useful will appear in
+the ``'detectors'`` directory of the Roundup distribution. If you want
+to use one, copy it to the ``'detectors'`` of your tracker instance:
+
+**irker.py**
+  This detector sends notification on IRC through an irker daemon
+  (http://www.catb.org/esr/irker/) when issues are created or messages
+  are added.  In order to use it you need to install irker, start the
+  irkerd daemon, and add an ``[irker]`` section in ``detectors/config.ini``
+  that contains a comma-separated list of channels where the messages should
+  be sent, e.g. ``channels = irc://chat.freenode.net/channelname``.
+**newissuecopy.py**
+  This detector sends an email to a team address whenever a new issue is
+  created. The address is hard-coded into the detector, so edit it
+  before you use it (look for the text 'team@team.host') or you'll get
+  email errors!
+**creator_resolution.py**
+  Catch attempts to set the status to "resolved" - if the assignedto
+  user isn't the creator, then set the status to "confirm-done". Note that
+  "classic" Roundup doesn't have that status, so you'll have to add it. If
+  you don't want to though, it'll just use "in-progress" instead.
+**email_auditor.py**
+  If a file added to an issue is of type message/rfc822, we tack on the
+  extension .eml.
+  The reason for this is that Microsoft Internet Explorer will not open
+  things with a .eml attachment, as they deem it 'unsafe'. Worse yet,
+  they'll just give you an incomprehensible error message. For more 
+  information, see the detector code - it has a lengthy explanation.
+
+
+.. index:: auditors; rules for use
+   single: reactors; rules for use
+
+Auditor or Reactor?
+-------------------
+
+Generally speaking, the following rules should be observed:
+
+**Auditors**
+  Are used for `vetoing creation of or changes to items`_. They might
+  also make automatic changes to item properties.
+**Reactors**
+  Detect changes in the database and react accordingly. They should avoid
+  making changes to the database where possible, as this could create
+  detector loops.
+
+
+Vetoing creation of or changes to items
+---------------------------------------
+
+Auditors may raise the ``Reject`` exception to prevent the creation of
+or changes to items in the database.  The mail gateway, for example, will
+not attach files or messages to issues when the creation of those files or
+messages are prevented through the ``Reject`` exception. It'll also not create
+users if that creation is ``Reject``'ed too.
+
+To use, simply add at the top of your auditor::
+
+   from roundup.exceptions import Reject
+
+And then when your rejection criteria have been detected, simply::
+
+   raise Reject('Description of error')
+
+Error messages raised with ``Reject`` automatically have any HTML content
+escaped before being displayed to the user. To display an error message to the
+user without performing any HTML escaping the ``RejectRaw`` should be used. All
+security implications should be carefully considering before using
+``RejectRaw``.
+
+
+Generating email from Roundup
+-----------------------------
+
+The module ``roundup.mailer`` contains most of the nuts-n-bolts required
+to generate email messages from Roundup.
+
+In addition, the ``IssueClass`` methods ``nosymessage()`` and
+``send_message()`` are used to generate nosy messages, and may generate
+messages which only consist of a change note (ie. the message id parameter
+is not required - this is referred to as a "System Message" because it
+comes from "the system" and not a user).
+
+
+.. index:: extensions
+.. index:: extensions; add python functions to tracker
+.. _extensions:
+.. _actions:
+
+Extensions - adding capabilities to your tracker
+================================================
+
+While detectors_ add new behavior by reacting to changes in tracked
+objects, `extensions` add new actions and utilities to Roundup, which
+are mostly used to enhance web interface.
+
+You can create an extension by creating Python file in your tracker
+``extensions`` directory.  All files from this dir are loaded when
+tracker instance is created, at which point it calls ``init(instance)``
+from each file supplying itself as a first argument.
+
+Note that at this point web interface is not loaded, but extensions still
+can register actions for in tracker instance. This may be fixed in
+Roundup 1.6 by introducing ``init_web(client)`` callback or a more
+flexible extension point mechanism.
+
+* ``instance.registerUtil`` is used for adding `templating utilities`_
+  (see `adding a time log to your issues
+  <customizing.html#adding-a-time-log-to-your-issues-4>`_ for an example)
+
+* ``instance.registerAction`` is used to add more actions to instance
+  and to web interface. See `Defining new web actions`_ for details.
+  Generic action can be added by inheriting from ``action.Action``
+  instead of ``cgi.action.Action``.
+
+.. _interfaces.py:
+.. _modifying the core of Roundup:
+
+interfaces.py - hooking into the core of Roundup
+================================================
+
+There is a magic trick for hooking into the core of Roundup. Using
+this you can:
+
+* modify class data structures
+* monkey patch core code to add new functionality
+* modify the email gateway
+* add new rest endpoints
+* enable experimental or future features
+
+but with great power comes great responsibility.
+
+Interfaces.py has been around since the earliest releases of Roundup
+and used to be the main way to get a lot of customization done. In
+modern Roundup, the extensions_ mechanism is used to `add actions
+<#defining-new-web-actions>`_ and `templating utilities`_. But there are
+places where interfaces.py is still useful. Note that the tracker
+directories are not on the Python system path when interfaces.py is
+evaluated. You need to add library directories explictly by
+modifying sys.path.
+
+See `Changing How the Core Code Works
+<customizing.html#changing-how-the-core-code-works>`_ for examples.
+
+Database Content
+================
+
+.. note::
+   If you modify the content of a :term:`definitional class`, you will
+   need to edit the tracker `detectors`_ if they reference a value of
+   a definitional class. (E.g. if a detector checks to see if an issue
+   has a status of "open", and you change the "open" definition to be
+   "working", you need to change the check.)
+
+Customisation of the special :term:`definitional classes <definitional
+class>` (eg. status,
+priority, resolution, ...) may be done either before or after the
+tracker is initialised. The actual method of doing so is completely
+different in each case though, so be careful to use the right one.
+
+**Changing content before tracker initialisation**
+    Edit the initial_data.py module in your tracker to alter the items
+    created using the ``create( ... )`` methods.
+
+**Changing content after tracker initialisation**
+    As the "admin" user, click on the "class list" link in the web
+    interface to bring up a list of all database classes. Click on the
+    name of the class you wish to change the content of.
+
+    You may also use the ``roundup-admin`` interface's create, set and
+    retire methods to add, alter or remove items from the classes in
+    question.
+
+See "`adding a new field to the classic schema
+<customizing.html#adding-a-new-field-to-the-classic-schema>`_" for an
+example that requires database content changes.
+
+
+Security / Access Controls
+==========================
+
+A set of Permissions is built into the security module by default. For
+each Class defined in the tracker schema, the following permissions
+are defined:
+
+- Create (everything)
+- Edit (everything)
+- Search (everything) - (used if View does not permit access)
+- Retire (everything)
+- Restore (everything)
+- View (everything)
+
+All of these are assigned to the "Admin" Role by default for every
+class. They allow a user to do anything. The web and email interfaces
+also define:
+
+Email Access
+  If defined, the user may use the email interface. Used by default to deny
+  Anonymous users access to the email interface. When granted to the
+  Anonymous user, they will be automatically registered by the email
+  interface (see also the ``new_email_user_roles`` configuration option).
+
+Web Access
+  If defined, the user may use the web interface. This is usually
+  assigned to the Anonymous role as well to allow authorized users to
+  access the form based login. If some other authorization mode (basic
+  auth, SSO, etc.) is used Web Access can be removed from the
+  Anonymous user.
+
+Web Roles
+  Controls user access to editing the "roles" property of the "user" class.
+  TODO: deprecate in favour of a property-based control.
+
+Rest Access |br| Xmlrpc Access
+  These control access to the Rest and Xmlrpc endpoints. The Admin and User
+  roles have these by default in the classic tracker. See the
+  `directions in the rest interface documentation`_ and the
+  `xmlrpc interface documentation`_.
+
+Register
+  This is assigned to the anonymous user and allows automatic user
+  registration by email or web.
+
+These are hooked into the default Roles:
+
+- Admin (Create, Edit, Retire, Restore, Search, View for everything; Web Roles)
+- User (Web Access; Email Access)
+- Anonymous (Web Access)
+
+Finally, the "admin" user gets the "Admin" Role, and the "anonymous"
+user gets the "Anonymous" Role assigned when the tracker is installed.
+
+For the "User" Role, the "classic" tracker defines:
+
+- Create, Edit and View issue, file, msg, query, keyword 
+- View priority, status
+- View user
+- Edit their own user record
+
+And the "Anonymous" Role is defined as:
+
+- Web interface access
+- Register user (for registration)
+- View issue, file, msg, query, keyword, priority, status
+
+Put together, these settings appear in the tracker's ``schema.py`` file::
+
+    #
+    # TRACKER SECURITY SETTINGS
+    #
+    # See the configuration and customisation document for information
+    # about security setup.
+
+    #
+    # REGULAR USERS
+    #
+    # Give the regular users access to the web and email interface
+    db.security.addPermissionToRole('User', 'Web Access')
+    db.security.addPermissionToRole('User', 'Email Access')
+    db.security.addPermissionToRole('User', 'Rest Access')
+    db.security.addPermissionToRole('User', 'Xmlrpc Access')
+
+    # Assign the access and edit Permissions for issue, file and message
+    # to regular users now
+    for cl in 'issue', 'file', 'msg', 'keyword':
+        db.security.addPermissionToRole('User', 'View', cl)
+        db.security.addPermissionToRole('User', 'Edit', cl)
+        db.security.addPermissionToRole('User', 'Create', cl)
+    for cl in 'priority', 'status':
+        db.security.addPermissionToRole('User', 'View', cl)
+
+    # May users view other user information? Comment these lines out
+    # if you don't want them to
+    p = db.security.addPermission(name='View', klass='user', 
+	properties=('id', 'organisation', 'phone', 'realname', 'timezone',
+	'username'))
+    db.security.addPermissionToRole('User', p)
+
+    # Users should be able to edit their own details -- this permission is
+    # limited to only the situation where the Viewed or Edited item is their own.
+    def own_record(db, userid, itemid, **ctx):
+        '''Determine whether the userid matches the item being accessed.'''
+        return userid == itemid
+    p = db.security.addPermission(name='View', klass='user', check=own_record,
+        description="User is allowed to view their own user details")
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Edit', klass='user', check=own_record,
+        properties=('username', 'password', 'address', 'realname', 'phone',
+            'organisation', 'alternate_addresses', 'queries', 'timezone'),
+        description="User is allowed to edit their own user details")
+    db.security.addPermissionToRole('User', p)
+
+    # Users should be able to edit and view their own queries. They should also
+    # be able to view any marked as not private. They should not be able to
+    # edit others' queries, even if they're not private
+    def view_query(db, userid, itemid):
+	private_for = db.query.get(itemid, 'private_for')
+	if not private_for: return True
+	return userid == private_for
+    def edit_query(db, userid, itemid):
+	return userid == db.query.get(itemid, 'creator')
+    p = db.security.addPermission(name='View', klass='query', check=view_query,
+	description="User is allowed to view their own and public queries")
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Search', klass='query')
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Edit', klass='query', check=edit_query,
+	description="User is allowed to edit their queries")
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Retire', klass='query', check=edit_query,
+	description="User is allowed to retire their queries")
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Restore', klass='query', check=edit_query,
+	description="User is allowed to restore their queries")
+    db.security.addPermissionToRole('User', p)
+    p = db.security.addPermission(name='Create', klass='query',
+	description="User is allowed to create queries")
+    db.security.addPermissionToRole('User', p)
+
+    #
+    # ANONYMOUS USER PERMISSIONS
+    #
+    # Let anonymous users access the web interface. Note that almost all
+    # trackers will need this Permission. The only situation where it's not
+    # required is in a tracker that uses an HTTP Basic Authenticated front-end.
+    db.security.addPermissionToRole('Anonymous', 'Web Access')
+
+    # Let anonymous users access the email interface (note that this implies
+    # that they will be registered automatically, hence they will need the
+    # "Create" user Permission below)
+    # This is disabled by default to stop spam from auto-registering users on
+    # public trackers.
+    #db.security.addPermissionToRole('Anonymous', 'Email Access')
+
+    # Assign the appropriate permissions to the anonymous user's Anonymous
+    # Role. Choices here are:
+    # - Allow anonymous users to register
+    db.security.addPermissionToRole('Anonymous', 'Register', 'user')
+
+    # Allow anonymous users access to view issues (and the related, linked
+    # information)
+    for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status':
+        db.security.addPermissionToRole('Anonymous', 'View', cl)
+
+    # Allow the anonymous user to use the "Show Unassigned" search.
+    # It acts like "Show Open" if this permission is not available.
+    # If you are running a tracker that does not allow read access for
+    # anonymous, you should remove this entry as it can be used to perform
+    # a username guessing attack against a roundup install.
+    p = db.security.addPermission(name='Search', klass='user')
+    db.security.addPermissionToRole ('Anonymous', p)
+
+    # [OPTIONAL]
+    # Allow anonymous users access to create or edit "issue" items (and the
+    # related file and message items)
+    #for cl in 'issue', 'file', 'msg':
+    #   db.security.addPermissionToRole('Anonymous', 'Create', cl)
+    #   db.security.addPermissionToRole('Anonymous', 'Edit', cl)
+
+.. index::
+   single: roundup-admin; view class permissions
+
+You can use ``roundup-admin security`` to verify the permissions
+defined in the schema. It also verifies that properties specified in
+permissions are valid for the class. This helps detect typos that can
+cause baffling permission issues.
+
+Automatic Permission Checks
+---------------------------
+
+Permissions are automatically checked when information is rendered
+through the web. This includes:
+
+1. View checks for properties when being rendered via the ``plain()`` or
+   similar methods. If the check fails, the text "[hidden]" will be
+   displayed.
+2. Edit checks for properties when the edit field is being rendered via
+   the ``field()`` or similar methods. If the check fails, the property
+   will be rendered via the ``plain()`` method (see point 1. for subsequent
+   checking performed)
+3. View checks are performed in index pages for each item being displayed
+   such that if the user does not have permission, the row is not rendered.
+4. View checks are performed at the top of item pages for the Item being
+   displayed. If the user does not have permission, the text "You are not
+   allowed to view this page." will be displayed.
+5. View checks are performed at the top of index pages for the Class being
+   displayed. If the user does not have permission, the text "You are not
+   allowed to view this page." will be displayed.
+
+
+New User Roles
+--------------
+
+New users are assigned the Roles defined in the config file as:
+
+- NEW_WEB_USER_ROLES
+- NEW_EMAIL_USER_ROLES
+
+The `users may only edit their issues
+<customizing.html#users-may-only-edit-their-issues>`_ example shows
+customisation of these parameters.
+
+
+Changing Access Controls
+------------------------
+
+You may alter the configuration variables to change the Role that new
+web or email users get, for example to not give them access to the web
+interface if they register through email. 
+
+You may use the ``roundup-admin`` "``security``" command to display the
+current Role and Permission configuration in your tracker.
+
+
+Adding a new Permission
+~~~~~~~~~~~~~~~~~~~~~~~
+
+When adding a new Permission, you will need to:
+
+1. add it to your tracker's ``schema.py`` so it is created, using
+   ``security.addPermission``, for example::
+
+    db.security.addPermission(name="View", klass='frozzle',
+        description="User is allowed to access frozzles")
+
+   will set up a new "View" permission on the Class "frozzle".
+2. enable it for the Roles that should have it (verify with
+   "``roundup-admin security``")
+3. add it to the relevant HTML interface templates
+4. add it to the appropriate xxxPermission methods on in your tracker
+   interfaces module
+
+The ``addPermission`` method takes a few optional parameters:
+
+**properties**
+  A sequence of property names that are the only properties to apply the
+  new Permission to (eg. ``... klass='user', properties=('name',
+  'email') ...``)
+**props_only**
+  A boolean value (set to false by default) that is a new feature
+  in Roundup 1.6.
+  A permission defined using:
+
+     ``properties=('list', 'of', 'property', 'names')``
+
+  is used to determine access for things other than just those
+  properties. For example a check for View permission on the issue
+  class or an issue item can use any View permission for the issue
+  class even if that permission has a property list.  This can be
+  confusing and surprising as you would think that a permission
+  including properties would be used only for determining the
+  access permission for those properties.
+
+  ``roundup-admin security`` will report invalid properties for the
+  class. For example a permission with an invalid summary property is
+  presented as::
+
+     Allowed to see content of object regardless of spam status
+        (View for "file": ('content', 'summary') only)
+
+     **Invalid properties for file: ['summary']
+
+  Setting ``props_only=True`` will make the permission valid only for
+  those properties.
+
+  If you use a lot of permissions with property checks, it can be
+  difficult to change all of them. Calling the function:
+
+    db.security.set_props_only_default(True)
+
+  at the top of ``schema.py`` will make every permission creation
+  behave as though props_only was set to True. It is expected that the
+  default of True will become the default in a future Roundup release.
+**check**
+  A function to be executed which returns boolean determining whether
+  the Permission is allowed. If it returns True, the permission is
+  allowed, if it returns False the permission is denied.  The function
+  can have one of two signatures::
+
+     check(db, userid, itemid)
+
+  or::
+
+     check(db, userid, itemid, **ctx)
+
+  where ``db`` is a handle on the open database, ``userid`` is
+  the user attempting access and ``itemid`` is the specific item being
+  accessed. If the second form is used the ``ctx`` dictionary is
+  defined with the following values::
+
+     ctx['property'] the name of the property being checked or None if
+            it's a class check.
+
+     ctx['classname'] the name of the class that is being checked
+            (issue, query ....).
+
+     ctx['permission'] the name of the permission (e.g. View, Edit...).
+
+The second form is preferred as it makes it easier to implement more
+complex permission schemes. An `example in upgrading.html
+<upgrading.html#enhancement-to-check-command-for-permissions>`_
+shows the use of ``ctx``.
+
+Example Scenarios
+~~~~~~~~~~~~~~~~~
+
+See the `examples <customizing.html#examples>`_ section for longer
+examples of customisation.
+
+**anonymous access through the e-mail gateway**
+ Give the "anonymous" user the "Email Access", ("Edit", "issue") and
+ ("Create", "msg") Permissions but do not not give them the ("Create",
+ "user") Permission. This means that when an unknown user sends email
+ into the tracker, they're automatically logged in as "anonymous".
+ Since they don't have the ("Create", "user") Permission, they won't
+ be automatically registered, but since "anonymous" has permission to
+ use the gateway, they'll still be able to submit issues. Note that
+ the Sender information - their email address - will not be available
+ - they're *anonymous*.
+
+**automatic registration of users in the e-mail gateway**
+ By giving the "anonymous" user the ("Register", "user") Permission, any
+ unidentified user will automatically be registered with the tracker
+ (with no password, so they won't be able to log in through
+ the web until an admin sets their password). By default new Roundup
+ trackers don't allow this as it opens them up to spam. It may be enabled
+ by uncommenting the appropriate addPermissionToRole in your tracker's
+ ``schema.py`` file. The new user is given the Roles list defined in the
+ "new_email_user_roles" config variable. 
+
+**only developers may be assigned issues**
+ Create a new Permission called "Fixer" for the "issue" class. Create a
+ new Role "Developer" which has that Permission, and assign that to the
+ appropriate users. Filter the list of users available in the assignedto
+ list to include only those users. Enforce the Permission with an
+ auditor. See the example 
+ `restricting the list of users that are assignable to a task
+ <customizing.html#restricting-the-list-of-users-that-are-assignable-to-a-task>`_.
+
+**only managers may sign off issues as complete**
+ Create a new Permission called "Closer" for the "issue" class. Create a
+ new Role "Manager" which has that Permission, and assign that to the
+ appropriate users. In your web interface, only display the "resolved"
+ issue state option when the user has the "Closer" Permissions. Enforce
+ the Permission with an auditor. This is very similar to the previous
+ example, except that the web interface check would look like::
+
+   <option tal:condition="python:request.user.hasPermission('Closer')"
+           value="resolved">Resolved</option>
+ 
+**don't give web access to users who register through email**
+ Create a new Role called "Email User" which has all the Permissions of
+ the normal "User" Role minus the "Web Access" Permission. This will
+ allow users to send in emails to the tracker, but not access the web
+ interface.
+
+**let some users edit the details of all users**
+ Create a new Role called "User Admin" which has the Permission for
+ editing users::
+
+    db.security.addRole(name='User Admin', description='Managing users')
+    p = db.security.getPermission('Edit', 'user')
+    db.security.addPermissionToRole('User Admin', p)
+
+ and assign the Role to the users who need the permission.
+
+
+Web Interface
+=============
+
+.. contents::
+   :local:
+
+The web interface is provided by the ``roundup.cgi.client`` module and
+is used by ``roundup.cgi``, ``roundup-server`` and ``ZRoundup``
+(``ZRoundup``  is broken, until further notice). In all cases, we
+determine which tracker is being accessed (the first part of the URL
+path inside the scope of the CGI handler) and pass control on to the
+``roundup.cgi.client.Client`` class - which handles the rest of the
+access through its ``main()`` method. This means that you can do pretty
+much anything you want as a web interface to your tracker.
+
+
+
+Repercussions of changing the tracker schema
+---------------------------------------------
+
+If you choose to change the `tracker schema`_ you will need to ensure
+the web interface knows about it:
+
+1. Index, item and search pages for the relevant classes may need to
+   have properties added or removed,
+2. The "page" template may require links to be changed, as might the
+   "home" page's content arguments.
+
+
+How requests are processed
+--------------------------
+
+The basic processing of a web request proceeds as follows:
+
+1. figure out who we are, defaulting to the "anonymous" user
+2. figure out what the request is for - we call this the "context"
+3. handle any requested action (item edit, search, ...)
+4. render the template requested by the context, resulting in HTML
+   output
+
+In some situations, exceptions occur:
+
+- HTTP Redirect  (generally raised by an action)
+    the URL to redirect to is the first argument of the exception.
+- SendFile       (generally raised by ``determine_context``)
+    here we serve up a FileClass "content" property
+- SendStaticFile (generally raised by ``determine_context``)
+    here we serve up a file from the tracker "html" directory
+- Unauthorised   (generally raised by an action)
+    here the action is cancelled, the request is rendered and an error
+    message is displayed indicating that permission was not granted for
+    the action to take place
+- NotFound       (raised wherever it needs to be)
+    this exception percolates up to the CGI interface that called the
+    client
+
+
+Roundup URL design
+------------------
+
+Each tracker has several hardcoded URLs. These three are equivalent and
+lead to the main tracker page:
+
+1. ``/``
+2. ``/index``
+3. ``/home``
+
+The following prefix is used to access static resources:
+
+4. ``/@@file/``
+
+Two additional url's are used for the API's.
+The `REST api`_ is accessed via:
+ 
+5. ``/rest/``
+
+.. _`REST api`: rest.html
+
+and the `XMLRPC api`_ is available at:
+
+6. ``/xmlrpc``
+
+.. _`XMLRPC api`: xmlrpc.html
+
+All other URLs depend on the classes configured in Roundup database.
+Each class receives two URLs - one for the class itself and another
+for specific items of that class. Example for class URL:
+
+7. ``/issue``
+
+This is usually used to show listings of class items. The URL for
+for specific object of issue class with id 1 will look like:
+
+8. ``/issue1``
+
+.. _strip_zeros:
+
+Note that a leading string of 0's will be stripped from the id part of
+the object :term:`designator` in the URL. E.G. ``/issue001`` is the
+same as ``/issue1``.  Similarly for ``/file01`` etc. However you
+should generate URL's without the extra zeros.
+
+Determining web context
+-----------------------
+
+To determine the "context" of a request (what request is for), we look at
+the URL path after the tracker root and at ``@template`` request
+parameter. Typical URL paths look like:
+
+1.  ``/tracker/issue``
+2.  ``/tracker/issue1``
+3.  ``/tracker/@@file/style.css``
+4.  ``/cgi-bin/roundup.cgi/tracker/file1``
+5.  ``/cgi-bin/roundup.cgi/tracker/file1/kitten.png``
+
+where tracker root is ``/tracker/`` or ``/cgi-bin/roundup.cgi/tracker/``
+We're looking at "issue", "issue1", "@@file/style.css", "file1" and
+"file1/kitten.png" in the cases above.
+
+1. with is no path we are in the "home" context. See `the "home"
+   context`_ below for details. "index" or "home" paths may also be used
+   to switch into "home" context.
+2. for paths starting with "@@file" the additional path entry ("style.css"
+   in the example above) specifies the static file to be served
+   from the tracker TEMPLATES directory (or STATIC_FILES, if configured).
+   This is usually the tracker's "html" directory. Internally this works
+   by raising SendStaticFile exception.
+3. if there is something in the path (as in example 1, "issue"), it
+   identifies the tracker class to display.
+4. if the path is an item designator (as in examples 2 and 4, "issue1"
+   and "file1"), then we're to display a specific item.
+   :ref:`Note. <strip_zeros>`
+5. if the path starts with an item designator and is longer than one
+   entry (as in example 5, "file1/kitten.png"), then we're assumed to be
+   handling an item of a ``FileClass``, and the extra path information
+   gives the filename that the client is going to label the download
+   with (i.e. "file1/kitten.png" is nicer to download than "file1").
+   This raises a ``SendFile`` exception.
+
+Neither 2. or 5. use templates and stop before the template is
+determined. For other contexts the template used is specified by the
+``@template`` variable, which defaults to:
+
+- only classname supplied:       "index"
+- full item designator supplied: "item"
+
+
+The "home" Context
+------------------
+
+The "home" context is special because it allows you to add templated
+pages to your tracker that don't rely on a class or item (ie. an issues
+list or specific issue).
+
+Let's say you wish to add frames to control the layout of your tracker's
+interface. You'd probably have:
+
+- A top-level frameset page. This page probably wouldn't be templated, so
+  it could be served as a static file (see `serving static content`_)
+- A sidebar frame that is templated. Let's call this page
+  "home.navigation.html" in your tracker's "html" directory. To load that
+  page up, you use the URL::
+
+    <tracker url>/home?@template=navigation
+
+
+Serving static content
+----------------------
+
+See the previous section `determining web context`_ where it describes
+``@@file`` paths.
+
+These files are served without any permission checks. Any user on the
+internet with the url can download the file.
+
+This is rarely an issue since the html templates are just source code
+and much of it can be found in the Roundup repository. Other
+decoration (logos, stylesheets) are similarly not security sensitive.
+You can use the static_files setting in config.ini to eliminate
+access to the templates directory if desired.
+
+If a file resolves to a symbolic link, it is not served.
+
+Performing actions in web requests
+----------------------------------
+
+When a user requests a web page, they may optionally also request for an
+action to take place. As described in `how requests are processed`_, the
+action is performed before the requested page is generated. Actions are
+triggered by using a ``@action`` CGI variable, where the value is one
+of:
+
+**login**
+ Attempt to log a user in.
+
+**logout**
+ Log the user out - make them "anonymous".
+
+**register**
+ Attempt to create a new user based on the contents of the form and then
+ log them in.
+
+**edit**
+ Perform an edit of an item in the database. There are some `special form
+ variables`_ you may use. Also you can set the ``__redirect_to`` form
+ variable to the URL that should be displayed after the edit is succesfully
+ completed. If you wanted to edit a sequence of issues, users etc. this
+ could be used to display the next item in the sequence to the user. 
+
+**new**
+ Add a new item to the database. You may use the same `special form
+ variables`_ as in the "edit" action. Also you can set the
+ ``__redirect_to`` form variable to the URL that should be displayed after
+ the new item is created. This is useful if you want to create another
+ item rather than edit the newly created item.
+
+**retire**
+ Retire the item in the database.
+
+**editCSV**
+ Performs an edit of all of a class' items in one go. See also the
+ *class*.csv templating method which generates the CSV data to be
+ edited, and the ``'_generic.index'`` template which uses both of these
+ features.
+
+**search**
+ Mangle some of the form variables:
+
+ - Set the form ":filter" variable based on the values of the filter
+   variables - if they're set to anything other than "dontcare" then add
+   them to :filter.
+
+ - Also handle the ":queryname" variable and save off the query to the
+   user's query list.
+
+Each of the actions is implemented by a corresponding ``*XxxAction*`` (where
+"Xxx" is the name of the action) class in the ``roundup.cgi.actions`` module.
+These classes are registered with ``roundup.cgi.client.Client``. If you need
+to define new actions, you may add them there (see `defining new
+web actions`_).
+
+Each action class also has a ``*permission*`` method which determines whether
+the action is permissible given the current user. The base permission checks
+for each action are:
+
+**login**
+ Determine whether the user has the "Web Access" Permission.
+**logout**
+ No permission checks are made.
+**register**
+ Determine whether the user has the ("Create", "user") Permission.
+**edit**
+ Determine whether the user has permission to edit this item. If we're
+ editing the "user" class, users are allowed to edit their own details -
+ unless they try to edit the "roles" property, which requires the
+ special Permission "Web Roles".
+**new**
+ Determine whether the user has permission to create this item. No
+ additional property checks are made. Additionally, new user items may
+ be created if the user has the ("Create", "user") Permission.
+**editCSV**
+ Determine whether the user has permission to edit this class.
+**search**
+ Determine whether the user has permission to view this class.
+
+Protecting users from web application attacks
+---------------------------------------------
+
+There is a class of attacks known as Cross Site Request Forgeries
+(CSRF). Malicious code running in the browser can making a
+request to Roundup while you are logged into Roundup.  The
+malicious code piggy backs on your existing Roundup session to
+make changes without your knowledge. Roundup 1.6 has support for
+defending against this by analyzing the
+
+* Referer,
+* Origin, and
+* Host or 
+* X-Forwarded-Host
+
+HTTP headers. It compares the headers to the value of the web setting
+in the [tracker] section of the tracker's ``config.ini``.
+
+Also a per form token (also called a nonce) can be enabled for
+the tracker using the ``csrf_enforce_token`` option in
+config.ini.  When enabled, Roundup will validate a hidden form
+field called ``@csrf``. If the validation fails (or the token
+is used more than once) the request is rejected.  The ``@csrf``
+input field is added automatically by calling the ``submit``
+function/path. It can also be added manually by calling
+anti_csrf_nonce() directly. For example::
+
+   <input name="@csrf" type="hidden"
+      tal:attributes="value python:utils.anti_csrf_nonce(lifetime=10)">
+
+By default a nonce lifetime is 2 weeks. However the lifetime (in
+minutes) can be set by passing a lifetime argument as shown
+above. The example above makes the nonce lifetime 10 minutes.
+ 
+Search for @csrf in this document for more examples. There are
+more examples and information in ``upgrading.txt``.
+
+The token protects you because malicious code supplied by another
+site is unable to obtain the token. Thus many attempts they make
+to submit a request are rejected.
+
+The protection on the xmlrpc interface is untested, but is based
+on a valid header check against the Roundup url and the presence
+of the ``X-REQUESTED-WITH`` header. Work to improve this is a
+future project after the 1.6 release.
+
+The enforcement levels can be modified in ``config.ini``. Refer to
+that file for details.
+
+Special form variables
+----------------------
+
+Item properties and their values are edited with html FORM
+variables and their values. You can:
+
+- Change the value of some property of the current item.
+- Create a new item of any class, and edit the new item's
+  properties,
+- Attach newly created items to a multilink property of the
+  current item.
+- Remove items from a multilink property of the current item.
+- Specify that some properties are required for the edit
+  operation to be successful.
+- Redirect to a different page after creating a new item (new action
+  only, not edit action). Usually you end up on the page for the
+  created item.
+- Set up user interface locale.
+
+These operations will only take place if the form action (the
+``@action`` variable) is "edit" or "new".
+
+In the following, <bracketed> values are variable, "@" may be
+either ":" or "@", and other text "required" is fixed.
+
+Two special form variables are used to specify user language preferences:
+
+``@language``
+  value may be locale name or ``none``. If this variable is set to
+  locale name, web interface language is changed to given value
+  (provided that appropriate translation is available), the value
+  is stored in the browser cookie and will be used for all following
+  requests.  If value is ``none`` the cookie is removed and the
+  language is changed to the tracker default, set up in the tracker
+  configuration or OS environment.
+
+``@charset``
+  value may be character set name or ``none``.  Character set name
+  is stored in the browser cookie and sets output encoding for all
+  HTML pages generated by Roundup.  If value is ``none`` the cookie
+  is removed and HTML output is reset to Roundup internal encoding
+  (UTF-8).
+
+Most properties are specified as form variables:
+
+``<propname>``
+  property on the current context item
+
+``<designator>"@"<propname>``
+  property on the indicated item (for editing related information)
+
+Designators name a specific item of a class.
+
+``<classname><N>``
+    Name an existing item of class <classname>.
+
+``<classname>"-"<N>``
+    Name the <N>th new item of class <classname>. If the form
+    submission is successful, a new item of <classname> is
+    created. Within the submitted form, a particular
+    designator of this form always refers to the same new
+    item.
+
+Once we have determined the "propname", we look at it to see
+if it's special:
+
+``@required``
+    The associated form value is a comma-separated list of
+    property names that must be specified when the form is
+    submitted for the edit operation to succeed.  
+
+    When the <designator> is missing, the properties are
+    for the current context item.  When <designator> is
+    present, they are for the item specified by
+    <designator>.
+
+    The "@required" specifier must come before any of the
+    properties it refers to are assigned in the form.
+
+``@remove@<propname>=id(s)`` or ``@add@<propname>=id(s)``
+    The "@add@" and "@remove@" edit actions apply only to
+    Multilink properties.  The form value must be a
+    comma-separate list of keys for the class specified by
+    the simple form variable.  The listed items are added
+    to (respectively, removed from) the specified
+    property.
+
+``@link@<propname>=<designator>``
+    If the edit action is "@link@", the simple form
+    variable must specify a Link or Multilink property.
+    The form value is a comma-separated list of
+    designators.  The item corresponding to each
+    designator is linked to the property given by simple
+    form variable.
+
+None of the above (ie. just a simple form value)
+    The value of the form variable is converted
+    appropriately, depending on the type of the property.
+
+    For a Link('klass') property, the form value is a
+    single key for 'klass', where the key field is
+    specified in schema.py.  
+
+    For a Multilink('klass') property, the form value is a
+    comma-separated list of keys for 'klass', where the
+    key field is specified in schema.py.  
+
+    Note that for simple-form-variables specifiying Link
+    and Multilink properties, the linked-to class must
+    have a key field.
+
+    For a String() property specifying a filename, the
+    file named by the form value is uploaded. This means we
+    try to set additional properties "filename" and "type" (if
+    they are valid for the class).  Otherwise, the property
+    is set to the form value.
+
+    For Date(), Interval(), Boolean(), Integer() and Number()
+    properties, the form value is converted to the
+    appropriate value.
+
+Any of the form variables may be prefixed with a classname or
+designator.
+
+Setting the form variable: ``__redirect_to=`` to a url when
+@action=new redirects the user to the specified url after successfully
+creating the new item. This is useful if you want the user to create
+another item rather than edit the newly created item.  Note that the
+url assigned to ``__redirect_to`` must be url encoded/quoted and be
+under the tracker's base url. If the base_url uses http, you can set
+the url to https.
+
+Two special form values are supported for backwards compatibility:
+
+@note
+    This is equivalent to::
+
+        @link@messages=msg-1
+        msg-1@content=value
+
+    which is equivalent to the html::
+
+        <textarea name="msg-1@content"></textarea>
+        <input type="hidden" name="@link@messages" value="msg-1">
+
+    except that in addition, the "author" and "date" properties of
+    "msg-1" are set to the userid of the submitter, and the current
+    time, respectively.
+
+@file
+    This is equivalent to::
+
+        @link@files=file-1
+        file-1@content=value
+
+    by adding the HTML::
+
+        <input type="file" name="file-1@content">
+        <input type="hidden" name="@link@files" value="file-1">
+
+    The String content value is handled as described above for file
+    uploads.
+
+If both the "@note" and "@file" form variables are
+specified, the action::
+
+        msg-1@link@files=file-1
+
+is also performed. This would be expressed in HTML with::
+
+        <input type="hidden" name="msg-1@link@files" value="file-1">
+
+We also check that FileClass items have a "content" property with
+actual content, otherwise we remove them from all_props before
+returning.
+
+
+Default templates
+-----------------
+
+The default templates are html4 compliant. If you wish to change them to be
+xhtml compliant, you'll need to change the ``html_version`` configuration
+variable in ``config.ini`` to ``'xhtml'`` instead of ``'html4'``.
+
+Most customisation of the web view can be done by modifying the
+templates in the tracker ``'html'`` directory. There are several types
+of files in there. The *minimal* template includes:
+
+**page.html**
+  This template usually defines the overall look of your tracker. When
+  you view an issue, it appears inside this template. When you view an
+  index, it also appears inside this template. This template defines a
+  macro called "icing" which is used by almost all other templates as a
+  coating for their content, using its "content" slot. It also defines
+  the "head_title" and "body_title" slots to allow setting of the page
+  title.
+**home.html**
+  the default page displayed when no other page is indicated by the user
+**home.classlist.html**
+  a special version of the default page that lists the classes in the
+  tracker
+**classname.item.html**
+  displays an item of the *classname* class
+**classname.index.html**
+  displays a list of *classname* items
+**classname.search.html**
+  displays a search page for *classname* items
+**_generic.index.html**
+  used to display a list of items where there is no
+  ``*classname*.index`` available
+**_generic.help.html**
+  used to display a "class help" page where there is no
+  ``*classname*.help``
+**user.register.html**
+  a special page just for the user class, that renders the registration
+  page
+**style.css**
+  a static file that is served up as-is
+
+The *classic* template has a number of additional templates.
+
+Remember that you can create any template extension you want to,
+so if you just want to play around with the templating for new issues,
+you can copy the current "issue.item" template to "issue.test", and then
+access the test template using the "@template" URL argument::
+
+   http://your.tracker.example/tracker/issue?@template=test
+
+and it won't affect your users using the "issue.item" template.
+
+You can also put templates into a subdirectory of the template
+directory. So if you specify::
+
+   http://your.tracker.example/tracker/issue?@template=test/item
+
+you will use the template at: ``test/issue.item.html``. If that
+template doesn't exit it will try to use
+``test/_generic.item.html``. If that template doesn't exist
+it will return an error.
+
+Implementing Modal Editing Using @template
+------------------------------------------
+
+Many item templates allow you to edit the item. They contain
+code that renders edit boxes if the user has edit permissions.
+Otherwise the template will just display the item information.
+
+In some cases you want to do a modal edit. The user has to take some
+action (click a button or follow a link) to shift from display mode to
+edit mode. When the changes are submitted, ending the edit mode,
+the user is returned to display mode.
+
+Modal workflows usually slow things down and are not implemented by
+default templates. However for some workflows a modal edit is useful.
+For example a batch edit mode that allows the user to edit a number of
+issues all from one form could be implemented as a modal workflow of:
+
+* search for issues to modify
+* switch to edit mode and change values
+* exit back to the results of the search
+
+To implement the modal edit, assume you have an issue.edit.html
+template that implements an edit form.  On the display page (a version
+of issue.item.html modified to only display information) add a link
+that calls the display url, but adds ``@template=edit`` to the link.
+
+This will now display the edit page. On the edit page you want to add
+a hidden text field to your form named ``@template`` with the value:
+``item|edit``.  When the form is submitted it is validated. If the
+form is correct the user will see the item rendered using the item
+template. If there is an error (validation failed) the item will be
+rendered using the edit template. The edit template that is rendered
+will display all the changes that the user made to the form before it
+was submitted. The user can correct the error and resubmit the changes
+until the form validates.
+
+If the form failed to validate but the ``@template`` field had the
+value ``item`` the user would still see the error, but all of the data
+the user entered would be discarded. The user would have to redo all
+the edits again.
+
+
+How the templates work
+----------------------
+
+
+Templating engines
+~~~~~~~~~~~~~~~~~~
+
+Since version 1.4.20 Roundup supports two templating engines:
+
+* the original `Template Attribute Language`_ (TAL) engine from Zope
+* the standalone Chameleon templating engine. Chameleon is intended
+  as a replacement for the original TAL engine, and supports the
+  same syntax, but they are not 100% compatible. The major (and most
+  likely the only) incompatibility is the default expression type being
+  ``python:`` instead of ``path:``. See also "Incompatibilities and
+  differences" section of `Chameleon documentation`__.
+
+Version 1.5.0 added experimental support for the `jinja2`_ templating
+language. You must install the `jinja2`_ module in order to use it. The
+``jinja2`` template supplied with Roundup has the templates rewritten
+to use ``jinja2`` rather than TAL. A number of trackers are running
+using ``jinja2`` templating so it is considered less experimental than
+Chameleon templating.
+
+.. _jinja2: https://palletsprojects.com/p/jinja/
+
+
+**NOTE1**: For historical reasons, examples given below assumes path
+expression as default expression type. With Chameleon you have to manually
+resolve the path expressions. A Chameleon-based, z3c.pt, that is fully
+compatible with the old TAL implementation, is planned to be included in a
+future release.
+
+**NOTE2**: As of 1.4.20 Chameleon support is highly experimental and **not**
+recommended for production use.
+
+.. _Chameleon:
+   https://pypi.org/project/Chameleon/
+.. _z3c.pt:
+   https://pypi.org/project/z3c.pt/
+__ https://chameleon.readthedocs.io/en/latest/reference.html?highlight=differences#incompatibilities-and-differences
+.. _TAL:
+.. _Template Attribute Language:
+   https://pagetemplates.readthedocs.io/en/latest/history/TALSpecification14.html
+
+
+Basic Templating Actions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Roundup's templates consist of special attributes on the HTML tags.
+These attributes form the **Template Attribute Language**, or TAL.
+The basic TAL commands are:
+
+**tal:define="variable expression; variable expression; ..."**
+   Define a new variable that is local to this tag and its contents. For
+   example::
+
+      <html tal:define="title request/description">
+       <head><title tal:content="title"></title></head>
+      </html>
+
+   In this example, the variable "title" is defined as the result of the
+   expression "request/description". The "tal:content" command inside the
+   <html> tag may then use the "title" variable.
+
+**tal:condition="expression"**
+   Only keep this tag and its contents if the expression is true. For
+   example::
+
+     <p tal:condition="python:request.user.hasPermission('View', 'issue')">
+      Display some issue information.
+     </p>
+
+   In the example, the <p> tag and its contents are only displayed if
+   the user has the "View" permission for issues. We consider the number
+   zero, a blank string, an empty list, and the built-in variable
+   nothing to be false values. Nearly every other value is true,
+   including non-zero numbers, and strings with anything in them (even
+   spaces!).
+
+**tal:repeat="variable expression"**
+   Repeat this tag and its contents for each element of the sequence
+   that the expression returns, defining a new local variable and a
+   special "repeat" variable for each element. For example::
+
+     <tr tal:repeat="u user/list">
+      <td tal:content="u/id"></td>
+      <td tal:content="u/username"></td>
+      <td tal:content="u/realname"></td>
+     </tr>
+
+   The example would iterate over the sequence of users returned by
+   "user/list" and define the local variable "u" for each entry. Using
+   the repeat command creates a new variable called "repeat" which you
+   may access to gather information about the iteration. See the section
+   below on `the repeat variable`_.
+
+**tal:replace="expression"**
+   Replace this tag with the result of the expression. For example::
+
+    <span tal:replace="request/user/realname" />
+
+   The example would replace the <span> tag and its contents with the
+   user's realname. If the user's realname was "Bruce", then the
+   resultant output would be "Bruce".
+
+**tal:content="expression"**
+   Replace the contents of this tag with the result of the expression.
+   For example::
+
+    <span tal:content="request/user/realname">user's name appears here
+    </span>
+
+   The example would replace the contents of the <span> tag with the
+   user's realname. If the user's realname was "Bruce" then the
+   resultant output would be "<span>Bruce</span>".
+
+**tal:attributes="attribute expression; attribute expression; ..."**
+   Set attributes on this tag to the results of expressions. For
+   example::
+
+     <a tal:attributes="href string:user${request/user/id}">My Details</a>
+
+   In the example, the "href" attribute of the <a> tag is set to the
+   value of the "string:user${request/user/id}" expression, which will
+   be something like "user123".
+
+**tal:omit-tag="expression"**
+   Remove this tag (but not its contents) if the expression is true. For
+   example::
+
+      <span tal:omit-tag="python:1">Hello, world!</span>
+
+   would result in output of::
+
+      Hello, world!
+
+Note that the commands on a given tag are evaulated in the order above,
+so *define* comes before *condition*, and so on.
+
+Additionally, you may include tags such as <tal:block>, which are
+removed from output. Its content is kept, but the tag itself is not (so
+don't go using any "tal:attributes" commands on it). This is useful for
+making arbitrary blocks of HTML conditional or repeatable (very handy
+for repeating multiple table rows, which would otherwise require an
+illegal tag placement to effect the repeat).
+
+
+Templating Expressions
+~~~~~~~~~~~~~~~~~~~~~~
+
+Templating Expressions are covered by `Template Attribute Language
+Expression Syntax`_, or TALES. The expressions you may use in the
+attribute values may be one of the following forms:
+
+**Path Expressions** - eg. ``item/status/checklist``
+   These are object attribute / item accesses. Roughly speaking, the
+   path ``item/status/checklist`` is broken into parts ``item``,
+   ``status`` and ``checklist``. The ``item`` part is the root of the
+   expression. We then look for a ``status`` attribute on ``item``, or
+   failing that, a ``status`` item (as in ``item['status']``). If that
+   fails, the path expression fails. When we get to the end, the object
+   we're left with is evaluated to get a string - if it is a method, it
+   is called; if it is an object, it is stringified. Path expressions
+   may have an optional ``path:`` prefix, but they are the default
+   expression type, so it's not necessary.
+
+   If an expression evaluates to ``default``, then the expression is
+   "cancelled" - whatever HTML already exists in the template will
+   remain (tag content in the case of ``tal:content``, attributes in the
+   case of ``tal:attributes``).
+
+   If an expression evaluates to ``nothing`` then the target of the
+   expression is removed (tag content in the case of ``tal:content``,
+   attributes in the case of ``tal:attributes`` and the tag itself in
+   the case of ``tal:replace``).
+
+   If an element in the path may not exist, then you can use the ``|``
+   operator in the expression to provide an alternative. So, the
+   expression ``request/form/foo/value | default`` would simply leave
+   the current HTML in place if the "foo" form variable doesn't exist.
+
+   You may use the python function ``path``, as in
+   ``path("item/status")``, to embed path expressions in Python
+   expressions.
+
+**String Expressions** - eg. ``string:hello ${user/name}`` 
+   These expressions are simple string interpolations - though they can
+   be just plain strings with no interpolation if you want. The
+   expression in the ``${ ... }`` is just a path expression as above.
+
+**Python Expressions** - eg. ``python: 1+1`` 
+   These expressions give the full power of Python. All the "root level"
+   variables are available, so ``python:item.status.checklist()`` would
+   be equivalent to ``item/status/checklist``, assuming that
+   ``checklist`` is a method.
+
+Modifiers:
+
+**structure** - eg. ``structure python:msg.content.plain(hyperlink=1)``
+   The result of expressions are normally *escaped* to be safe for HTML
+   display (all "<", ">" and "&" are turned into special entities). The
+   ``structure`` expression modifier turns off this escaping - the
+   result of the expression is now assumed to be HTML, which is passed
+   to the web browser for rendering.
+
+**not:** - eg. ``not:python:1=1``
+   This simply inverts the logical true/false value of another
+   expression.
+
+.. _TALES:
+.. _Template Attribute Language Expression Syntax:
+   https://pagetemplates.readthedocs.io/en/latest/history/TALESSpecification13.html
+
+
+Template Macros
+~~~~~~~~~~~~~~~
+
+Macros are used in Roundup to save us from repeating the same common
+page stuctures over and over. The most common (and probably only) macro
+you'll use is the "icing" macro defined in the "page" template.
+
+Macros are generated and used inside your templates using special
+attributes similar to the `basic templating actions`_. In this case,
+though, the attributes belong to the `Macro Expansion Template
+Attribute Language`_, or METAL. The macro commands are:
+
+**metal:define-macro="macro name"**
+  Define that the tag and its contents are now a macro that may be
+  inserted into other templates using the *use-macro* command. For
+  example::
+
+    <html metal:define-macro="page">
+     ...
+    </html>
+
+  defines a macro called "page" using the ``<html>`` tag and its
+  contents. Once defined, macros are stored on the template they're
+  defined on in the ``macros`` attribute. You can access them later on
+  through the ``templates`` variable, eg. the most common
+  ``templates/page/macros/icing`` to access the "page" macro of the
+  "page" template.
+
+**metal:use-macro="path expression"**
+  Use a macro, which is identified by the path expression (see above).
+  This will replace the current tag with the identified macro contents.
+  For example::
+
+   <tal:block metal:use-macro="templates/page/macros/icing">
+    ...
+   </tal:block>
+
+   will replace the tag and its contents with the "page" macro of the
+   "page" template.
+
+**metal:define-slot="slot name"** and **metal:fill-slot="slot name"**
+  To define *dynamic* parts of the macro, you define "slots" which may
+  be filled when the macro is used with a *use-macro* command. For
+  example, the ``templates/page/macros/icing`` macro defines a slot like
+  so::
+
+    <title metal:define-slot="head_title">title goes here</title>
+
+  In your *use-macro* command, you may now use a *fill-slot* command
+  like this::
+
+    <title metal:fill-slot="head_title">My Title</title>
+
+  where the tag that fills the slot completely replaces the one defined
+  as the slot in the macro.
+
+Note that you may not mix `METAL`_ and `TAL`_ commands on the same tag, but
+TAL commands may be used freely inside METAL-using tags (so your
+*fill-slots* tags may have all manner of TAL inside them).
+
+.. _METAL:
+.. _Macro Expansion Template Attribute Language:
+   https://pagetemplates.readthedocs.io/en/latest/history/TALESSpecification13.html
+
+Information available to templates
+----------------------------------
+
+This is implemented by ``roundup.cgi.templating.RoundupPageTemplate``
+
+The following variables are available to templates.
+
+**context**
+  The current context. This is either None, a `hyperdb class wrapper`_
+  or a `hyperdb item wrapper`_
+
+**request**
+  Includes information about the current request, including:
+
+  - the current index information (``filterspec``, ``filter``
+    args, ``properties``, etc) parsed out of the form. 
+  - methods for easy filterspec link generation
+  - "form"
+    The current CGI form information as a mapping of form argument name
+    to value (specifically a cgi.FieldStorage)
+  - "env" the CGI environment variables
+  - "base" the base URL for this instance
+  - "user" a HTMLItem instance for the current user
+  - "language" as determined by the browser or config
+  - "classname" the current classname (possibly None)
+  - "template" the current template (suffix, also possibly None)
+**config**
+  This variable holds all the values defined in the tracker config.ini
+  file (eg. TRACKER_NAME, etc.)
+**db**
+  The current database, used to access arbitrary database items.
+**templates**
+  Access to all the tracker templates by name. Used mainly in
+  *use-macro* commands.
+**utils**
+  This variable makes available some utility functions like batching.
+**nothing**
+  This is a special variable - if an expression evaluates to this, then
+  the tag (in the case of a ``tal:replace``), its contents (in the case
+  of ``tal:content``) or some attributes (in the case of
+  ``tal:attributes``) will not appear in the the output. So, for
+  example::
+
+    <span tal:attributes="class nothing">Hello, World!</span>
+
+  would result in::
+
+    <span>Hello, World!</span>
+
+**default**
+  Also a special variable - if an expression evaluates to this, then the
+  existing HTML in the template will not be replaced or removed, it will
+  remain. So::
+
+    <span tal:replace="default">Hello, World!</span>
+
+  would result in::
+
+    <span>Hello, World!</span>
+
+**true**, **false**
+  Boolean constants that may be used in `templating expressions`_
+  instead of ``python:1`` and ``python:0``.
+**i18n**
+  Internationalization service, providing two string translation methods:
+
+  **gettext** (*message*)
+    Return the localized translation of message
+  **ngettext** (*singular*, *plural*, *number*)
+    Like ``gettext()``, but consider plural forms. If a translation
+    is found, apply the plural formula to *number*, and return the
+    resulting message (some languages have more than two plural forms).
+    If no translation is found, return singular if *number* is 1;
+    return plural otherwise.
+
+The context variable
+~~~~~~~~~~~~~~~~~~~~
+
+The *context* variable is one of three things based on the current
+context (see `determining web context`_ for how we figure this out):
+
+1. if we're looking at a "home" page, then it's None
+2. if we're looking at a specific hyperdb class, it's a
+   `hyperdb class wrapper`_.
+3. if we're looking at a specific hyperdb item, it's a
+   `hyperdb item wrapper`_.
+
+If the context is not None, we can access the properties of the class or
+item. The only real difference between cases 2 and 3 above are:
+
+1. the properties may have a real value behind them, and this will
+   appear if the property is displayed through ``context/property`` or
+   ``context/property/field``.
+2. the context's "id" property will be a false value in the second case,
+   but a real, or true value in the third. Thus we can determine whether
+   we're looking at a real item from the hyperdb by testing
+   "context/id".
+
+Hyperdb class wrapper
+:::::::::::::::::::::
+
+This is implemented by the ``roundup.cgi.templating.HTMLClass``
+class.
+
+This wrapper object provides access to a hyperdb class. It is used
+primarily in both index view and new item views, but it's also usable
+anywhere else that you wish to access information about a class, or the
+items of a class, when you don't have a specific item of that class in
+mind.
+
+We allow access to properties. There will be no "id" property. The value
+accessed through the property will be the current value of the same name
+from the CGI form.
+
+There are several methods available on these wrapper objects:
+
+=========== =============================================================
+Method      Description
+=========== =============================================================
+properties  return a `hyperdb property wrapper`_ for all of this class's
+            properties that are searchable by the user. You can use
+            the argument cansearch=False to get all properties.
+list        lists all of the active (not retired) items in the class.
+csv         return the items of this class as a chunk of CSV text.
+propnames   lists the names of the properties of this class.
+filter      lists of items from this class, filtered and sorted. Two
+            options are available for sorting:
+
+            1. by the current *request* filterspec/filter/sort/group args
+            2. by the "filterspec", "sort" and "group" keyword args.
+               "filterspec" is ``{propname: value(s)}``. "sort" and
+               "group" are an optionally empty list ``[(dir, prop)]``
+               where dir is '+', '-' or None
+               and prop is a prop name or None.
+
+               The propname in filterspec and prop in a sort/group spec
+               may be transitive, i.e., it may contain properties of
+               the form link.link.link.name.
+
+            eg. All issues with a priority of "1" with messages added in
+            the last week, sorted by activity date:
+            ``issue.filter(filterspec={"priority": "1",
+            'messages.creation' : '.-1w;'}, sort=[('activity', '+')])``
+
+            Note that when searching for Link and Multilink values, the
+            special value '-1' searches for empty Link or Multilink
+            values. For both, Links and Multilinks, multiple values
+            given in a filter call are combined with 'OR' by default.
+            For Multilinks a postfix expression syntax using negative ID
+            numbers (as strings) as operators is supported. Each
+            non-negative number (or '-1') is pushed on an operand stack.
+            A negative number pops the required number of arguments from
+            the stack, applies the operator, and pushes the result. The
+            following operators are supported:
+
+	    - '-2' stands for 'NOT' and takes one argument
+	    - '-3' stands for 'AND' and takes two arguments
+	    - '-4' stands for 'OR' and takes two arguments
+
+	    Note that this special handling of ID arguments is applied only
+	    when a negative number smaller than -1 is encountered as an ID
+	    in the filter call. Otherwise the implicit OR default
+            applies.
+	    Examples of using Multilink expressions would be
+
+	    - '1', '2', '-4', '3', '4', '-4', '-3'
+	      would search for IDs (1 or 2) and (3 or 4)
+	    - '-1' '-2' would search for all non-empty Multilinks
+
+filter_sql  **Only in SQL backends**
+
+            Lists the items that match the SQL provided. The SQL is a
+            complete "select" statement.
+
+            The SQL select must include the item id as the first column.
+
+            This function **does not** filter out retired items, add
+            on a where clause "__retired__ <> 1" if you don't want
+            retired nodes.
+
+classhelp   display a link to a javascript popup containing this class'
+            "help" template.
+
+            This generates a link to a popup window which displays the
+            properties indicated by "properties" of the class named by
+            "classname". The "properties" should be a comma-separated list
+            (eg. 'id,name,description'). Properties defaults to all the
+            properties of a class (excluding id, creator, created and
+            activity).
+
+            You may optionally override the "label" displayed, the "width",
+            the "height", the number of items per page ("pagesize") and
+            the field on which the list is sorted ("sort").
+
+            With the "filter" arg it is possible to specify a filter for
+            which items are supposed to be displayed. It has to be of
+            the format "<field>=<values>;<field>=<values>;...".
+
+            The popup window will be resizable and scrollable.
+
+            If the "property" arg is given, it's passed through to the
+            javascript help_window function. This allows updating of a
+            property in the calling HTML page.
+
+            If the "form" arg is given, it's passed through to the
+            javascript help_window function - it's the name of the form
+            the "property" belongs to.
+
+submit      generate a submit button (and action and @csrf hidden elements)
+renderWith  render this class with the given template.
+history     returns 'New node - no history' :)
+is_edit_ok  is the user allowed to Edit the current class?
+is_view_ok  is the user allowed to View the current class?
+=========== =============================================================
+
+Note that if you have a property of the same name as one of the above
+methods, you'll need to access it using a python "item access"
+expression. For example::
+
+   python:context['list']
+
+will access the "list" property, rather than the list method.
+
+
+Hyperdb item wrapper
+::::::::::::::::::::
+
+This is implemented by the ``roundup.cgi.templating.HTMLItem``
+class.
+
+This wrapper object provides access to a hyperdb item.
+
+We allow access to properties. There will be no "id" property. The value
+accessed through the property will be the current value of the same name
+from the CGI form.
+
+There are several methods available on these wrapper objects:
+
+=============== ========================================================
+Method          Description
+=============== ========================================================
+submit          generate a submit button (and action and @csrf hidden elements)
+journal         return the journal of the current item (**not
+                implemented**)
+history         render the journal of the current item as
+                HTML. By default properties marked as "quiet" (see 
+		`design documentation`_) are not shown unless the
+		function is called with the showall=True parameter.
+		Properties that are not Viewable to the user are not
+		shown.
+renderQueryForm specific to the "query" class - render the search form
+                for the query
+hasPermission   specific to the "user" class - determine whether the
+                user has a Permission. The signature is::
+
+                    hasPermission(self, permission, [classname=],
+                        [property=], [itemid=])
+
+                where the classname defaults to the current context.
+hasRole         specific to the "user" class - determine whether the
+                user has a Role. The signature is::
+
+                    hasRole(self, rolename)
+
+is_edit_ok      is the user allowed to Edit the current item?
+is_view_ok      is the user allowed to View the current item?
+is_retired      is the item retired?
+download_url    generate a url-quoted link for download of FileClass
+                item contents (ie. file<id>/<name>)
+copy_url        generate a url-quoted link for creating a copy
+                of this item.  By default, the copy will acquire
+                all properties of the current item except for
+                ``messages`` and ``files``.  This can be overridden
+                by passing ``exclude`` argument which contains a list
+                (or any iterable) of property names that shall not be
+                copied.  Database-driven properties like ``id`` or
+                ``activity`` cannot be copied.
+=============== ========================================================
+
+Note that if you have a property of the same name as one of the above
+methods, you'll need to access it using a python "item access"
+expression. For example::
+
+   python:context['journal']
+
+will access the "journal" property, rather than the journal method.
+
+
+Hyperdb property wrapper
+::::::::::::::::::::::::
+
+This is implemented by subclasses of the
+``roundup.cgi.templating.HTMLProperty`` class (``HTMLStringProperty``,
+``HTMLNumberProperty``, and so on).
+
+This wrapper object provides access to a single property of a class. Its
+value may be either:
+
+1. if accessed through a `hyperdb item wrapper`_, then it's a value from
+   the hyperdb
+2. if access through a `hyperdb class wrapper`_, then it's a value from
+   the CGI form
+
+
+The property wrapper has some useful attributes:
+
+=============== ========================================================
+Attribute       Description
+=============== ========================================================
+_name           the name of the property
+_value          the value of the property if any - this is the actual
+                value retrieved from the hyperdb for this property
+=============== ========================================================
+
+There are several methods available on these wrapper objects:
+
+=========== ================================================================
+Method      Description
+=========== ================================================================
+plain       render a "plain" representation of the property. This method
+            may take two arguments:
+
+            escape
+             If true, escape the text so it is HTML safe (default: no). The
+             reason this defaults to off is that text is usually escaped
+             at a later stage by the TAL commands, unless the "structure"
+             option is used in the template. The following ``tal:content``
+             expressions are all equivalent::
+ 
+              "structure python:msg.content.plain(escape=1)"
+              "python:msg.content.plain()"
+              "msg/content/plain"
+              "msg/content"
+
+             Usually you'll only want to use the escape option in a
+             complex expression.
+
+            hyperlink
+             If true, turn URLs, email addresses and hyperdb item
+             designators in the text into hyperlinks (default: no). Note
+             that you'll need to use the "structure" TAL option if you
+             want to use this ``tal:content`` expression::
+  
+              "structure python:msg.content.plain(hyperlink=1)"
+
+             The text is automatically HTML-escaped before the hyperlinking
+             transformation done in the plain() method.
+
+hyperlinked The same as msg.content.plain(hyperlink=1), but nicer::
+
+              "structure msg/content/hyperlinked"
+
+field       render an appropriate form edit field for the property - for
+            most types this is a text entry box, but for Booleans it's a
+            tri-state yes/no/neither selection. This method may take some
+            arguments:
+
+            size
+              Sets the width in characters of the edit field
+
+            format (Date properties only)
+              Sets the format of the date in the field - uses the same
+              format string argument as supplied to the ``pretty`` method
+              below.
+
+            popcal (Date properties only)
+              Include the Javascript-based popup calendar for date
+              selection. Defaults to on.
+
+stext       only on String properties - render the value of the property
+            as StructuredText (requires the StructureText module to be
+            installed separately)
+multiline   only on String properties - render a multiline form edit
+            field for the property
+email       only on String properties - render the value of the property
+            as an obscured email address
+url_quote   only on String properties. It quotes any characters in the
+            string so it is safe to use in a url. E.G. a space is
+            replaced with %20.
+confirm     only on Password properties - render a second form edit field
+            for the property, used for confirmation that the user typed
+            the password correctly. Generates a field with name
+            "name:confirm".
+now         only on Date properties - return the current date as a new
+            property
+reldate     only on Date properties - render the interval between the date
+            and now
+local       only on Date properties - return this date as a new property
+            with some timezone offset, for example::
+            
+                python:context.creation.local(10)
+
+            will render the date with a +10 hour offset.
+pretty      Date properties - render the date as "dd Mon YYYY" (eg. "19
+            Mar 2004"). Takes an optional format argument, for example::
+
+                python:context.activity.pretty('%Y-%m-%d')
+
+            Will format as "2004-03-19" instead.
+
+            Interval properties - render the interval in a pretty
+            format (eg. "yesterday"). The format arguments are those used
+            in the standard ``strftime`` call (see the `Python Library
+            Reference: time module`__)
+
+            Number properties - takes a printf style format argument
+            (default: '%0.3f') and formats the number accordingly.
+            If the value can't be converted, '' is returned if the
+            value is ``None`` otherwise it is converted to a string.
+popcal      Generate a link to a popup calendar which may be used to
+            edit the date field, for example::
+
+              <span tal:replace="structure context/due/popcal" />
+
+            you still need to include the ``field`` for the property, so
+            typically you'd have::
+
+              <span tal:replace="structure context/due/field" />
+              <span tal:replace="structure context/due/popcal" />
+
+menu        only on Link and Multilink properties - render a form select
+            list for this property. Takes a number of optional arguments
+
+            size
+               is used to limit the length of the list labels
+            height
+               is used to set the <select> tag's "size" attribute
+            showid
+               includes the item ids in the list labels
+            additional
+               lists properties which should be included in the label
+            sort_on
+                indicates the property to sort the list on as (direction,
+                (direction, property) where direction is '+' or '-'. A
+                single string with the direction prepended may be used.
+                For example: ('-', 'order'), '+name'.
+            value
+                gives a default value to preselect in the menu
+
+            The remaining keyword arguments are used as conditions for
+            filtering the items in the list - they're passed as the
+            "filterspec" argument to a Class.filter() call. For example::
+
+             <span tal:replace="structure context/status/menu" />
+
+             <span tal:replace="python:context.status.menu(order='+name",
+                                   value='chatting', 
+                                   filterspec={'status': '1,2,3,4'}" />
+
+sorted      only on Multilink properties - produce a list of the linked
+            items sorted by some property, for example::
+            
+                python:context.files.sorted('creation')
+
+            Will list the files by upload date. While::
+
+               python:context.files.sorted('creation', reverse=True)
+
+            Will list the files by upload date in reverse order from
+            the prior example. If the property can be unset, you can
+            use the ``NoneFirst`` parameter to sort the None/Unset
+            values at the front or the end of the list. For example::
+            
+                python:context.files.sorted('creation', NoneFirst=True)
+            
+            will sort files by creation date with files missing a
+            creation date at the start of the list. The default for
+            ``NoneFirst`` is False so these files will sort at the end
+            by default. (Note creation date is never unset, but you
+            get the idea.) If you combine NoneFirst with
+            ``reverse=True`` the meaning of NoneFirst is inverted:
+	    True sorts None/unset at the end and False sorts at the
+	    beginning.
+reverse     only on Multilink properties - produce a list of the linked
+            items in reverse order
+isset       returns True if the property has been set to a value
+=========== ================================================================
+
+__ https://docs.python.org/2/library/time.html
+
+All of the above functions perform checks for permissions required to
+display or edit the data they are manipulating. The simplest case is
+editing an issue title. Including the expression::
+
+   context/title/field
+
+Will present the user with an edit field, if they have edit permission. If
+not, then they will be presented with a static display if they have view
+permission. If they don't even have view permission, then an error message
+is raised, preventing the display of the page, indicating that they don't
+have permission to view the information.
+
+
+The request variable
+~~~~~~~~~~~~~~~~~~~~
+
+This is implemented by the ``roundup.cgi.templating.HTMLRequest``
+class.
+
+The request variable is packed with information about the current
+request.
+
+.. taken from ``roundup.cgi.templating.HTMLRequest`` docstring
+
+=========== ============================================================
+Variable    Holds
+=========== ============================================================
+form        the CGI form as a cgi.FieldStorage
+env         the CGI environment variables
+base        the base URL for this tracker
+user        a HTMLUser instance for this user
+classname   the current classname (possibly None)
+template    the current template (suffix, also possibly None)
+form        the current CGI form variables in a FieldStorage
+=========== ============================================================
+
+**Index page specific variables (indexing arguments)**
+
+=========== ============================================================
+Variable    Holds
+=========== ============================================================
+columns     dictionary of the columns to display in an index page
+show        a convenience access to columns - request/show/colname will
+            be true if the columns should be displayed, false otherwise
+sort        index sort columns [(direction, column name)]
+group       index grouping properties [(direction, column name)]
+filter      properties to filter the index on
+filterspec  values to filter the index on (property=value, eg
+            ``priority=1`` or ``messages.author=42``
+search_text text to perform a full-text search on for an index
+=========== ============================================================
+
+There are several methods available on the request variable:
+
+=============== ========================================================
+Method          Description
+=============== ========================================================
+description     render a description of the request - handle for the
+                page title
+indexargs_form  render the current index args as form elements
+indexargs_url   render the current index args as a URL
+base_javascript render some javascript that is used by other components
+                of the templating
+batch           run the current index args through a filter and return a
+                list of items (see `hyperdb item wrapper`_, and
+                `batching`_)
+=============== ========================================================
+
+The form variable
+:::::::::::::::::
+
+The form variable is a bit special because it's actually a python
+FieldStorage object. That means that you have two ways to access its
+contents. For example, to look up the CGI form value for the variable
+"name", use the path expression::
+
+   request/form/name/value
+
+or the python expression::
+
+   python:request.form['name'].value
+
+Note the "item" access used in the python case, and also note the
+explicit "value" attribute we have to access. That's because the form
+variables are stored as MiniFieldStorages. If there's more than one
+"name" value in the form, then the above will break since
+``request/form/name`` is actually a *list* of MiniFieldStorages. So it's
+best to know beforehand what you're dealing with.
+
+
+The db variable
+~~~~~~~~~~~~~~~
+
+This is implemented by the ``roundup.cgi.templating.HTMLDatabase``
+class.
+
+Allows access to all hyperdb classes as attributes of this variable. If
+you want access to the "user" class, for example, you would use::
+
+  db/user
+  python:db.user
+
+Also, the current id of the current user is available as
+``db.getuid()``. This isn't so useful in templates (where you have
+``request/user``), but it can be useful in detectors or interfaces.
+
+The access results in a `hyperdb class wrapper`_.
+
+
+The templates variable
+~~~~~~~~~~~~~~~~~~~~~~
+
+This was implemented by the ``roundup.cgi.templating.Templates``
+class before 1.4.20. In later versions it is the instance of appropriate
+template engine loader class.
+
+This variable is used to access other templates in expressions and
+template macros. It doesn't have any useful methods defined. The
+templates can be accessed using the following path expression::
+
+   templates/name
+
+or the python expression::
+
+   templates[name]
+
+where "name" is the name of the template you wish to access. The
+template has one useful attribute, namely "macros". To access a specific
+macro (called "macro_name"), use the path expression::
+
+   templates/name/macros/macro_name
+
+or the python expression::
+
+   templates[name].macros[macro_name]
+
+The repeat variable
+~~~~~~~~~~~~~~~~~~~
+
+The repeat variable holds an entry for each active iteration. That is, if
+you have a ``tal:repeat="user db/users"`` command, then there will be a
+repeat variable entry called "user". This may be accessed as either::
+
+    repeat/user
+    python:repeat['user']
+
+The "user" entry has a number of methods available for information:
+
+=============== =========================================================
+Method          Description
+=============== =========================================================
+first           True if the current item is the first in the sequence.
+last            True if the current item is the last in the sequence.
+even            True if the current item is an even item in the sequence.
+odd             True if the current item is an odd item in the sequence.
+number          Current position in the sequence, starting from 1.
+letter          Current position in the sequence as a letter, a through
+                z, then aa through zz, and so on.
+Letter          Same as letter(), except uppercase.
+roman           Current position in the sequence as lowercase roman
+                numerals.
+Roman           Same as roman(), except uppercase.
+=============== =========================================================
+
+.. _templating utilities:
+
+The utils variable
+~~~~~~~~~~~~~~~~~~
+
+This is implemented by the
+``roundup.cgi.templating.TemplatingUtils`` class, which may be extended
+with additional methods by extensions_.
+
+=============== ========================================================
+Method          Description
+=============== ========================================================
+Batch           return a batch object using the supplied list
+url_quote       quote some text as safe for a URL (ie. space, %, ...)
+html_quote      quote some text as safe in HTML (ie. <, >, ...)
+html_calendar   renders an HTML calendar used by the
+                ``_generic.calendar.html`` template (itself invoked by
+                the popupCalendar DateHTMLProperty method
+anti_csrf_nonce returns the random noncue generated for this session
+=============== ========================================================
+
+
+Batching
+::::::::
+
+Use Batch to turn a list of items, or item ids of a given class, into a
+series of batches. Its usage is::
+
+    python:utils.Batch(sequence, size, start, end=0, orphan=0,
+    overlap=0)
+
+or, to get the current index batch::
+
+    request/batch
+
+The parameters are:
+
+========= ==============================================================
+Parameter  Usage
+========= ==============================================================
+sequence  a list of HTMLItems
+size      how big to make the sequence.
+start     where to start (0-indexed) in the sequence.
+end       where to end (0-indexed) in the sequence.
+orphan    if the next batch would contain less items than this value,
+          then it is combined with this batch
+overlap   the number of items shared between adjacent batches
+========= ==============================================================
+
+All of the parameters are assigned as attributes on the batch object. In
+addition, it has several more attributes:
+
+=============== ========================================================
+Attribute       Description
+=============== ========================================================
+start           indicates the start index of the batch. *Unlike
+                the argument, is a 1-based index (I know, lame)*
+first           indicates the start index of the batch *as a 0-based
+                index*
+length          the actual number of elements in the batch
+sequence_length the length of the original, unbatched, sequence.
+=============== ========================================================
+
+And several methods:
+
+=============== ========================================================
+Method          Description
+=============== ========================================================
+previous        returns a new Batch with the previous batch settings
+next            returns a new Batch with the next batch settings
+propchanged     detect if the named property changed on the current item
+                when compared to the last item
+=============== ========================================================
+
+An example of batching::
+
+ <table class="otherinfo">
+  <tr><th colspan="4" class="header">Existing Keywords</th></tr>
+  <tr tal:define="keywords db/keyword/list"
+      tal:repeat="start python:range(0, len(keywords), 4)">
+   <td tal:define="batch python:utils.Batch(keywords, 4, start)"
+       tal:repeat="keyword batch" tal:content="keyword/name">
+       keyword here</td>
+  </tr>
+ </table>
+
+... which will produce a table with four columns containing the items of
+the "keyword" class (well, their "name" anyway).
+
+
+Translations
+~~~~~~~~~~~~
+
+Should you wish to enable multiple languages in template content that you
+create you'll need to add new locale files in the tracker home under a
+``locale`` directory. Use the `translation instructions in the
+developer's guide <developers.html#extracting-translatable-messages>`_ to
+create the locale files.
+
+
+Displaying Properties
+---------------------
+
+Properties appear in the user interface in three contexts: in indices,
+in editors, and as search arguments. For each type of property, there
+are several display possibilities. For example, in an index view, a
+string property may just be printed as a plain string, but in an editor
+view, that property may be displayed in an editable field.
+
+
+Index Views
+-----------
+
+This is one of the class context views. It is also the default view for
+classes. The template used is "*classname*.index".
+
+
+Index View Specifiers
+~~~~~~~~~~~~~~~~~~~~~
+
+An index view specifier (URL fragment) looks like this (whitespace has
+been added for clarity)::
+
+    /issue?status=unread,in-progress,resolved&
+        keyword=security,ui&
+        @group=priority,-status&
+        @sort=-activity&
+        @filters=status,keyword&
+        @columns=title,status,fixer
+
+The index view is determined by two parts of the specifier: the layout
+part and the filter part. The layout part consists of the query
+parameters that begin with colons, and it determines the way that the
+properties of selected items are displayed. The filter part consists of
+all the other query parameters, and it determines the criteria by which
+items are selected for display. The filter part is interactively
+manipulated with the form widgets displayed in the filter section. The
+layout part is interactively manipulated by clicking on the column
+headings in the table.
+
+The filter part selects the union of the sets of items with values
+matching any specified Link properties and the intersection of the sets
+of items with values matching any specified Multilink properties.
+
+The example specifies an index of "issue" items. Only items with a
+"status" of either "unread" or "in-progress" or "resolved" are
+displayed, and only items with "keyword" values including both "security"
+and "ui" are displayed. The items are grouped by priority arranged in
+ascending order and in descending order by status; and within
+groups, sorted by activity, arranged in descending order. The filter
+section shows filters for the "status" and "keyword" properties, and the
+table includes columns for the "title", "status", and "fixer"
+properties.
+
+============ =============================================================
+Argument     Description
+============ =============================================================
+@sort        sort by prop name, optionally preceeded with '-' to give
+             descending or nothing for ascending sorting. Several
+             properties can be specified delimited with comma.
+             Internally a search-page using several sort properties may
+             use @sort0, @sort1 etc. with option @sortdir0, @sortdir1
+             etc. for the direction of sorting (a non-empty value of
+             sortdir0 specifies reverse order).
+@group       group by prop name, optionally preceeded with '-' or to sort
+             in descending or nothing for ascending order. Several
+             properties can be specified delimited with comma.
+             Internally a search-page using several grouping properties may
+             use @group0, @group1 etc. with option @groupdir0, @groupdir1
+             etc. for the direction of grouping (a non-empty value of
+             groupdir0 specifies reverse order).
+@columns     selects the columns that should be displayed. Default is
+             all.                     
+@filter      indicates which properties are being used in filtering.
+             Default is none.
+propname     selects the values the item properties given by propname must
+             have (very basic search/filter).
+@search_text if supplied, performs a full-text search (message bodies,
+             issue titles, etc)
+============ =============================================================
+
+
+Searching Views
+---------------
+
+.. note::
+   if you add a new column to the ``@columns`` form variable potentials
+   then you will need to add the column to the appropriate `index views`_
+   template so that it is actually displayed.
+
+This is one of the class context views. The template used is typically
+"*classname*.search". The form on this page should have "search" as its
+``@action`` variable. The "search" action:
+
+- sets up additional filtering, as well as performing indexed text
+  searching
+- sets the ``@filter`` variable correctly
+- saves the query off if ``@query_name`` is set.
+
+The search page should lay out any fields that you wish to allow the
+user to search on. If your schema contains a large number of properties,
+you should be wary of making all of those properties available for
+searching, as this can cause confusion. If the additional properties are
+Strings, consider having their value indexed, and then they will be
+searchable using the full text indexed search. This is both faster, and
+more useful for the end user.
+
+If the search view does specify the "search" ``@action``, then it may also
+provide an additional argument:
+
+============ =============================================================
+Argument     Description
+============ =============================================================
+@query_name  if supplied, the index parameters (including @search_text)
+             will be saved off as a the query item and registered against
+             the user's queries property. Note that the *classic* template
+             schema has this ability, but the *minimal* template schema
+             does not.
+============ =============================================================
+
+
+Item Views
+----------
+
+The basic view of a hyperdb item is provided by the "*classname*.item"
+template. It generally has three sections; an "editor", a "spool" and a
+"history" section.
+
+
+Editor Section
+~~~~~~~~~~~~~~
+
+The editor section is used to manipulate the item - it may be a static
+display if the user doesn't have permission to edit the item.
+
+Here's an example of a basic editor template (this is the default
+"classic" template issue item edit form - from the "issue.item.html"
+template)::
+
+ <table class="form">
+ <tr>
+  <th>Title</th>
+  <td colspan="3" tal:content="structure python:context.title.field(size=60)">title</td>
+ </tr>
+ 
+ <tr>
+  <th>Priority</th>
+  <td tal:content="structure context/priority/menu">priority</td>
+  <th>Status</th>
+  <td tal:content="structure context/status/menu">status</td>
+ </tr>
+ 
+ <tr>
+  <th>Superseder</th>
+  <td>
+   <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" />
+   <span tal:replace="structure python:db.issue.classhelp('id,title')" />
+   <span tal:condition="context/superseder">
+    <br>View: <span tal:replace="structure python:context.superseder.link(showid=1)" />
+   </span>
+  </td>
+  <th>Nosy List</th>
+  <td>
+   <span tal:replace="structure context/nosy/field" />
+   <span tal:replace="structure python:db.user.classhelp('username,realname,address,phone')" />
+  </td>
+ </tr>
+ 
+ <tr>
+  <th>Assigned To</th>
+  <td tal:content="structure context/assignedto/menu">
+   assignedto menu
+  </td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+ </tr>
+ 
+ <tr>
+  <th>Change Note</th>
+  <td colspan="3">
+   <textarea name=":note" wrap="hard" rows="5" cols="60"></textarea>
+  </td>
+ </tr>
+ 
+ <tr>
+  <th>File</th>
+  <td colspan="3"><input type="file" name=":file" size="40"></td>
+ </tr>
+ 
+ <tr>
+  <td>&nbsp;</td>
+  <td colspan="3" tal:content="structure context/submit">
+   submit button will go here
+  </td>
+ </tr>
+ </table>
+
+
+When a change is submitted, the system automatically generates a message
+describing the changed properties. As shown in the example, the editor
+template can use the ":note" and ":file" fields, which are added to the
+standard changenote message generated by Roundup.
+
+
+Form values
+:::::::::::
+
+We have a number of ways to pull properties out of the form in order to
+meet the various needs of:
+
+1. editing the current item (perhaps an issue item)
+2. editing information related to the current item (eg. messages or
+   attached files)
+3. creating new information to be linked to the current item (eg. time
+   spent on an issue)
+
+In the following, ``<bracketed>`` values are variable, ":" may be one of
+":" or "@", and other text ("required") is fixed.
+
+Properties are specified as form variables:
+
+``<propname>``
+  property on the current context item
+
+``<designator>:<propname>``
+  property on the indicated item (for editing related information)
+
+``<classname>-<N>:<propname>``
+  property on the Nth new item of classname (generally for creating new
+  items to attach to the current item)
+
+Once we have determined the "propname", we check to see if it is one of
+the special form values:
+
+``@required``
+  The named property values must be supplied or a ValueError will be
+  raised.
+
+``@remove@<propname>=id(s)``
+  The ids will be removed from the multilink property.
+
+``:add:<propname>=id(s)``
+  The ids will be added to the multilink property.
+
+``:link:<propname>=<designator>``
+  Used to add a link to new items created during edit. These are
+  collected and returned in ``all_links``. This will result in an
+  additional linking operation (either Link set or Multilink append)
+  after the edit/create is done using ``all_props`` in ``_editnodes``.
+  The <propname> on the current item will be set/appended the id of the
+  newly created item of class <designator> (where <designator> must be
+  <classname>-<N>).
+
+Any of the form variables may be prefixed with a classname or
+designator.
+
+Two special form values are supported for backwards compatibility:
+
+``:note``
+  create a message (with content, author and date), linked to the
+  context item. This is ALWAYS designated "msg-1".
+``:file``
+  create a file, attached to the current item and any message created by
+  :note. This is ALWAYS designated "file-1".
+
+
+Spool Section
+~~~~~~~~~~~~~
+
+The spool section lists related information like the messages and files
+of an issue.
+
+TODO
+
+
+History Section
+~~~~~~~~~~~~~~~
+
+The final section displayed is the history of the item - its database
+journal. This is generally generated with the template::
+
+ <tal:block tal:replace="structure context/history" />
+
+or::
+
+   <tal:block
+       tal:replace="structure python:context.history(showall=True)" />
+
+if you want to show history entries for quiet properties.
+
+*To be done:*
+
+*The actual history entries of the item may be accessed for manual
+templating through the "journal" method of the item*::
+
+ <tal:block tal:repeat="entry context/journal">
+  a journal entry
+ </tal:block>
+
+*where each journal entry is an HTMLJournalEntry.*
+
+
+Defining new web actions
+------------------------
+
+You may define new actions to be triggered by the ``@action`` form variable.
+These are added to the tracker ``extensions`` directory and registered
+using ``instance.registerAction``.
+
+All the existing Actions are defined in ``roundup.cgi.actions``.
+
+Adding action classes takes three steps; first you `define the new
+action class`_, then you `register the action class`_ with the cgi
+interface so it may be triggered by the ``@action`` form variable.
+Finally you `use the new action`_ in your HTML form.
+
+See `setting up a "wizard" (or "druid") for controlled adding of
+issues
+<customizing.html#setting-up-a-wizard-or-druid-for-controlled-adding-of-issues>`_ for an example.
+
+
+Define the new action class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create a new action class in your tracker's ``extensions`` directory, for
+example ``myaction.py``::
+
+ from roundup.cgi.actions import Action
+
+ class MyAction(Action):
+     def handle(self):
+         ''' Perform some action. No return value is required.
+         '''
+
+The *self.client* attribute is an instance of ``roundup.cgi.client.Client``.
+See the docstring of that class for details of what it can do.
+
+The method will typically check the ``self.form`` variable's contents.
+It may then:
+
+- add information to ``self.client._ok_message``
+  or ``self.client._error_message`` (by using ``self.client.add_ok_message``
+  or ``self.client.add_error_message``, respectively)
+- change the ``self.client.template`` variable to alter what the user will see
+  next
+- raise Unauthorised, SendStaticFile, SendFile, NotFound or Redirect
+  exceptions (import them from roundup.cgi.exceptions)
+
+
+Register the action class
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The class is now written, but isn't available to the user until you register
+it with the following code appended to your ``myaction.py`` file::
+
+    def init(instance):
+        instance.registerAction('myaction', myActionClass)
+
+This maps the action name "myaction" to the action class we defined.
+
+
+Use the new action
+~~~~~~~~~~~~~~~~~~
+
+In your HTML form, add a hidden form element like so::
+
+  <input type="hidden" name="@action" value="myaction">
+
+where "myaction" is the name you registered in the previous step.
+
+Actions may return content to the user
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Actions generally perform some database manipulation and then pass control
+on to the rendering of a template in the current context (see `Determining
+web context`_ for how that works.) Some actions will want to generate the
+actual content returned to the user. Action methods may return their own
+content string to be displayed to the user, overriding the templating step.
+In this situation, we assume that the content is HTML by default. You may
+override the content type indicated to the user by calling ``setHeader``::
+
+   self.client.setHeader('Content-Type', 'text/csv')
+
+This example indicates that the value sent back to the user is actually
+comma-separated value content (eg. something to be loaded into a
+spreadsheet or database).
+
+
+8-bit character set support in Web interface
+--------------------------------------------
+
+The web interface uses UTF-8 default. It may be overridden in both forms
+and a browser cookie.
+
+- In forms, use the ``@charset`` variable.
+- To use the cookie override, have the ``roundup_charset`` cookie set.
+
+In both cases, the value is a valid charset name (eg. ``utf-8`` or
+``kio8-r``).
+
+Inside Roundup, all strings are stored and processed in utf-8.
+Unfortunately, some older browsers do not work properly with
+utf-8-encoded pages (e.g. Netscape Navigator 4 displays wrong
+characters in form fields).  This version allows one to change
+the character set for http transfers.  To do so, you may add
+the following code to your ``page.html`` template::
+
+ <tal:block define="uri string:${request/base}${request/env/PATH_INFO}">
+  <a tal:attributes="href python:request.indexargs_url(uri,
+   {'@charset':'utf-8'})">utf-8</a>
+  <a tal:attributes="href python:request.indexargs_url(uri,
+   {'@charset':'koi8-r'})">koi8-r</a>
+ </tal:block>
+
+(substitute ``koi8-r`` with appropriate charset for your language).
+Charset preference is kept in the browser cookie ``roundup_charset``.
+
+``meta http-equiv`` lines added to the tracker templates in version 0.6.0
+should be changed to include actual character set name::
+
+ <meta http-equiv="Content-Type"
+  tal:attributes="content string:text/html;; charset=${request/client/charset}"
+ />
+
+The charset is also sent in the http header.
+
+
+
+
+
+Debugging Trackers
+==================
+
+There are three switches in tracker configs that turn on debugging in
+Roundup:
+
+1. web :: debug
+2. mail :: debug
+3. logging :: level
+
+See the config.ini file or the `tracker configuration`_ section for
+more information.
+
+Additionally, the ``roundup-server.py`` script has its own debugging mode
+in which it reloads edited templates immediately when they are changed,
+rather than requiring a web server restart.
+
+
+.. _`design documentation`: design.html
+.. _change the rate limiting method: rest.html#creating-custom-rate-limits
+.. _`directions in the rest interface documentation`: rest.html#enabling-the-rest-api
+.. _`xmlrpc interface documentation`: xmlrpc.html#through-roundup
+
+.. allow line breaks in term definitions.
+.. |br| raw:: html
+
+   <br/>
+

Roundup Issue Tracker: http://roundup-tracker.org/