"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "share/doc/roundup/html/_sources/customizing.txt" between
roundup-1.6.1.tar.gz and roundup-2.0.0.tar.gz

About: Roundup is an highly customisable issue-tracking system with command-line, web and e-mail interfaces (written in Python).

customizing.txt  (roundup-1.6.1):customizing.txt  (roundup-2.0.0)
skipping to change at line 25 skipping to change at line 25
=============== ===============
Before you get too far, it's probably worth having a quick read of the Roundup Before you get too far, it's probably worth having a quick read of the Roundup
`design documentation`_. `design documentation`_.
Customisation of Roundup can take one of six forms: Customisation of Roundup can take one of six forms:
1. `tracker configuration`_ changes 1. `tracker configuration`_ changes
2. database, or `tracker schema`_ changes 2. database, or `tracker schema`_ changes
3. "definition" class `database content`_ changes 3. "definition" class `database content`_ changes
4. behavioural changes, through detectors_ and extensions_ 4. behavioural changes through detectors_, extensions_ and interfaces.py_
5. `security / access controls`_ 5. `security / access controls`_
6. change the `web interface`_ 6. change the `web interface`_
The third case is special because it takes two distinctly different forms The third case is special because it takes two distinctly different forms
depending upon whether the tracker has been initialised or not. The other two depending upon whether the tracker has been initialised or not. The other two
may be done at any time, before or after tracker initialisation. Yes, this may be done at any time, before or after tracker initialisation. Yes, this
includes adding or removing properties from classes. includes adding or removing properties from classes.
Trackers in a Nutshell Trackers in a Nutshell
====================== ======================
Trackers have the following structure: 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 Tracker File Description
=================== ======================================================== =================== ========================================================
config.ini Holds the basic `tracker configuration`_ config.ini Holds the basic `tracker configuration`_
schema.py Holds the `tracker schema`_ schema.py Holds the `tracker schema`_
initial_data.py Holds any data to be entered into the database when the initial_data.py Holds any data to be entered into the database when the
tracker is initialised. tracker is initialised.
db/ Holds the tracker's database db/ Holds the tracker's database
db/files/ Holds the tracker's upload files and messages db/files/ Holds the tracker's upload files and messages
db/backend_name Names the database back-end for the tracker db/backend_name Names the database back-end for the tracker (obsolete).
Current way uses the ``backend`` setting in the rdbms
section of config.ini.
detectors/ Auditors and reactors for this tracker detectors/ Auditors and reactors for this tracker
extensions/ Additional actions and `templating utilities`_ extensions/ Additional actions and `templating utilities`_
html/ Web interface templates, images and style sheets html/ Web interface templates, images and style sheets
lib/ optional common imports for detectors and extensions lib/ optional common imports for detectors and extensions
=================== ======================================================== =================== ========================================================
.. index:: config.ini
.. index:: configuration; see config.ini
Tracker Configuration Tracker Configuration
===================== =====================
The ``config.ini`` located in your tracker home contains the basic The ``config.ini`` located in your tracker home contains the basic
configuration for the web and e-mail components of roundup's interfaces. 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 Changes to the data captured by your tracker is controlled by the `tracker
schema`_. Some configuration is also performed using permissions - see the schema`_. Some configuration is also performed using permissions - see the
`security / access controls`_ section. For example, to allow users to `security / access controls`_ section. For example, to allow users to
automatically register through the email interface, you must grant the automatically register through the email interface, you must grant the
"Anonymous" Role the "Email Access" Permission. "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) The following is taken from the `Python Library Reference`__ (July 18, 2018)
section "ConfigParser -- Configuration file parser": section "ConfigParser -- Configuration file parser":
The configuration file consists of sections, led by a [section] header The configuration file consists of sections, led by a [section] header
and followed by name: value entries, with continuations in the style 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 of RFC 822 (see section 3.1.1, “LONG HEADER FIELDS”); name=value is
also accepted. Note that leading whitespace is removed from also accepted. Note that leading whitespace is removed from
values. The optional values can contain format strings which refer to values. The optional values can contain format strings which refer to
other values in the same section, or values in a special DEFAULT other values in the same section, or values in a special DEFAULT
section. Additional defaults can be provided on initialization and section. Additional defaults can be provided on initialization and
skipping to change at line 91 skipping to change at line 108
[My Section] [My Section]
foodir = %(dir)s/whatever foodir = %(dir)s/whatever
dir = frob dir = frob
would resolve the "%(dir)s" to the value of "dir" ("frob" in this case) would resolve the "%(dir)s" to the value of "dir" ("frob" in this case)
resulting in "foodir" being "frob/whatever". resulting in "foodir" being "frob/whatever".
__ https://docs.python.org/2/library/configparser.html __ https://docs.python.org/2/library/configparser.html
Example configuration settings are below. Example configuration settings are below. This is a partial
list. Documentation on all the settings is included in the
``config.ini`` file.
.. index:: config.ini; sections main
Section **main** Section **main**
database -- ``db`` database -- ``db``
Database directory path. The path may be either absolute or relative Database directory path. The path may be either absolute or relative
to the directory containig this config file. to the directory containig this config file.
templates -- ``html`` templates -- ``html``
Path to the HTML templates directory. The path may be either absolute Path to the HTML templates directory. The path may be either absolute
or relative to the directory containing this config file. or relative to the directory containing this config file.
skipping to change at line 177 skipping to change at line 198
Defines the file creation mode mask. Defines the file creation mode mask.
csv_field_size -- ``131072`` csv_field_size -- ``131072``
Maximum size of a csv-field during import. Roundups export Maximum size of a csv-field during import. Roundups export
format is a csv (comma separated values) variant. The csv format is a csv (comma separated values) variant. The csv
reader has a limit on the size of individual fields reader has a limit on the size of individual fields
starting with python 2.5. Set this to a higher value if you starting with python 2.5. Set this to a higher value if you
get the error 'Error: field larger than field limit' during get the error 'Error: field larger than field limit' during
import. import.
.. index:: config.ini; sections tracker
Section **tracker** Section **tracker**
name -- ``Roundup issue tracker`` name -- ``Roundup issue tracker``
A descriptive name for your roundup instance. A descriptive name for your roundup instance.
web -- ``http://host.example/demo/`` web -- ``http://host.example/demo/``
The web address that the tracker is viewable at. The web address that the tracker is viewable at.
This will be included in information sent to users of the tracker. This will be included in information sent to users of the tracker.
The URL MUST include the cgi-bin part or anything else The URL MUST include the cgi-bin part or anything else
that is required to get to the home page of the tracker. that is required to get to the home page of the tracker.
You MUST include a trailing '/' in the URL. You MUST include a trailing '/' in the URL.
email -- ``issue_tracker`` email -- ``issue_tracker``
Email address that mail to roundup should go to. Email address that mail to roundup should go to.
language -- default *blank* language -- default *blank*
Default locale name for this tracker. If this option is not set, the Default locale name for this tracker. If this option is not set, the
language is determined by the environment variable LANGUAGE, LC_ALL, language is determined by the environment variable LANGUAGE, LC_ALL,
LC_MESSAGES, or LANG, in that order of preference. LC_MESSAGES, or LANG, in that order of preference.
.. index:: config.ini; sections web
Section **web** Section **web**
allow_html_file -- ``no`` allow_html_file -- ``no``
Setting this option enables Roundup to serve uploaded HTML Setting this option enables Roundup to serve uploaded HTML
file content *as HTML*. This is a potential security risk file content *as HTML*. This is a potential security risk
and is therefore disabled by default. Set to 'yes' if you and is therefore disabled by default. Set to 'yes' if you
trust *all* users uploading content to your tracker. trust *all* users uploading content to your tracker.
http_auth -- ``yes`` http_auth -- ``yes``
Whether to use HTTP Basic Authentication, if present. Whether to use HTTP Basic Authentication, if present.
Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION
skipping to change at line 222 skipping to change at line 247
Browsers send a language-region preference list. Browsers send a language-region preference list.
It's usually set in the client's browser or in their It's usually set in the client's browser or in their
Operating System. Operating System.
Set this option to 'no' if you want to ignore it. Set this option to 'no' if you want to ignore it.
debug -- ``no`` debug -- ``no``
Setting this option makes Roundup display error tracebacks Setting this option makes Roundup display error tracebacks
in the user's browser rather than emailing them to the in the user's browser rather than emailing them to the
tracker admin."), tracker admin."),
.. index:: config.ini; sections rdbms
single: config.ini; database settings
Section **rdbms** Section **rdbms**
Settings in this section are used by Postgresql and MySQL backends only 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 -- ``roundup``
Name of the database to use. Name of the database to use.
host -- ``localhost`` host -- ``localhost``
Database server host. Database server host.
port -- default *blank* port -- default *blank*
TCP port number of the database server. Postgresql usually resides on TCP port number of the database server. Postgresql usually resides on
port 5432 (if any), for MySQL default port number is 3306. Leave this port 5432 (if any), for MySQL default port number is 3306. Leave this
skipping to change at line 249 skipping to change at line 292
password -- ``roundup`` password -- ``roundup``
Database user password. Database user password.
read_default_file -- ``~/.my.cnf`` read_default_file -- ``~/.my.cnf``
Name of the MySQL defaults file. Only used in MySQL connections. Name of the MySQL defaults file. Only used in MySQL connections.
read_default_group -- ``roundup`` read_default_group -- ``roundup``
Name of the group to use in the MySQL defaults file. Only used in Name of the group to use in the MySQL defaults file. Only used in
MySQL connections. 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** Section **logging**
config -- default *blank* config -- default *blank*
Path to configuration file for standard Python logging module. If this Path to configuration file for standard Python logging module. If this
option is set, logging configuration is loaded from specified file; option is set, logging configuration is loaded from specified file;
options 'filename' and 'level' in this section are ignored. The path may options 'filename' and 'level' in this section are ignored. The path may
be either absolute or relative to the directory containig this config file. be either absolute or relative to the directory containig this config file.
filename -- default *blank* filename -- default *blank*
Log file name for minimal logging facility built into Roundup. If no file Log file name for minimal logging facility built into Roundup. If no file
name specified, log messages are written on stderr. If above 'config' 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 option is set, this option has no effect. The path may be either absolute
or relative to the directory containig this config file. or relative to the directory containig this config file.
level -- ``ERROR`` level -- ``ERROR``
Minimal severity level of messages written to log file. If above 'config' Minimal severity level of messages written to log file. If above 'config'
option is set, this option has no effect. option is set, this option has no effect.
Allowed values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR`` Allowed values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``
.. index:: config.ini; sections mail
Section **mail** Section **mail**
Outgoing email options. Used for nosy messages, password reset and Outgoing email options. Used for nosy messages, password reset and
registration approval requests. registration approval requests.
domain -- ``localhost`` domain -- ``localhost``
Domain name used for email addresses. Domain name used for email addresses.
host -- default *blank* host -- default *blank*
SMTP mail host that roundup will use to send mail SMTP mail host that roundup will use to send mail
skipping to change at line 332 skipping to change at line 391
add_authorinfo -- ``yes`` add_authorinfo -- ``yes``
Add a line with author information at top of all messages send by Add a line with author information at top of all messages send by
roundup. roundup.
add_authoremail -- ``yes`` add_authoremail -- ``yes``
Add the mail address of the author to the author information at the 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, 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 only the name of the actor is added which protects the mail address
of the actor from being exposed at mail archives, etc. of the actor from being exposed at mail archives, etc.
.. index:: config.ini; sections mailgw
single: mailgw; config
see: mail gateway; mailgw
Section **mailgw** Section **mailgw**
Roundup Mail Gateway options Roundup Mail Gateway options
keep_quoted_text -- ``yes`` keep_quoted_text -- ``yes``
Keep email citations when accepting messages. Setting this to ``no`` strips Keep email citations when accepting messages. Setting this to ``no`` strips
out "quoted" text from the message. Signatures are also stripped. out "quoted" text from the message. Signatures are also stripped.
Allowed values: ``yes``, ``no`` Allowed values: ``yes``, ``no``
leave_body_unchanged -- ``no`` leave_body_unchanged -- ``no``
Preserve the email body as is - that is, keep the citations *and* Preserve the email body as is - that is, keep the citations *and*
skipping to change at line 412 skipping to change at line 475
Regular expression matching a blank line. Value is Python Regular Regular expression matching a blank line. Value is Python Regular
Expression (UTF8-encoded). Expression (UTF8-encoded).
ignore_alternatives -- ``no`` ignore_alternatives -- ``no``
When parsing incoming mails, roundup uses the first When parsing incoming mails, roundup uses the first
text/plain part it finds. If this part is inside a text/plain part it finds. If this part is inside a
multipart/alternative, and this option is set, all other multipart/alternative, and this option is set, all other
parts of the multipart/alternative are ignored. The default parts of the multipart/alternative are ignored. The default
is to keep all parts and attach them to the issue. is to keep all parts and attach them to the issue.
.. index:: config.ini; sections php
Section **pgp** Section **pgp**
OpenPGP mail processing options OpenPGP mail processing options
enable -- ``no`` enable -- ``no``
Enable PGP processing. Requires pyme. Enable PGP processing. Requires gpg.
roles -- default *blank* roles -- default *blank*
If specified, a comma-separated list of roles to perform PGP If specified, a comma-separated list of roles to perform PGP
processing on. If not specified, it happens for all users. processing on. If not specified, it happens for all users.
homedir -- default *blank* homedir -- default *blank*
Location of PGP directory. Defaults to $HOME/.gnupg if not Location of PGP directory. Defaults to $HOME/.gnupg if not
specified. specified.
.. index:: config.ini; sections nosy
Section **nosy** Section **nosy**
Nosy messages sending Nosy messages sending
messages_to_author -- ``no`` messages_to_author -- ``no``
Send nosy messages to the author of the message. Send nosy messages to the author of the message.
If ``yes`` is used, then messages are sent to the author 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). 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. When set to ``nosy``, the nosy list controls sending messages to the author.
Allowed values: ``yes``, ``no``, ``new``, ``nosy`` Allowed values: ``yes``, ``no``, ``new``, ``nosy``
skipping to change at line 464 skipping to change at line 531
email_sending -- ``single`` email_sending -- ``single``
Controls the email sending from the nosy reactor. If ``multiple`` then Controls the email sending from the nosy reactor. If ``multiple`` then
a separate email is sent to each recipient. If ``single`` then a single a separate email is sent to each recipient. If ``single`` then a single
email is sent with each recipient as a CC address. email is sent with each recipient as a CC address.
max_attachment_size -- ``2147483647`` max_attachment_size -- ``2147483647``
Attachments larger than the given number of bytes won't be attached 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 to nosy mails. They will be replaced by a link to the tracker's
download page for the file. 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 You may generate a new default config file using the ``roundup-admin
genconfig`` command. You can generate a new config file merging in genconfig`` command. You can generate a new config file merging in
existing settings using the ``roundup-admin updateconfig`` command. existing settings using the ``roundup-admin updateconfig`` command.
Configuration variables may be referred to in lower or upper case. In code, 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 variables not in the "main" section are referred to using their section and
name, so "domain" in the section "mail" becomes MAIL_DOMAIN. The name, so "domain" in the section "mail" becomes MAIL_DOMAIN.
configuration variables available are:
.. index:: pair: configuration; extensions
pair: configuration; detectors
Extending the configuration file Extending the configuration file
-------------------------------- --------------------------------
You can't add new variables to the config.ini file in the tracker home but You can't add new variables to the config.ini file in the tracker home but
you can add two new config.ini files: you can add two new config.ini files:
- a config.ini in the ``extensions`` directory will be loaded and attached - a config.ini in the ``extensions`` directory will be loaded and attached
to the config variable as "ext". to the config variable as "ext".
- a config.ini in the ``detectors`` directory will be loaded and attached - a config.ini in the ``detectors`` directory will be loaded and attached
skipping to change at line 501 skipping to change at line 575
db.config.detectors['QA_RECIPIENTS'] db.config.detectors['QA_RECIPIENTS']
Note that the name grouping applied to the main configuration file is Note that the name grouping applied to the main configuration file is
applied to the extension config files, so if you instead have:: applied to the extension config files, so if you instead have::
[qa] [qa]
recipients = email@example.com recipients = email@example.com
then the above ``db.config.detectors['QA_RECIPIENTS']`` will still work. then the above ``db.config.detectors['QA_RECIPIENTS']`` will still work.
.. index:: ! schema
Tracker Schema Tracker Schema
============== ==============
.. note:: .. note::
if you modify the schema, you'll most likely need to edit the if you modify the schema, you'll most likely need to edit the
`web interface`_ HTML template files and `detectors`_ to reflect `web interface`_ HTML template files and `detectors`_ to reflect
your changes. your changes.
A tracker schema defines what data is stored in the tracker's database. 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 Schemas are defined using Python code in the ``schema.py`` module of your
tracker. tracker.
The ``schema.py`` module The ``schema.py`` and ``initial_data.py`` modules
-------------------------------------------------
The ``schema.py`` module contains two functions: 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.
**open** .. index:: schema; classic - description of
This function defines 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.
**init**
This function is responsible for setting 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.
The "classic" schema The "classic" schema
-------------------- --------------------
The "classic" schema looks like this (see section `setkey(property)`_ The "classic" schema looks like this (see section `setkey(property)`_
below for the meaning of ``'setkey'`` -- you may also want to look into below for the meaning of ``'setkey'`` -- you may also want to look into
the sections `setlabelprop(property)`_ and `setorderprop(property)`_ for the sections `setlabelprop(property)`_ and `setorderprop(property)`_ for
specifying (default) labelling and ordering of classes.):: specifying (default) labelling and ordering of classes.)::
pri = Class(db, "priority", name=String(), order=String()) pri = Class(db, "priority", name=String(), order=String())
skipping to change at line 564 skipping to change at line 639
date=Date(), recipients=Multilink("user"), date=Date(), recipients=Multilink("user"),
files=Multilink("file"), messageid=String(), inreplyto=String()) files=Multilink("file"), messageid=String(), inreplyto=String())
file = FileClass(db, "file", name=String()) file = FileClass(db, "file", name=String())
issue = IssueClass(db, "issue", keyword=Multilink("keyword"), issue = IssueClass(db, "issue", keyword=Multilink("keyword"),
status=Link("status"), assignedto=Link("user"), status=Link("status"), assignedto=Link("user"),
priority=Link("priority")) priority=Link("priority"))
issue.setkey('title') issue.setkey('title')
.. index:: schema; allowed changes
What you can't do to the schema What you can't do to the schema
------------------------------- -------------------------------
You must never: You must never:
**Remove the users class** **Remove the users class**
This class is the only *required* class in Roundup. This class is the only *required* class in Roundup.
**Remove the "username", "address", "password" or "realname" user properties** **Remove the "username", "address", "password" or "realname" user properties**
Various parts of Roundup require these properties. Don't remove them. Various parts of Roundup require these properties. Don't remove them.
skipping to change at line 589 skipping to change at line 666
assignedto property a Multilink, you'd need to create a new property assignedto property a Multilink, you'd need to create a new property
assignedto_list and remove the old assignedto property. assignedto_list and remove the old assignedto property.
What you can do to the schema What you can do to the schema
----------------------------- -----------------------------
Your schema may be changed at any time before or after the tracker has been Your schema may be changed at any time before or after the tracker has been
initialised (or used). You may: initialised (or used). You may:
**Add new properties to classes, or add whole new classes** **Add new properties to classes, or add whole new classes**
This is painless and easy to do - there are generally no repurcussions This is painless and easy to do - there are generally no repercussions
from adding new information to a tracker's schema. from adding new information to a tracker's schema.
**Remove properties** **Remove properties**
Removing properties is a little more tricky - you need to make sure that 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 the property is no longer used in the `web interface`_ *or* by the
detectors_. detectors_.
Classes and Properties - creating a new information store Classes and Properties - creating a new information store
--------------------------------------------------------- ---------------------------------------------------------
skipping to change at line 626 skipping to change at line 703
Initially empty, will hold all e-mail messages sent to or Initially empty, will hold all e-mail messages sent to or
generated by roundup. generated by roundup.
file file
Initially empty, will hold all files attached to issues. Initially empty, will hold all files attached to issues.
issue issue
Initially empty, this is where the issue information is stored. Initially empty, this is where the issue information is stored.
We define the "priority" and "status" classes to allow two things: We define the "priority" and "status" classes to allow two things:
reduction in the amount of information stored on the issue and more
powerful, accurate searching of issues by priority and status. By only 1. reduction in the amount of information stored on the issue
requiring a link on the issue (which is stored as a single number) we 2. more powerful, accurate searching of issues by priority and status
reduce the chance that someone mis-types a priority or status - or
simply makes a new one up. 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 and Items Class and Items
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
A Class defines a particular class (or type) of data that will be stored 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 in the database. A class comprises one or more properties, which gives
the information about the class items. the information about the class items.
The actual data entered into the database, using ``class.create()``, are The actual data entered into the database, using ``class.create()``, are
called items. They have a special immutable property called ``'id'``. We called items. They have a special immutable property called ``'id'``. We
sometimes refer to this as the *itemid*. sometimes refer to this as the *itemid*.
.. index:: schema; property types
Properties Properties
~~~~~~~~~~ ~~~~~~~~~~
A Class is comprised of one or more properties of the following types: A Class is comprised of one or more properties of the following types:
* String properties are for storing arbitrary-length strings. String
* Password properties are for storing encoded arbitrary-length strings. properties are for storing arbitrary-length strings.
The default encoding is defined on the ``roundup.password.Password`` Password
class. properties are for storing encoded arbitrary-length strings.
* Date properties store date-and-time stamps. Their values are Timestamp The default encoding is defined on the ``roundup.password.Password``
objects. class.
* Integer properties store integer values. (Number can store real/float values.) Date
* Number properties store numeric values. There is an option to use properties store date-and-time stamps. Their values are Timestamp
double-precision floating point numbers. objects.
* Boolean properties store on/off, yes/no, true/false values. Interval
* A Link property refers to a single other item selected from a properties store time periods rather than absolute dates. For
specified class. The class is part of the property; the value is an example 2 hours.
integer, the id of the chosen item. Integer
* A Multilink property refers to possibly many items in a specified properties store integer values. (Number can store real/float values.)
class. The value is a list of integers. 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 Properties can have additional attributes to change the default
behaviour: behaviour:
.. index:: triple: schema; property attributes; required
triple: schema; property attributes; default_value
triple: schema; property attributes; quiet
* All properties support the following attributes: * All properties support the following attributes:
- ``required``: see `design documentation`_. Adds the property to - ``required``: see `design documentation`_. Adds the property to
the list returned by calling get_required_props for the class. the list returned by calling get_required_props for the class.
- ``default_value``: see `design documentation`_ Sets the default - ``default_value``: see `design documentation`_ Sets the default
value is the property is not set. value if the property is not set.
- ``quiet``: see `design documentation`_. Suppresses user visible - ``quiet``: see `design documentation`_. Suppresses user visible
to changes to this property. The property change is not reported: to changes to this property. The property change is not reported:
- in the change feedback/confirmation message in the web - in the change feedback/confirmation message in the web
interface interface
- the property change section of the nosy email - the property change section of the nosy email
- the web history at the bottom of an item's page - 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 This can be used to store state of the user interface (e.g. the
names of elements that are collapsed or hidden from the names of elements that are collapsed or hidden from the
user). Making properties that are updated as an indirect result of user). Making properties that are updated as an indirect result of
a user's change (e.g. updating a blockers property, counting a user's change (e.g. updating a blockers property, counting
number of times an issue was reopened or reassigned etc.) should number of times an issue was reopened or reassigned etc.) should
not be displayed to the user as they can be confusing. 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 * 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 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 can be set to 'yes' to allow a property's contents to be in the full
text index. text index.
.. index:: triple: schema; property attributes; use_double
* Number properties can have a ``use_double`` attribute that, when set * Number properties can have a ``use_double`` attribute that, when set
to ``True``, will use double precision floating point in the database. to ``True``, will use double precision floating point in the database.
* Link and Multilink properties can have several attributes: * 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 - ``do_journal``: By default, every change of a link property is
recorded in the item being linked to (or being unlinked). A typical recorded in the item being linked to (or being unlinked). A typical
use-case for setting ``do_journal='no'`` would be to turn off use-case for setting ``do_journal='no'`` would be to turn off
journalling of nosy list, message author and message recipient link journalling of nosy list, message author and message recipient link
and unlink events to prevent the journal from clogged with these and unlink events to prevent the journal from clogged with these
events. events.
.. index:: triple: schema; property attributes; try_id_parsing
- ``try_id_parsing`` is turned on by default. If entering a number - ``try_id_parsing`` is turned on by default. If entering a number
into a Link or Multilink field, roundup interprets this number as an 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 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 (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 numeric name and should never match an ID. In this case you can set
``try_id_parsing='no'``. ``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 - The ``msg_header_property`` is used by the mail gateway when sending
out messages. When a link or multilink property of an issue changes, out messages. When a link or multilink property of an issue changes,
roundup creates email headers of the form:: roundup creates email headers of the form::
X-Roundup-issue-prop: value X-Roundup-issue-prop: value
where ``value`` is the ``name`` property for the linked item(s). where ``value`` is the ``name`` property for the linked item(s).
For example, if you have a multilink for attached_files in your For example, if you have a multilink for attached_files in your
issue, you will see a header:: issue, you will see a header::
skipping to change at line 771 skipping to change at line 940
for the assigned_to property will generated message headers of the for the assigned_to property will generated message headers of the
form:: form::
X-Roundup-issue-assigned_to: joe_user X-Roundup-issue-assigned_to: joe_user
for emails sent on issues where joe_user has been assigned to the issue. 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 If this property is set to the empty string "", it will prevent
the header from being generated on outgoing mail. 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: All Classes automatically have a number of properties by default:
*creator* *creator*
Link to the user that created the item. Link to the user that created the item.
*creation* *creation*
Date the item was created. Date the item was created.
*actor* *actor*
Link to the user that last modified the item. Link to the user that last modified the item.
*activity* *activity*
Date the item was last modified. Date the item was last modified.
.. index:: triple: schema; class property; content
triple: schema; class property; type
FileClass FileClass
~~~~~~~~~ ~~~~~~~~~
FileClasses save their "content" attribute off in a separate file from 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 rest of the database. This reduces the number of large entries in
the database, which generally makes databases more efficient, and also the database, which generally makes databases more efficient, and also
allows us to use command-line tools to operate on the files. They are 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 stored in the files sub-directory of the ``'db'`` directory in your
tracker. FileClasses also have a "type" attribute to store the MIME tracker. FileClasses also have a "type" attribute to store the MIME
type of the file. type of the file.
.. index:: triple: schema; class property; messages
triple: schema; class property; files
triple: schema; class property; nosy
triple: schema; class property; superseder
IssueClass IssueClass
~~~~~~~~~~ ~~~~~~~~~~
IssueClasses automatically include the "messages", "files", "nosy", and IssueClasses automatically include the "messages", "files", "nosy", and
"superseder" properties. "superseder" properties.
The messages and files properties list the links to the messages and 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 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" 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 e-mails when messages are sent to or generated by the issue. The nosy
skipping to change at line 815 skipping to change at line 997
They also have the dynamically generated "creation", "activity" and They also have the dynamically generated "creation", "activity" and
"creator" properties. "creator" properties.
The value of the "creation" property is the date when an item was 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 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 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" on the first and last records in the item's journal). The "creator"
property holds a link to the user that created the issue. property holds a link to the user that created the issue.
.. index: triple: schema; class method; setkey
setkey(property) setkey(property)
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
.. index:: roundup-admin; setting assignedto on an issue
Select a String property of the class to be the key property. The key 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 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 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, 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 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:: assign an issue to him, we could do either of::
roundup-admin set issue23 assignedto=2 roundup-admin set issue23 assignedto=2
or:: or::
roundup-admin set issue23 assignedto=richard roundup-admin set issue23 assignedto=richard
Note, the same thing can be done in the web and e-mail interfaces. Note, the same thing can be done in the web and e-mail interfaces.
.. index: triple: schema; class method; setlabelprop
setlabelprop(property) setlabelprop(property)
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Select a property of the class to be the label property. The label 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., 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 when displaying a link to an item. If setlabelprop is not specified for
a class, the following values are tried for the label: a class, the following values are tried for the label:
* the key of the class (see the `setkey(property)`_ section above) * the key of the class (see the `setkey(property)`_ section above)
* the "name" property * the "name" property
skipping to change at line 854 skipping to change at line 1042
* the first property from the sorted property name list * the first property from the sorted property name list
So in most cases you can get away without specifying setlabelprop So in most cases you can get away without specifying setlabelprop
explicitly. explicitly.
You should make sure that users have View access to this property or 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 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 user, looping over items in the class (e.g. messages attached to an
issue) will not work. issue) will not work.
.. index: triple: schema; class method; setorderprop
setorderprop(property) setorderprop(property)
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Select a property of the class to be the order property. The order 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, 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 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 interface, the order property of class B is used for sorting. If
setorderprop is not specified for a class, the following values are tried setorderprop is not specified for a class, the following values are tried
for the order property: for the order property:
* the property named "order" * the property named "order"
* the label property (see `setlabelprop(property)`_ above) * the label property (see `setlabelprop(property)`_ above)
So in most cases you can get away without specifying setorderprop So in most cases you can get away without specifying setorderprop
explicitly. explicitly.
.. index: triple: schema; class method; create
create(information) create(information)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Create an item in the database. This is generally used to create items Create an item in the database. This is generally used to create items
in the "definitional" classes like "priority" and "status". in the "definitional" classes like "priority" and "status".
.. index: schema; item ordering
A note about ordering A note about ordering
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
When we sort items in the hyperdb, we use one of a number of methods, When we sort items in the hyperdb, we use one of a number of methods,
depending on the properties being sorted on: depending on the properties being sorted on:
1. If it's a String, Integer, Number, Date or Interval property, we 1. If it's a String, Integer, Number, Date or Interval property, we
just sort the scalar value of the property. Strings are sorted just sort the scalar value of the property. Strings are sorted
case-sensitively. case-sensitively.
2. If it's a Link property, we sort by either the linked item's "order" 2. If it's a Link property, we sort by either the linked item's "order"
skipping to change at line 898 skipping to change at line 1092
list item, and if they're the same, we sort by the second item, and list item, and if they're the same, we sort by the second item, and
so on. so on.
Note that if an "order" property is defined on a Class that is used for 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" sorting, all items of that Class *must* have a value against the "order"
property, or sorting will result in random ordering. property, or sorting will result in random ordering.
Examples of adding to your schema Examples of adding to your schema
--------------------------------- ---------------------------------
The Roundup wiki has examples of how schemas can be customised to add Some examples are in the :ref:`CustomExamples` section below.
new functionality.
Also the `Roundup wiki`_ has additional examples of how schemas can be
customised to add new functionality.
.. _Roundup wiki:
https://wiki.roundup-tracker.org/
.. index:: !detectors
Detectors - adding behaviour to your tracker Detectors - adding behaviour to your tracker
============================================ ============================================
.. _detectors: .. _detectors:
Detectors are initialised every time you open your tracker database, so 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 you're free to add and remove them any time, even after the database is
initialised via the ``roundup-admin initialise`` command. initialised via the ``roundup-admin initialise`` command.
The detectors in your tracker fire *before* (**auditors**) and *after* The detectors in your tracker fire *before* (**auditors**) and *after*
(**reactors**) changes to the contents of your database. They are Python (**reactors**) changes to the contents of your database. They are Python
modules that sit in your tracker's ``detectors`` directory. You will modules that sit in your tracker's ``detectors`` directory. You will
have some installed by default - have a look. You can write new have some installed by default - have a look. You can write new
detectors or modify the existing ones. The existing detectors installed detectors or modify the existing ones. The existing detectors installed
for you are: for you are:
.. index:: detectors; installed
**nosyreaction.py** **nosyreaction.py**
This provides the automatic nosy list maintenance and email sending. This provides the automatic nosy list maintenance and email sending.
The nosy reactor (``nosyreaction``) fires when new messages are added The nosy reactor (``nosyreaction``) fires when new messages are added
to issues. The nosy auditor (``updatenosy``) fires when issues are to issues. The nosy auditor (``updatenosy``) fires when issues are
changed, and figures out what changes need to be made to the nosy list changed, and figures out what changes need to be made to the nosy list
(such as adding new authors, etc.) (such as adding new authors, etc.)
**statusauditor.py** **statusauditor.py**
This provides the ``chatty`` auditor which changes the issue status This provides the ``chatty`` auditor which changes the issue status
from ``unread`` or ``closed`` to ``chatting`` if new messages appear. from ``unread`` or ``closed`` to ``chatting`` if new messages appear.
It also provides the ``presetunread`` auditor which pre-sets the It also provides the ``presetunread`` auditor which pre-sets the
skipping to change at line 943 skipping to change at line 1146
roles lists). roles lists).
If you don't want this default behaviour, you're completely free to change If you don't want this default behaviour, you're completely free to change
or remove these detectors. or remove these detectors.
See the detectors section in the `design document`__ for details of the See the detectors section in the `design document`__ for details of the
interface for detectors. interface for detectors.
__ design.html __ design.html
.. index:: detectors; writing api
Detector API Detector API
------------ ------------
.. index:: pair: detectors; auditors
single: auditors; function signature
single: auditors; defining
single: auditors; arguments
Auditors are called with the arguments:: Auditors are called with the arguments::
audit(db, cl, itemid, newdata) audit(db, cl, itemid, newdata)
where ``db`` is the database, ``cl`` is an instance of Class or where ``db`` is the database, ``cl`` is an instance of Class or
IssueClass within the database, and ``newdata`` is a dictionary mapping IssueClass within the database, and ``newdata`` is a dictionary mapping
property names to values. property names to values.
For a ``create()`` operation, the ``itemid`` argument is None and For a ``create()`` operation, the ``itemid`` argument is None and
newdata contains all of the initial property values with which the item newdata contains all of the initial property values with which the item
is about to be created. is about to be created.
For a ``set()`` operation, newdata contains only the names and values of For a ``set()`` operation, newdata contains only the names and values of
properties that are about to be changed. properties that are about to be changed.
For a ``retire()`` or ``restore()`` operation, newdata is None. 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:: Reactors are called with the arguments::
react(db, cl, itemid, olddata) react(db, cl, itemid, olddata)
where ``db`` is the database, ``cl`` is an instance of Class or where ``db`` is the database, ``cl`` is an instance of Class or
IssueClass within the database, and ``olddata`` is a dictionary mapping IssueClass within the database, and ``olddata`` is a dictionary mapping
property names to values. property names to values.
For a ``create()`` operation, the ``itemid`` argument is the id of the For a ``create()`` operation, the ``itemid`` argument is the id of the
newly-created item and ``olddata`` is None. newly-created item and ``olddata`` is None.
For a ``set()`` operation, ``olddata`` contains the names and previous For a ``set()`` operation, ``olddata`` contains the names and previous
values of properties that were changed. values of properties that were changed.
For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
the retired or restored item and ``olddata`` is None. the retired or restored item and ``olddata`` is None.
.. index:: detectors; additional
Additional Detectors Ready For Use Additional Detectors Ready For Use
---------------------------------- ----------------------------------
Sample additional detectors that have been found useful will appear in Sample additional detectors that have been found useful will appear in
the ``'detectors'`` directory of the Roundup distribution. If you want the ``'detectors'`` directory of the Roundup distribution. If you want
to use one, copy it to the ``'detectors'`` of your tracker instance: to use one, copy it to the ``'detectors'`` of your tracker instance:
**irker.py** **irker.py**
This detector sends notification on IRC through an irker daemon This detector sends notification on IRC through an irker daemon
(http://www.catb.org/esr/irker/) when issues are created or messages (http://www.catb.org/esr/irker/) when issues are created or messages
skipping to change at line 1012 skipping to change at line 1229
"classic" Roundup doesn't have that status, so you'll have to add it. If "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. you don't want to though, it'll just use "in-progress" instead.
**email_auditor.py** **email_auditor.py**
If a file added to an issue is of type message/rfc822, we tack on the If a file added to an issue is of type message/rfc822, we tack on the
extension .eml. extension .eml.
The reason for this is that Microsoft Internet Explorer will not open The reason for this is that Microsoft Internet Explorer will not open
things with a .eml attachment, as they deem it 'unsafe'. Worse yet, things with a .eml attachment, as they deem it 'unsafe'. Worse yet,
they'll just give you an incomprehensible error message. For more they'll just give you an incomprehensible error message. For more
information, see the detector code - it has a length explanation. information, see the detector code - it has a length explanation.
.. index:: auditors; rules for use
single: reactors; rules for use
Auditor or Reactor? Auditor or Reactor?
------------------- -------------------
Generally speaking, the following rules should be observed: Generally speaking, the following rules should be observed:
**Auditors** **Auditors**
Are used for `vetoing creation of or changes to items`_. They might Are used for `vetoing creation of or changes to items`_. They might
also make automatic changes to item properties. also make automatic changes to item properties.
**Reactors** **Reactors**
Detect changes in the database and react accordingly. They should avoid Detect changes in the database and react accordingly. They should avoid
skipping to change at line 1060 skipping to change at line 1280
The module ``roundup.mailer`` contains most of the nuts-n-bolts required The module ``roundup.mailer`` contains most of the nuts-n-bolts required
to generate email messages from Roundup. to generate email messages from Roundup.
In addition, the ``IssueClass`` methods ``nosymessage()`` and In addition, the ``IssueClass`` methods ``nosymessage()`` and
``send_message()`` are used to generate nosy messages, and may generate ``send_message()`` are used to generate nosy messages, and may generate
messages which only consist of a change note (ie. the message id parameter 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 is not required - this is referred to as a "System Message" because it
comes from "the system" and not a user). comes from "the system" and not a user).
.. index:: extensions
.. index:: extensions; add python functions to tracker
Extensions - adding capabilities to your tracker Extensions - adding capabilities to your tracker
================================================ ================================================
.. _extensions: .. _extensions:
While detectors_ add new behavior by reacting to changes in tracked While detectors_ add new behavior by reacting to changes in tracked
objects, `extensions` add new actions and utilities to Roundup, which objects, `extensions` add new actions and utilities to Roundup, which
are mostly used to enhance web interface. are mostly used to enhance web interface.
You can create an extension by creating Python file in your tracker You can create an extension by creating Python file in your tracker
``extensions`` directory. All files from this dir are loaded when ``extensions`` directory. All files from this dir are loaded when
skipping to change at line 1086 skipping to change at line 1309
flexible extension point mechanism. flexible extension point mechanism.
* ``instance.registerUtil`` is used for adding `templating utilities`_ * ``instance.registerUtil`` is used for adding `templating utilities`_
(see `adding a time log to your issues`_ for an example) (see `adding a time log to your issues`_ for an example)
* ``instance.registerAction`` is used to add more actions to instance * ``instance.registerAction`` is used to add more actions to instance
and to web interface. See `Defining new web actions`_ for details. and to web interface. See `Defining new web actions`_ for details.
Generic action can be added by inheriting from ``action.Action`` Generic action can be added by inheriting from ``action.Action``
instead of ``cgi.action.Action``. instead of ``cgi.action.Action``.
interfaces.py - hooking into the core of roundup
================================================
.. _interfaces.py:
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
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, but there are places
where interfaces.py is still useful.
Example: Changing Cache-Control headers
---------------------------------------
The Client class in cgi/client.py has a lookup table that is used to
set the Cache-Control headers for static files. The entries in this
table are set from interfaces.py using::
from roundup.cgi.client import Client
Client.Cache_Control['text/css'] = "public, max-age=3600"
Client.Cache_Control['application/javascript'] = "public, max-age=30"
Client.Cache_Control['rss.xml'] = "public, max-age=900"
Client.Cache_Control['local.js'] = "public, max-age=7200"
In this case static files delivered using @@file will have cache
headers set. These files are searched for along the `static_files`
path in the tracker's `config.ini`. In the example above:
* a css file (e.g. @@file/style.css) will be cached for an hour
* javascript files (e.g. @@file/libraries/jquery.js) will be cached
for 30 seconds
* a file named rss.xml will be cached for 15 minutes
* a file named local.js will be cached for 2 hours
Note that a file name match overrides the mime type settings.
Example: Implement password complexity checking
-----------------------------------------------
.. index:: tracker; lib directory (example)
This example uses the zxcvbn_ module that you can place in the zxcvbn
subdirectory of your tracker's lib directory.
If you add this to the interfaces.py file in the root directory of
your tracker (same place as schema.py)::
import roundup.password as password
from roundup.exceptions import Reject
from zxcvbn import zxcvbn
# monkey patch the setPassword method with this method
# that checks password strength.
origPasswordFunc = password.Password.setPassword
def mpPasswordFunc(self, plaintext, scheme, config=None):
""" Replace the password set function with one that
verifies that the password is complex enough. It
has to be done at this point and not in an auditor
as the auditor only sees the encrypted password.
"""
results = zxcvbn(plaintext)
if results['score'] < 3:
l = []
map(l.extend, [[results['feedback']['warning']], results['feedback'][
'suggestions']])
errormsg = " ".join(l)
raise Reject ("Password is too easy to guess. " + errormsg)
return origPasswordFunc(self, plaintext, scheme, config=config)
password.Password.setPassword = mpPasswordFunc
it replaces the setPassword method in the Password class. The new
version validates that the password is sufficiently complex. Then it
passes off the setting of password to the original method.
Example: Enhance time intervals
-------------------------------
To make the user interface easier to use, you may want to support
other forms for intervals. For example you can support an interval
like 1.5 by interpreting it the same as 1:30 (1 hour 30 minutes).
Also you can allow a bare integer (e.g. 45) as a number of minutes.
To do this we intercept the from_raw method of the Interval class in
hyperdb.py with::
import roundup.hyperdb as hyperdb
origFrom_Raw = hyperdb.Interval.from_raw
def normalizeperiod(self, value, **kw):
''' Convert alternate time forms into standard interval format
[+-] [#y] [#m] [#w] [#d] [[[H]H:MM]:SS]
if value is float, it's hour and fractional hours
if value is integer, it's number of minutes
'''
if ":" not in value:
# Not a specified interval
# if int consider number of minutes
try:
isMinutes = int(value)
minutes = isMinutes%60
hours = (isMinutes - minutes) / 60
value = "%d:%d"%(hours,minutes)
except ValueError:
pass
# if float, consider it number of hours and fractional hours.
import math
try:
afterdecimal, beforedecimal = math.modf(float(value))
value = "%d:%d"%(beforedecimal,60*afterdecimal)
except ValueError:
pass
return origFrom_Raw(self, value, **kw)
hyperdb.Interval.from_raw = normalizeperiod
any call to convert an interval from raw form now has two simpler
(and more friendly) ways to specify common time intervals.
Example: Modifying the mail gateway
-----------------------------------
One site receives email on a main gateway. The virtual alias delivery
table on the postfix server is configured with::
test-issues@example.com roundup-test@roundup-vm.example.com
test-support@example.com roundup-test+support-a@roundup-vm.example.com
test@support.example.com roundup-test+support-b@roundup-vm.example.com
These modifications to the mail gateway for roundup allows anonymous
submissions. It hides all of the requesters under the "support"
user. It also makes some other modifications to the mail parser
allowing keywords to be set and prefixes to be defined based on the
delivery alias.
This is the entry in interfaces.py::
import roundup.mailgw
import email.utils
class SupportTracker(object):
def __init__(self, prefix=None, keyword=None):
self.prefix = prefix
self.keyword = keyword
# Define new prefixes and keywords based on local address.
support_trackers = {
### production instances ###
### test instances ###
'roundup-test+support-a':
SupportTracker(prefix='Support 1', keyword='support1'),
'roundup-test+support-b':
SupportTracker(prefix='Support 2', keyword='support2'),
'roundup-test2+support-a':
SupportTracker(prefix='Support 1', keyword='support1'),
'roundup-test2+support-b':
SupportTracker(prefix='Support 2', keyword='support2'),
}
class parsedMessage(roundup.mailgw.parsedMessage):
def __init__(self, mailgw, message, support_tracker):
roundup.mailgw.parsedMessage.__init__(self, mailgw, message)
if support_tracker.prefix:
self.prefix = '%s: ' % support_tracker.prefix
else:
self.prefix = ''
self.keywords = []
if support_tracker.keyword:
try:
self.keywords = [
self.db.keyword.lookup(support_tracker.keyword)]
except KeyError:
pass
self.config.ADD_AUTHOR_TO_NOSY = 'no'
self.config.ADD_RECIPIENTS_TO_NOSY = 'no'
self.config.MAILGW_KEEP_QUOTED_TEXT = 'yes'
self.config.MAILGW_LEAVE_BODY_UNCHANGED = 'yes'
self.classname = 'issue'
self.pfxmode = 'loose'
self.sfxmode = 'none'
# set the support user id
self.fixed_author = self.db.user.lookup('support')
self.fixed_props = {
'nosy': [self.fixed_author],
'keyword': self.keywords,
}
def handle_help(self):
pass
def check_subject(self):
if not self.subject:
self.subject = 'no subject'
def rego_confirm(self):
pass
def get_author_id(self):
# force the support user to be the author
self.author = self.fixed_author
def get_props(self):
self.props = {}
if not self.nodeid:
self.props.update(self.fixed_props)
self.props['title'] = ("%s%s" % (
self.prefix, self.subject.replace('[', '(').replace(']', ')')
))
def get_content_and_attachments(self):
roundup.mailgw.parsedMessage.get_content_and_attachments(self)
if not self.content:
self.content = 'no text'
intro = []
for header in ['From', 'To', 'Cc']:
for addr in self.message.getaddrlist(header):
intro.append('%s: %s' % (header, email.utils.formataddr(addr)
))
intro.append('Subject: %s' % self.subject)
intro.append('\n')
self.content = '\n'.join(intro) + self.content
class MailGW(roundup.mailgw.MailGW):
def parsed_message_class(self, mailgw, message):
support_tracker = None
# The delivered-to header is unique to postfix
# it is the target address:
# roundup-test+support-a@roundup-vm.example.com
# rather than
# test-support@example.com
recipients = message.getaddrlist('delivered-to')
if recipients:
localpart = recipients[0][1].rpartition('@')[0]
support_tracker = support_trackers.get(localpart)
if support_tracker:
# parse the mesage using the parsedMessage class
# defined above.
return parsedMessage(mailgw, message, support_tracker)
else:
# parse the message normally
return roundup.mailgw.parsedMessage(mailgw, message)
This is the most complex example section. The mail gateway is also one
of the more complex subsystems in roundup, and modifying it is not
trivial.
Other Examples
--------------
See the `rest interface documentation`_ for instructions on how to add
new rest endpoints using interfaces.py.
Database Content Database Content
================ ================
.. note:: .. note::
If you modify the content of definitional classes, you'll most If you modify the content of definitional classes, you'll most
likely need to edit the tracker `detectors`_ to reflect your changes. likely need to edit the tracker `detectors`_ to reflect your changes.
Customisation of the special "definitional" classes (eg. status, Customisation of the special "definitional" classes (eg. status,
priority, resolution, ...) may be done either before or after the priority, resolution, ...) may be done either before or after the
tracker is initialised. The actual method of doing so is completely tracker is initialised. The actual method of doing so is completely
skipping to change at line 1140 skipping to change at line 1625
If defined, the user may use the email interface. Used by default to deny 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 users access to the email interface. When granted to the
Anonymous user, they will be automatically registered by the email Anonymous user, they will be automatically registered by the email
interface (see also the ``new_email_user_roles`` configuration option). interface (see also the ``new_email_user_roles`` configuration option).
*Web Access* *Web Access*
If defined, the user may use the web interface. All users are able to see If defined, the user may use the web interface. All users are able to see
the login form, regardless of this setting (thus enabling logging in). the login form, regardless of this setting (thus enabling logging in).
*Web Roles* *Web Roles*
Controls user access to editing the "roles" property of the "user" class. Controls user access to editing the "roles" property of the "user" class.
TODO: deprecate in favour of a property-based control. TODO: deprecate in favour of a property-based control.
*Rest Access* and *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`_.
These are hooked into the default Roles: These are hooked into the default Roles:
- Admin (Create, Edit, View and everything; Web Roles) - Admin (Create, Edit, View and everything; Web Roles)
- User (Web Access; Email Access) - User (Web Access; Email Access)
- Anonymous (Web Access) - Anonymous (Web Access)
And finally, the "admin" user gets the "Admin" Role, and the "anonymous" And finally, the "admin" user gets the "Admin" Role, and the "anonymous"
user gets "Anonymous" assigned when the tracker is installed. user gets "Anonymous" assigned when the tracker is installed.
skipping to change at line 1177 skipping to change at line 1667
# #
# See the configuration and customisation document for information # See the configuration and customisation document for information
# about security setup. # about security setup.
# #
# REGULAR USERS # REGULAR USERS
# #
# Give the regular users access to the web and email interface # Give the regular users access to the web and email interface
db.security.addPermissionToRole('User', 'Web Access') db.security.addPermissionToRole('User', 'Web Access')
db.security.addPermissionToRole('User', 'Email 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 # Assign the access and edit Permissions for issue, file and message
# to regular users now # to regular users now
for cl in 'issue', 'file', 'msg', 'query', 'keyword': for cl in 'issue', 'file', 'msg', 'query', 'keyword':
db.security.addPermissionToRole('User', 'View', cl) db.security.addPermissionToRole('User', 'View', cl)
db.security.addPermissionToRole('User', 'Edit', cl) db.security.addPermissionToRole('User', 'Edit', cl)
db.security.addPermissionToRole('User', 'Create', cl) db.security.addPermissionToRole('User', 'Create', cl)
for cl in 'priority', 'status': for cl in 'priority', 'status':
db.security.addPermissionToRole('User', 'View', cl) db.security.addPermissionToRole('User', 'View', cl)
skipping to change at line 1236 skipping to change at line 1728
for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status': for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status':
db.security.addPermissionToRole('Anonymous', 'View', cl) db.security.addPermissionToRole('Anonymous', 'View', cl)
# [OPTIONAL] # [OPTIONAL]
# Allow anonymous users access to create or edit "issue" items (and the # Allow anonymous users access to create or edit "issue" items (and the
# related file and message items) # related file and message items)
#for cl in 'issue', 'file', 'msg': #for cl in 'issue', 'file', 'msg':
# db.security.addPermissionToRole('Anonymous', 'Create', cl) # db.security.addPermissionToRole('Anonymous', 'Create', cl)
# db.security.addPermissionToRole('Anonymous', 'Edit', 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 Automatic Permission Checks
--------------------------- ---------------------------
Permissions are automatically checked when information is rendered Permissions are automatically checked when information is rendered
through the web. This includes: through the web. This includes:
1. View checks for properties when being rendered via the ``plain()`` or 1. View checks for properties when being rendered via the ``plain()`` or
similar methods. If the check fails, the text "[hidden]" will be similar methods. If the check fails, the text "[hidden]" will be
displayed. displayed.
2. Edit checks for properties when the edit field is being rendered via 2. Edit checks for properties when the edit field is being rendered via
skipping to change at line 1318 skipping to change at line 1818
``properties=('list', 'of', 'property', 'names')`` ``properties=('list', 'of', 'property', 'names')``
is used to determine access for things other than just those is used to determine access for things other than just those
properties. For example a check for View permission on the issue 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 or an issue item can use any View permission for the issue
class even if that permission has a property list. This can be class even if that permission has a property list. This can be
confusing and surprising as you would think that a permission confusing and surprising as you would think that a permission
including properties would be used only for determining the including properties would be used only for determining the
access permission for those properties. 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 Setting ``props_only=True`` will make the permission valid only for
those properties. those properties.
If you use a lot of permissions with property checks, it can be If you use a lot of permissions with property checks, it can be
difficult to change all of them. Calling the function: difficult to change all of them. Calling the function:
db.security.set_props_only_default(True) db.security.set_props_only_default(True)
at the top of ``schema.py`` will make every permission creation 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 behave as though props_only was set to True. It is expected that the
skipping to change at line 2298 skipping to change at line 2807
**gettext** (*message*) **gettext** (*message*)
Return the localized translation of message Return the localized translation of message
**ngettext** (*singular*, *plural*, *number*) **ngettext** (*singular*, *plural*, *number*)
Like ``gettext()``, but consider plural forms. If a translation Like ``gettext()``, but consider plural forms. If a translation
is found, apply the plural formula to *number*, and return the is found, apply the plural formula to *number*, and return the
resulting message (some languages have more than two plural forms). resulting message (some languages have more than two plural forms).
If no translation is found, return singular if *number* is 1; If no translation is found, return singular if *number* is 1;
return plural otherwise. return plural otherwise.
This function requires python2.3; in earlier python versions
may not work as expected.
The context variable The context variable
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
The *context* variable is one of three things based on the current The *context* variable is one of three things based on the current
context (see `determining web context`_ for how we figure this out): context (see `determining web context`_ for how we figure this out):
1. if we're looking at a "home" page, then it's None 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 2. if we're looking at a specific hyperdb class, it's a
`hyperdb class wrapper`_. `hyperdb class wrapper`_.
3. if we're looking at a specific hyperdb item, it's a 3. if we're looking at a specific hyperdb item, it's a
skipping to change at line 2647 skipping to change at line 3153
<span tal:replace="python:context.status.menu(order='+name", <span tal:replace="python:context.status.menu(order='+name",
value='chatting', value='chatting',
filterspec={'status': '1,2,3,4'}" /> filterspec={'status': '1,2,3,4'}" />
sorted only on Multilink properties - produce a list of the linked sorted only on Multilink properties - produce a list of the linked
items sorted by some property, for example:: items sorted by some property, for example::
python:context.files.sorted('creation') python:context.files.sorted('creation')
Will list the files by upload date. 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.
reverse only on Multilink properties - produce a list of the linked reverse only on Multilink properties - produce a list of the linked
items in reverse order items in reverse order
isset returns True if the property has been set to a value isset returns True if the property has been set to a value
=========== ================================================================ =========== ================================================================
__ https://docs.python.org/2/library/time.html __ https://docs.python.org/2/library/time.html
All of the above functions perform checks for permissions required to All of the above functions perform checks for permissions required to
display or edit the data they are manipulating. The simplest case is display or edit the data they are manipulating. The simplest case is
editing an issue title. Including the expression:: editing an issue title. Including the expression::
skipping to change at line 3321 skipping to change at line 3832
``meta http-equiv`` lines added to the tracker templates in version 0.6.0 ``meta http-equiv`` lines added to the tracker templates in version 0.6.0
should be changed to include actual character set name:: should be changed to include actual character set name::
<meta http-equiv="Content-Type" <meta http-equiv="Content-Type"
tal:attributes="content string:text/html;; charset=${request/client/charset}" tal:attributes="content string:text/html;; charset=${request/client/charset}"
/> />
The charset is also sent in the http header. The charset is also sent in the http header.
.. _CustomExamples:
Examples Examples
======== ========
.. contents:: .. contents::
:local: :local:
:depth: 2 :depth: 2
Changing what's stored in the database Changing what's stored in the database
-------------------------------------- --------------------------------------
skipping to change at line 3346 skipping to change at line 3859
This example shows how to add a simple field (a due date) to the default This example shows how to add a simple field (a due date) to the default
classic schema. It does not add any additional behaviour, such as enforcing classic schema. It does not add any additional behaviour, such as enforcing
the due date, or causing automatic actions to fire if the due date passes. the due date, or causing automatic actions to fire if the due date passes.
You add new fields by editing the ``schema.py`` file in you tracker's home. You add new fields by editing the ``schema.py`` file in you tracker's home.
Schema changes are automatically applied to the database on the next Schema changes are automatically applied to the database on the next
tracker access (note that roundup-server would need to be restarted as it tracker access (note that roundup-server would need to be restarted as it
caches the schema). caches the schema).
.. index:: schema; example changes
1. Modify the ``schema.py``:: 1. Modify the ``schema.py``::
issue = IssueClass(db, "issue", issue = IssueClass(db, "issue",
assignedto=Link("user"), keyword=Multilink("keyword"), assignedto=Link("user"), keyword=Multilink("keyword"),
priority=Link("priority"), status=Link("status"), priority=Link("priority"), status=Link("status"),
due_date=Date()) due_date=Date())
2. Add an edit field to the ``issue.item.html`` template:: 2. Add an edit field to the ``issue.item.html`` template::
<tr> <tr>
skipping to change at line 3460 skipping to change at line 3975
The ``Multilink()`` means that each issue can have many categories. If The ``Multilink()`` means that each issue can have many categories. If
you were adding something with a one-to-one relationship to issues (such you were adding something with a one-to-one relationship to issues (such
as the "assignedto" property), use ``Link()`` instead. as the "assignedto" property), use ``Link()`` instead.
That is all you need to do to change the schema. The rest of the effort That is all you need to do to change the schema. The rest of the effort
is fiddling around so you can actually use the new category. is fiddling around so you can actually use the new category.
Populating the new category class Populating the new category class
::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::
If you haven't initialised the database with the ``roundup-admin`` If you haven't initialised the database with the
"initialise" command, then you can add the following to the tracker "``roundup-admin initialise``" command, then you
``initial_data.py`` under the comment:: can add the following to the tracker ``initial_data.py``
under the comment::
# add any additional database creation steps here - but only if you # add any additional database creation steps here - but only if you
# haven't initialised the database with the admin "initialise" command # haven't initialised the database with the admin "initialise" command
Add:: Add::
category = db.getclass('category') category = db.getclass('category')
category.create(name="scipy") category.create(name="scipy")
category.create(name="chaco") category.create(name="chaco")
category.create(name="weave") category.create(name="weave")
.. index:: roundup-admin; create entries in class
If the database has already been initalised, then you need to use the If the database has already been initalised, then you need to use the
``roundup-admin`` tool:: ``roundup-admin`` tool::
% roundup-admin -i <tracker home> % roundup-admin -i <tracker home>
Roundup <version> ready for input. Roundup <version> ready for input.
Type "help" for help. Type "help" for help.
roundup> create category name=scipy roundup> create category name=scipy
1 1
roundup> create category name=chaco roundup> create category name=chaco
2 2
skipping to change at line 3837 skipping to change at line 4355
You will need to grant "Creation" permission to the users who are You will need to grant "Creation" permission to the users who are
allowed to add timelog entries. You may do this with:: allowed to add timelog entries. You may do this with::
db.security.addPermissionToRole('User', 'Create', 'timelog') db.security.addPermissionToRole('User', 'Create', 'timelog')
db.security.addPermissionToRole('User', 'View', 'timelog') db.security.addPermissionToRole('User', 'View', 'timelog')
If users are also able to *edit* timelog entries, then also include:: If users are also able to *edit* timelog entries, then also include::
db.security.addPermissionToRole('User', 'Edit', 'timelog') db.security.addPermissionToRole('User', 'Edit', 'timelog')
.. index:: schema; example changes
2. Link to the new class from your issue class (again, in 2. Link to the new class from your issue class (again, in
``schema.py``):: ``schema.py``)::
issue = IssueClass(db, "issue", issue = IssueClass(db, "issue",
assignedto=Link("user"), keyword=Multilink("keyword"), assignedto=Link("user"), keyword=Multilink("keyword"),
priority=Link("priority"), status=Link("status"), priority=Link("priority"), status=Link("status"),
times=Multilink("timelog")) times=Multilink("timelog"))
the "times" property is the new link to the "timelog" class. the "times" property is the new link to the "timelog" class.
skipping to change at line 3929 skipping to change at line 4449
</tr> </tr>
</table> </table>
I put this just above the Messages log in my issue display. Note our I put this just above the Messages log in my issue display. Note our
use of the ``totalTimeSpent`` method which will total up the times use of the ``totalTimeSpent`` method which will total up the times
for the issue and return a new Interval. That will be automatically for the issue and return a new Interval. That will be automatically
displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours
and 40 minutes). and 40 minutes).
6. If you're using a persistent web server - ``roundup-server`` or 6. If you're using a persistent web server - ``roundup-server`` or
``mod_python`` for example - then you'll need to restart that to pick up ``mod_wsgi`` for example - then you'll need to restart that to pick up
the code changes. When that's done, you'll be able to use the new the code changes. When that's done, you'll be able to use the new
time logging interface. time logging interface.
An extension of this modification attaches the timelog entries to any An extension of this modification attaches the timelog entries to any
change message entered at the time of the timelog entry: change message entered at the time of the timelog entry:
A. Add a link to the timelog to the msg class in ``schema.py``: A. Add a link to the timelog to the msg class in ``schema.py``:
msg = FileClass(db, "msg", msg = FileClass(db, "msg",
author=Link("user", do_journal='no'), author=Link("user", do_journal='no'),
skipping to change at line 4281 skipping to change at line 4801
def verifyPassword(self, userid, password): def verifyPassword(self, userid, password):
''' Verify the password that the user has supplied ''' Verify the password that the user has supplied
''' '''
# look up some unique LDAP information about the user # look up some unique LDAP information about the user
username = self.db.user.get(self.userid, 'username') username = self.db.user.get(self.userid, 'username')
# now verify the password supplied against the LDAP store # now verify the password supplied against the LDAP store
Changes to Tracker Behaviour Changes to Tracker Behaviour
---------------------------- ----------------------------
.. index:: single: auditors; how to register (example)
single: reactors; how to register (example)
Preventing SPAM Preventing SPAM
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
The following detector code may be installed in your tracker's The following detector code may be installed in your tracker's
``detectors`` directory. It will block any messages being created that ``detectors`` directory. It will block any messages being created that
have HTML attachments (a very common vector for spam and phishing) have HTML attachments (a very common vector for spam and phishing)
and any messages that have more than 2 HTTP URLs in them. Just copy and any messages that have more than 2 HTTP URLs in them. Just copy
the following into ``detectors/anti_spam.py`` in your tracker:: the following into ``detectors/anti_spam.py`` in your tracker::
from roundup.exceptions import Reject from roundup.exceptions import Reject
skipping to change at line 4801 skipping to change at line 5324
The code could also be changed to only trigger on the ``create()`` The code could also be changed to only trigger on the ``create()``
event, rather than also on the ``set()`` event, thus only setting event, rather than also on the ``set()`` event, thus only setting
the nosy list when the issue is created. the nosy list when the issue is created.
Scalability Scalability
In the auditor, there is a loop over all users. For a site with In the auditor, there is a loop over all users. For a site with
only few users this will pose no serious problem; however, with only few users this will pose no serious problem; however, with
many users this will be a serious performance bottleneck. many users this will be a serious performance bottleneck.
A way out would be to link from the keywords to the users who A way out would be to link from the keywords to the users who
selected these keywords as nosy keywords. This will eliminate the selected these keywords as nosy keywords. This will eliminate the
loop over all users. loop over all users. See the ``rev_multilink`` attribute to make
this easier.
Restricting updates that arrive by email Restricting updates that arrive by email
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Roundup supports multiple update methods: Roundup supports multiple update methods:
1. command line 1. command line
2. plain email 2. plain email
3. pgp signed email 3. pgp signed email
4. web access 4. web access
skipping to change at line 4838 skipping to change at line 5362
from roundup.mailgw import Unauthorized from roundup.mailgw import Unauthorized
def restrict_nosy_changes(db, cl, nodeid, newvalues): def restrict_nosy_changes(db, cl, nodeid, newvalues):
'''Do not permit changes to nosy via email.''' '''Do not permit changes to nosy via email.'''
if 'nosy' not in newvalues: if 'nosy' not in newvalues:
# the nosy field has not changed so no need to check. # the nosy field has not changed so no need to check.
return return
if db.tx_Source in ['web', 'email-sig-openpgp', 'cli' ]: if db.tx_Source in ['web', 'rest', 'xmlrpc', 'email-sig-openpgp', 'cli' ] :
# if the source of the transaction is from an authenticated # if the source of the transaction is from an authenticated
# source or a privileged process allow the transaction. # source or a privileged process allow the transaction.
# Other possible sources: 'email' # Other possible sources: 'email'
return return
# otherwise raise an error # otherwise raise an error
raise Unauthorized( \ raise Unauthorized( \
'Changes to nosy property not allowed via %s for this issue.'%\ 'Changes to nosy property not allowed via %s for this issue.'%\
tx_Source) tx_Source)
skipping to change at line 5275 skipping to change at line 5799
return return
# everything's ok, move on to the next page # everything's ok, move on to the next page
self.client.template = 'add_page2' self.client.template = 'add_page2'
def init(instance): def init(instance):
instance.registerAction('page1_submit', Page1SubmitAction) instance.registerAction('page1_submit', Page1SubmitAction)
4. Use the usual "new" action as the ``@action`` on the final page, and 4. Use the usual "new" action as the ``@action`` on the final page, and
you're done (the standard context/submit method can do this for you). you're done (the standard context/submit method can do this for you).
Silent Submit
~~~~~~~~~~~~~
When working on an issue, most of the time the people on the nosy list
need to be notified of changes. There are cases where a user wants to
add a comment to an issue and not bother other users on the nosy
list.
This feature is called Silent Submit because it allows the user to
silently modify an issue and not tell anyone.
There are several parts to this change. The main activity part
involves editing the stock detectors/nosyreaction.py file in your
tracker. Insert the following lines near the top of the nosyreaction
function::
# Did user click button to do a silent change?
try:
if db.web['submit'] == "silent_change":
return
except (AttributeError, KeyError) as err:
# The web attribute or submit key don't exist.
# That's fine. We were probably triggered by an email
# or cli based change.
pass
This checks the submit button to see if it is the silent type. If there
are exceptions trying to make that determination they are ignored and
processing continues. You may wonder how db.web gets set. This is done
by creating an extension. Add the file extensions/edit.py with
this content::
from roundup.cgi.actions import EditItemAction
class Edit2Action(EditItemAction):
def handle(self):
self.db.web = {} # create the dict
# populate the dict by getting the value of the submit_button
# element from the form.
self.db.web['submit'] = self.form['submit_button'].value
# call the core EditItemAction to process the edit.
EditItemAction.handle(self)
def init(instance):
'''Override the default edit action with this new version'''
instance.registerAction('edit', Edit2Action)
This code is a wrapper for the roundup EditItemAction. It checks the
form's submit button to save the value element. The rest of the changes
needed for the Silent Submit feature involves editing
html/issue.item.html to add the silent submit button. In
the stock issue.item.html the submit button is on a line that contains
"submit button". Replace that line with something like the following::
<input type="submit" name="submit_button"
tal:condition="context/is_edit_ok"
value="Submit Changes">&nbsp;
<button type="submit" name="submit_button"
tal:condition="context/is_edit_ok"
title="Click this to submit but not send nosy email."
value="silent_change" i18n:translate="">
Silent Change</button>
Note the difference in the value attribute for the two submit buttons.
The value "silent_change" in the button specification must match the
string in the nosy reaction function.
Debugging Trackers Debugging Trackers
================== ==================
There are three switches in tracker configs that turn on debugging in There are three switches in tracker configs that turn on debugging in
Roundup: Roundup:
1. web :: debug 1. web :: debug
2. mail :: debug 2. mail :: debug
3. logging :: level 3. logging :: level
See the config.ini file or the `tracker configuration`_ section above for See the config.ini file or the `tracker configuration`_ section above for
more information. more information.
Additionally, the ``roundup-server.py`` script has its own debugging mode Additionally, the ``roundup-server.py`` script has its own debugging mode
in which it reloads edited templates immediately when they are changed, in which it reloads edited templates immediately when they are changed,
rather than requiring a web server restart. rather than requiring a web server restart.
.. _`design documentation`: design.html .. _`design documentation`: design.html
.. _`developer's guide`: developers.html .. _`developer's guide`: developers.html
.. _`rest interface documentation`: rest.html#programming-the-rest-api
.. _`directions in the rest interface documentation`: rest.html#enabling-the-res
t-api
.. _`xmlrpc interface documentation`: xmlrpc.html#through-roundup
.. _`zxcvbn`: https://github.com/dwolfhub/zxcvbn-python
 End of changes. 69 change blocks. 
54 lines changed or deleted 649 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)