Mercurial > p > roundup > code
comparison doc/reference.txt @ 7464:82bbb95e5690 issue2550923_computed_property
merge from tip into issue2550923_computed_property
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Thu, 08 Jun 2023 00:10:32 -0400 |
| parents | b4ac699c6282 |
| children | cdb81976b8bc |
comparison
equal
deleted
inserted
replaced
| 7045:ca90f7270cd4 | 7464:82bbb95e5690 |
|---|---|
| 1 .. meta:: | |
| 2 :description: | |
| 3 Reference for the internals of Roundup. Includes background | |
| 4 information for cookbook and how-to examples. | |
| 5 Reference for the design and internals needed to understand | |
| 6 and extend the examples to meet new needs. | |
| 7 | |
| 8 :tocdepth: 2 | |
| 9 | |
| 10 ================= | |
| 11 Roundup Reference | |
| 12 ================= | |
| 13 | |
| 14 .. admonition:: Welcome | |
| 15 | |
| 16 This document used to be part of the `customization document`_. The | |
| 17 customization document was getting large and unwieldy. It was a | |
| 18 combination of examples and internal information that made finding | |
| 19 information difficult. We often had questions on the mailing list that | |
| 20 were well answered in the customization document, but finding the info | |
| 21 was difficult. | |
| 22 | |
| 23 The documentation is slowly being reorganized using the `Diataxis | |
| 24 framework`_. Help with the reorganization is welcome. | |
| 25 | |
| 26 This is the document you should look at for background on a tutorial | |
| 27 or how-to guide in the customization document. | |
| 28 | |
| 29 .. _customization document: customizing.html | |
| 30 .. _diataxis framework: https://diataxis.fr/ | |
| 31 | |
| 32 .. This document borrows from the ZopeBook section on ZPT. The original is at: | |
| 33 http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx | |
| 34 | |
| 35 .. contents:: | |
| 36 :depth: 2 | |
| 37 :local: | |
| 38 | |
| 39 Trackers in a Nutshell | |
| 40 ====================== | |
| 41 | |
| 42 Trackers have the following structure: | |
| 43 | |
| 44 .. index:: | |
| 45 single: tracker; structure db directory | |
| 46 single: tracker; structure detectors directory | |
| 47 single: tracker; structure extensions directory | |
| 48 single: tracker; structure html directory | |
| 49 single: tracker; structure html directory | |
| 50 single: tracker; structure lib directory | |
| 51 | |
| 52 =================== ======================================================== | |
| 53 Tracker File Description | |
| 54 =================== ======================================================== | |
| 55 config.ini Holds the basic `tracker configuration`_ | |
| 56 schema.py Holds the `tracker schema`_ | |
| 57 initial_data.py Holds any data to be entered into the database when the | |
| 58 tracker is initialised (optional) | |
| 59 interfaces.py Allows `modifying the core of Roundup`_ (optional) | |
| 60 db/ Holds the tracker's database | |
| 61 db/files/ Holds the tracker's upload files and messages | |
| 62 db/backend_name Names the database back-end for the tracker (obsolete). | |
| 63 Use the ``backend`` setting in the ``[rdbms]`` | |
| 64 section of ``config.ini`` instead. | |
| 65 detectors/ `Auditors and reactors`_ for this tracker | |
| 66 extensions/ Additional `actions`_ and `templating utilities`_ | |
| 67 html/ Web interface templates, images and style sheets | |
| 68 lib/ optional common imports for detectors and extensions | |
| 69 =================== ======================================================== | |
| 70 | |
| 71 | |
| 72 .. index:: config.ini | |
| 73 .. index:: configuration; see config.ini | |
| 74 | |
| 75 Tracker Configuration | |
| 76 ===================== | |
| 77 | |
| 78 The ``config.ini`` located in your tracker home contains the basic | |
| 79 configuration for the web and e-mail components of Roundup's interfaces. | |
| 80 | |
| 81 Changes to the data captured by your tracker is controlled by the `tracker | |
| 82 schema`_. Some configuration is also performed using permissions - see the | |
| 83 `security / access controls`_ section. For example, to allow users to | |
| 84 automatically register through the email interface, you must grant the | |
| 85 "Anonymous" Role the "Email Access" Permission. | |
| 86 | |
| 87 .. index:: | |
| 88 single: config.ini; sections | |
| 89 see: configuration; config.ini | |
| 90 | |
| 91 The following is taken from the `Python Library Reference`__ (July 18, 2018) | |
| 92 section "ConfigParser -- Configuration file parser": | |
| 93 | |
| 94 The configuration file consists of sections, led by a [section] header | |
| 95 and followed by name: value entries, with continuations in the style | |
| 96 of RFC 822 (see section 3.1.1, “LONG HEADER FIELDS”); name=value is | |
| 97 also accepted. Note that leading whitespace is removed from | |
| 98 values. The optional values can contain format strings which refer to | |
| 99 other values in the same section, or values in a special DEFAULT | |
| 100 section. Additional defaults can be provided on initialization and | |
| 101 retrieval. Lines beginning with '#' or ';' are ignored and may be | |
| 102 used to provide comments. | |
| 103 | |
| 104 For example:: | |
| 105 | |
| 106 [My Section] | |
| 107 foodir = %(dir)s/whatever | |
| 108 dir = frob | |
| 109 | |
| 110 would resolve the "%(dir)s" to the value of "dir" ("frob" in this case) | |
| 111 resulting in "foodir" being "frob/whatever". | |
| 112 | |
| 113 __ https://docs.python.org/2/library/configparser.html | |
| 114 | |
| 115 Example configuration settings are below. This is a partial | |
| 116 list. Documentation on all the settings is included in the | |
| 117 ``config.ini`` file. | |
| 118 | |
| 119 .. .comment out. file generation needs more work include:: tracker_config.txt | |
| 120 | |
| 121 .. index:: config.ini; sections main | |
| 122 | |
| 123 Section **main** | |
| 124 database -- ``db`` | |
| 125 Database directory path. The path may be either absolute or relative | |
| 126 to the directory containig this config file. | |
| 127 | |
| 128 templates -- ``html`` | |
| 129 Path to the HTML templates directory. The path may be either absolute | |
| 130 or relative to the directory containing this config file. | |
| 131 | |
| 132 static_files -- default *blank* | |
| 133 A list of space separated directory paths (or a single directory). | |
| 134 These directories hold additional static files available via Web UI. | |
| 135 These directories may contain sitewide images, CSS stylesheets etc. If | |
| 136 a '-' is included, the list processing ends and the TEMPLATES | |
| 137 directory is not searched after the specified directories. If this | |
| 138 option is not set, all static files are taken from the TEMPLATES | |
| 139 directory. | |
| 140 | |
| 141 admin_email -- ``roundup-admin`` | |
| 142 Email address that roundup will complain to if it runs into trouble. If | |
| 143 the email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined | |
| 144 below is used. | |
| 145 | |
| 146 dispatcher_email -- ``roundup-admin`` | |
| 147 The 'dispatcher' is a role that can get notified of new items to the | |
| 148 database. It is used by the ERROR_MESSAGES_TO config setting. If the | |
| 149 email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined | |
| 150 below is used. | |
| 151 | |
| 152 email_from_tag -- default *blank* | |
| 153 Additional text to include in the "name" part of the From: address used | |
| 154 in nosy messages. If the sending user is "Foo Bar", the From: line | |
| 155 is usually: ``"Foo Bar" <issue_tracker@tracker.example>`` | |
| 156 the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so: | |
| 157 ``"Foo Bar EMAIL_FROM_TAG" <issue_tracker@tracker.example>`` | |
| 158 | |
| 159 new_web_user_roles -- ``User`` | |
| 160 Roles that a user gets when they register with Web User Interface. | |
| 161 This is a comma-separated list of role names (e.g. ``Admin,User``). | |
| 162 | |
| 163 new_email_user_roles -- ``User`` | |
| 164 Roles that a user gets when they register with Email Gateway. | |
| 165 This is a comma-separated string of role names (e.g. ``Admin,User``). | |
| 166 | |
| 167 error_messages_to -- ``user`` | |
| 168 Send error message emails to the ``dispatcher``, ``user``, or ``both``? | |
| 169 The dispatcher is configured using the DISPATCHER_EMAIL setting. | |
| 170 Allowed values: ``dispatcher``, ``user``, or ``both`` | |
| 171 | |
| 172 html_version -- ``html4`` | |
| 173 HTML version to generate. The templates are ``html4`` by default. | |
| 174 If you wish to make them xhtml, then you'll need to change this | |
| 175 var to ``xhtml`` too so all auto-generated HTML is compliant. | |
| 176 Allowed values: ``html4``, ``xhtml`` | |
| 177 | |
| 178 timezone -- ``0`` | |
| 179 Numeric timezone offset used when users do not choose their own | |
| 180 in their settings. | |
| 181 | |
| 182 instant_registration -- ``yes`` | |
| 183 Register new users instantly, or require confirmation via | |
| 184 email? | |
| 185 Allowed values: ``yes``, ``no`` | |
| 186 | |
| 187 email_registration_confirmation -- ``yes`` | |
| 188 Offer registration confirmation by email or only through the web? | |
| 189 Allowed values: ``yes``, ``no`` | |
| 190 | |
| 191 indexer_stopwords -- default *blank* | |
| 192 Additional stop-words for the full-text indexer specific to | |
| 193 your tracker. See the indexer source for the default list of | |
| 194 stop-words (e.g. ``A,AND,ARE,AS,AT,BE,BUT,BY, ...``). | |
| 195 | |
| 196 umask -- ``02`` | |
| 197 Defines the file creation mode mask. | |
| 198 | |
| 199 csv_field_size -- ``131072`` | |
| 200 Maximum size of a csv-field during import. Roundup's export | |
| 201 format is a csv (comma separated values) variant. The csv | |
| 202 reader has a limit on the size of individual fields | |
| 203 starting with python 2.5. Set this to a higher value if you | |
| 204 get the error 'Error: field larger than field limit' during | |
| 205 import. | |
| 206 | |
| 207 .. index:: config.ini; sections tracker | |
| 208 | |
| 209 Section **tracker** | |
| 210 name -- ``Roundup issue tracker`` | |
| 211 A descriptive name for your Roundup instance. | |
| 212 | |
| 213 web -- ``http://host.example/demo/`` | |
| 214 The web address that the tracker is viewable at. | |
| 215 This will be included in information sent to users of the tracker. | |
| 216 The URL MUST include the cgi-bin part or anything else | |
| 217 that is required to get to the home page of the tracker. | |
| 218 You MUST include a trailing '/' in the URL. | |
| 219 | |
| 220 email -- ``issue_tracker`` | |
| 221 Email address that mail to Roundup should go to. | |
| 222 | |
| 223 language -- default *blank* | |
| 224 Default locale name for this tracker. If this option is not set, the | |
| 225 language is determined by the environment variable LANGUAGE, LC_ALL, | |
| 226 LC_MESSAGES, or LANG, in that order of preference. | |
| 227 | |
| 228 .. index:: config.ini; sections web | |
| 229 | |
| 230 Section **web** | |
| 231 allow_html_file -- ``no`` | |
| 232 Setting this option enables Roundup to serve uploaded HTML | |
| 233 file content *as HTML*. This is a potential security risk | |
| 234 and is therefore disabled by default. Set to 'yes' if you | |
| 235 trust *all* users uploading content to your tracker. | |
| 236 | |
| 237 http_auth -- ``yes`` | |
| 238 Whether to use HTTP Basic Authentication, if present. | |
| 239 Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION | |
| 240 variables supplied by your web server (in that order). | |
| 241 Set this option to 'no' if you do not wish to use HTTP Basic | |
| 242 Authentication in your web interface. | |
| 243 | |
| 244 use_browser_language -- ``yes`` | |
| 245 Whether to use HTTP Accept-Language, if present. | |
| 246 Browsers send a language-region preference list. | |
| 247 It's usually set in the client's browser or in their | |
| 248 Operating System. | |
| 249 Set this option to 'no' if you want to ignore it. | |
| 250 | |
| 251 debug -- ``no`` | |
| 252 Setting this option makes Roundup display error tracebacks | |
| 253 in the user's browser rather than emailing them to the | |
| 254 tracker admin."), | |
| 255 | |
| 256 .. index:: config.ini; sections rdbms | |
| 257 single: config.ini; database settings | |
| 258 | |
| 259 Section **rdbms** | |
| 260 Settings in this section are used to set the backend and configure | |
| 261 addition settings needed by RDBMs like SQLite, Postgresql and | |
| 262 MySQL backends. | |
| 263 | |
| 264 .. index:: | |
| 265 single: postgres; select backend in config.ini | |
| 266 single: mysql; select backend in config.ini | |
| 267 single: sqlite; select backend in config.ini | |
| 268 single: anydbm; select backend in config.ini | |
| 269 see: database; postgres | |
| 270 see: database; mysql | |
| 271 see: database; sqlite | |
| 272 see: database; anydbm | |
| 273 | |
| 274 backend -- set to value by init | |
| 275 The database backend such as anydbm, sqlite, mysql or postgres. | |
| 276 | |
| 277 name -- ``roundup`` | |
| 278 Name of the database to use. | |
| 279 | |
| 280 host -- ``localhost`` | |
| 281 Database server host. | |
| 282 | |
| 283 port -- default *blank* | |
| 284 TCP port number of the database server. Postgresql usually resides on | |
| 285 port 5432 (if any), for MySQL default port number is 3306. Leave this | |
| 286 option empty to use backend default. | |
| 287 | |
| 288 user -- ``roundup`` | |
| 289 Database user name that Roundup should use. | |
| 290 | |
| 291 password -- ``roundup`` | |
| 292 Database user password. | |
| 293 | |
| 294 read_default_file -- ``~/.my.cnf`` | |
| 295 Name of the MySQL defaults file. Only used in MySQL connections. | |
| 296 | |
| 297 read_default_group -- ``roundup`` | |
| 298 Name of the group to use in the MySQL defaults file. Only used in | |
| 299 MySQL connections. | |
| 300 | |
| 301 .. index:: | |
| 302 single: sqlite; lock timeout | |
| 303 | |
| 304 sqlite_timeout -- ``30`` | |
| 305 Number of seconds to wait when the SQLite database is locked. | |
| 306 Used only for SQLite. | |
| 307 | |
| 308 cache_size -- `100` | |
| 309 Size of the node cache (in elements) used to keep most recently used | |
| 310 data in memory. | |
| 311 | |
| 312 .. index:: config.ini; sections logging | |
| 313 see: logging; config.ini, sections logging | |
| 314 | |
| 315 Section **logging** | |
| 316 config -- default *blank* | |
| 317 Path to configuration file for standard Python logging module. If this | |
| 318 option is set, logging configuration is loaded from specified file; | |
| 319 options 'filename' and 'level' in this section are ignored. The path may | |
| 320 be either absolute or relative to the directory containig this config file. | |
| 321 | |
| 322 filename -- default *blank* | |
| 323 Log file name for minimal logging facility built into Roundup. If no file | |
| 324 name specified, log messages are written on stderr. If above 'config' | |
| 325 option is set, this option has no effect. The path may be either absolute | |
| 326 or relative to the directory containig this config file. | |
| 327 | |
| 328 level -- ``ERROR`` | |
| 329 Minimal severity level of messages written to log file. If above 'config' | |
| 330 option is set, this option has no effect. | |
| 331 Allowed values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR`` | |
| 332 | |
| 333 .. index:: config.ini; sections mail | |
| 334 | |
| 335 Section **mail** | |
| 336 Outgoing email options. Used for nosy messages, password reset and | |
| 337 registration approval requests. | |
| 338 | |
| 339 domain -- ``localhost`` | |
| 340 Domain name used for email addresses. | |
| 341 | |
| 342 host -- default *blank* | |
| 343 SMTP mail host that Roundup will use to send mail | |
| 344 | |
| 345 username -- default *blank* | |
| 346 SMTP login name. Set this if your mail host requires authenticated access. | |
| 347 If username is not empty, password (below) MUST be set! | |
| 348 | |
| 349 password -- default *blank* | |
| 350 SMTP login password. | |
| 351 Set this if your mail host requires authenticated access. | |
| 352 | |
| 353 port -- default *25* | |
| 354 SMTP port on mail host. | |
| 355 Set this if your mail host runs on a different port. | |
| 356 | |
| 357 local_hostname -- default *blank* | |
| 358 The fully qualified domain name (FQDN) to use during SMTP sessions. If left | |
| 359 blank, the underlying SMTP library will attempt to detect your FQDN. If your | |
| 360 mail host requires something specific, specify the FQDN to use. | |
| 361 | |
| 362 tls -- ``no`` | |
| 363 If your SMTP mail host provides or requires TLS (Transport Layer Security) | |
| 364 then you may set this option to 'yes'. | |
| 365 Allowed values: ``yes``, ``no`` | |
| 366 | |
| 367 tls_keyfile -- default *blank* | |
| 368 If TLS is used, you may set this option to the name of a PEM formatted | |
| 369 file that contains your private key. The path may be either absolute or | |
| 370 relative to the directory containig this config file. | |
| 371 | |
| 372 tls_certfile -- default *blank* | |
| 373 If TLS is used, you may set this option to the name of a PEM formatted | |
| 374 certificate chain file. The path may be either absolute or relative | |
| 375 to the directory containig this config file. | |
| 376 | |
| 377 charset -- utf-8 | |
| 378 Character set to encode email headers with. We use utf-8 by default, as | |
| 379 it's the most flexible. Some mail readers (eg. Eudora) can't cope with | |
| 380 that, so you might need to specify a more limited character set | |
| 381 (eg. iso-8859-1). | |
| 382 | |
| 383 debug -- default *blank* | |
| 384 Setting this option makes Roundup to write all outgoing email messages | |
| 385 to this file *instead* of sending them. This option has the same effect | |
| 386 as environment variable SENDMAILDEBUG. Environment variable takes | |
| 387 precedence. The path may be either absolute or relative to the directory | |
| 388 containig this config file. | |
| 389 | |
| 390 add_authorinfo -- ``yes`` | |
| 391 Add a line with author information at top of all messages send by | |
| 392 Roundup. | |
| 393 | |
| 394 add_authoremail -- ``yes`` | |
| 395 Add the mail address of the author to the author information at the | |
| 396 top of all messages. If this is false but add_authorinfo is true, | |
| 397 only the name of the actor is added which protects the mail address | |
| 398 of the actor from being exposed at mail archives, etc. | |
| 399 | |
| 400 .. index:: config.ini; sections mailgw | |
| 401 single: mailgw; config | |
| 402 see: mail gateway; mailgw | |
| 403 | |
| 404 Section **mailgw** | |
| 405 Roundup Mail Gateway options | |
| 406 | |
| 407 keep_quoted_text -- ``yes`` | |
| 408 Keep email citations when accepting messages. Setting this to ``no`` strips | |
| 409 out "quoted" text from the message. Signatures are also stripped. | |
| 410 Allowed values: ``yes``, ``no`` | |
| 411 | |
| 412 leave_body_unchanged -- ``no`` | |
| 413 Preserve the email body as is - that is, keep the citations *and* | |
| 414 signatures. | |
| 415 Allowed values: ``yes``, ``no`` | |
| 416 | |
| 417 default_class -- ``issue`` | |
| 418 Default class to use in the mailgw if one isn't supplied in email subjects. | |
| 419 To disable, leave the value blank. | |
| 420 | |
| 421 language -- default *blank* | |
| 422 Default locale name for the tracker mail gateway. If this option is | |
| 423 not set, mail gateway will use the language of the tracker instance. | |
| 424 | |
| 425 subject_prefix_parsing -- ``strict`` | |
| 426 Controls the parsing of the [prefix] on subject lines in incoming emails. | |
| 427 ``strict`` will return an error to the sender if the [prefix] is not | |
| 428 recognised. ``loose`` will attempt to parse the [prefix] but just | |
| 429 pass it through as part of the issue title if not recognised. ``none`` | |
| 430 will always pass any [prefix] through as part of the issue title. | |
| 431 | |
| 432 subject_suffix_parsing -- ``strict`` | |
| 433 Controls the parsing of the [suffix] on subject lines in incoming emails. | |
| 434 ``strict`` will return an error to the sender if the [suffix] is not | |
| 435 recognised. ``loose`` will attempt to parse the [suffix] but just | |
| 436 pass it through as part of the issue title if not recognised. ``none`` | |
| 437 will always pass any [suffix] through as part of the issue title. | |
| 438 | |
| 439 subject_suffix_delimiters -- ``[]`` | |
| 440 Defines the brackets used for delimiting the commands suffix in a subject | |
| 441 line. | |
| 442 | |
| 443 subject_content_match -- ``always`` | |
| 444 Controls matching of the incoming email subject line against issue titles | |
| 445 in the case where there is no designator [prefix]. ``never`` turns off | |
| 446 matching. ``creation + interval`` or ``activity + interval`` will match | |
| 447 an issue for the interval after the issue's creation or last activity. | |
| 448 The interval is a standard Roundup interval. | |
| 449 | |
| 450 subject_updates_title -- ``yes`` | |
| 451 Update issue title if incoming subject of email is different. | |
| 452 Setting this to ``no`` will ignore the title part of | |
| 453 the subject of incoming email messages. | |
| 454 | |
| 455 refwd_re -- ``(\s*\W?\s*(fw|fwd|re|aw|sv|ang)\W)+`` | |
| 456 Regular expression matching a single reply or forward prefix | |
| 457 prepended by the mailer. This is explicitly stripped from the | |
| 458 subject during parsing. Value is Python Regular Expression | |
| 459 (UTF8-encoded). | |
| 460 | |
| 461 origmsg_re -- `` ^[>|\s]*-----\s?Original Message\s?-----$`` | |
| 462 Regular expression matching start of an original message if quoted | |
| 463 in the body. Value is Python Regular Expression (UTF8-encoded). | |
| 464 | |
| 465 sign_re -- ``^[>|\s]*-- ?$`` | |
| 466 Regular expression matching the start of a signature in the message | |
| 467 body. Value is Python Regular Expression (UTF8-encoded). | |
| 468 | |
| 469 eol_re -- ``[\r\n]+`` | |
| 470 Regular expression matching end of line. Value is Python Regular | |
| 471 Expression (UTF8-encoded). | |
| 472 | |
| 473 blankline_re -- ``[\r\n]+\s*[\r\n]+`` | |
| 474 Regular expression matching a blank line. Value is Python Regular | |
| 475 Expression (UTF8-encoded). | |
| 476 | |
| 477 ignore_alternatives -- ``no`` | |
| 478 When parsing incoming mails, Roundup uses the first | |
| 479 text/plain part it finds. If this part is inside a | |
| 480 multipart/alternative, and this option is set, all other | |
| 481 parts of the multipart/alternative are ignored. The default | |
| 482 is to keep all parts and attach them to the issue. | |
| 483 | |
| 484 .. index:: config.ini; sections php | |
| 485 | |
| 486 Section **pgp** | |
| 487 OpenPGP mail processing options | |
| 488 | |
| 489 enable -- ``no`` | |
| 490 Enable PGP processing. Requires gpg. | |
| 491 | |
| 492 roles -- default *blank* | |
| 493 If specified, a comma-separated list of roles to perform PGP | |
| 494 processing on. If not specified, it happens for all users. | |
| 495 | |
| 496 homedir -- default *blank* | |
| 497 Location of PGP directory. Defaults to $HOME/.gnupg if not | |
| 498 specified. | |
| 499 | |
| 500 | |
| 501 .. index:: config.ini; sections nosy | |
| 502 | |
| 503 Section **nosy** | |
| 504 Nosy messages sending | |
| 505 | |
| 506 messages_to_author -- ``no`` | |
| 507 Send nosy messages to the author of the message. | |
| 508 If ``yes`` is used, then messages are sent to the author | |
| 509 even if not on the nosy list, same for ``new`` (but only for new messages). | |
| 510 When set to ``nosy``, the nosy list controls sending messages to the author. | |
| 511 Allowed values: ``yes``, ``no``, ``new``, ``nosy`` | |
| 512 | |
| 513 signature_position -- ``bottom`` | |
| 514 Where to place the email signature. | |
| 515 Allowed values: ``top``, ``bottom``, ``none`` | |
| 516 | |
| 517 add_author -- ``new`` | |
| 518 Does the author of a message get placed on the nosy list automatically? | |
| 519 If ``new`` is used, then the author will only be added when a message | |
| 520 creates a new issue. If ``yes``, then the author will be added on | |
| 521 followups too. If ``no``, they're never added to the nosy. | |
| 522 Allowed values: ``yes``, ``no``, ``new`` | |
| 523 | |
| 524 add_recipients -- ``new`` | |
| 525 Do the recipients (``To:``, ``Cc:``) of a message get placed on the nosy | |
| 526 list? If ``new`` is used, then the recipients will only be added when a | |
| 527 message creates a new issue. If ``yes``, then the recipients will be added | |
| 528 on followups too. If ``no``, they're never added to the nosy. | |
| 529 Allowed values: ``yes``, ``no``, ``new`` | |
| 530 | |
| 531 email_sending -- ``single`` | |
| 532 Controls the email sending from the nosy reactor. If ``multiple`` then | |
| 533 a separate email is sent to each recipient. If ``single`` then a single | |
| 534 email is sent with each recipient as a CC address. | |
| 535 | |
| 536 max_attachment_size -- ``2147483647`` | |
| 537 Attachments larger than the given number of bytes won't be attached | |
| 538 to nosy mails. They will be replaced by a link to the tracker's | |
| 539 download page for the file. | |
| 540 | |
| 541 | |
| 542 .. index:: single: roundup-admin; config.ini update | |
| 543 single: roundup-admin; config.ini create | |
| 544 single: config.ini; create | |
| 545 single: config.ini; update | |
| 546 | |
| 547 You may generate a new default config file using the ``roundup-admin | |
| 548 genconfig`` command. You can generate a new config file merging in | |
| 549 existing settings using the ``roundup-admin updateconfig`` command. | |
| 550 | |
| 551 Configuration variables may be referred to in lower or upper case. In code, | |
| 552 variables not in the "main" section are referred to using their section and | |
| 553 name, so "domain" in the section "mail" becomes MAIL_DOMAIN. | |
| 554 | |
| 555 .. index:: pair: configuration; extensions | |
| 556 pair: configuration; detectors | |
| 557 | |
| 558 Extending the configuration file | |
| 559 -------------------------------- | |
| 560 | |
| 561 You can't add new variables to the config.ini file in the tracker home but | |
| 562 you can add two new config.ini files: | |
| 563 | |
| 564 - a config.ini in the ``extensions`` directory will be loaded and attached | |
| 565 to the config variable as "ext". | |
| 566 - a config.ini in the ``detectors`` directory will be loaded and attached | |
| 567 to the config variable as "detectors". | |
| 568 | |
| 569 For example, the following in ``detectors/config.ini``:: | |
| 570 | |
| 571 [main] | |
| 572 qa_recipients = email@example.com | |
| 573 | |
| 574 is accessible as:: | |
| 575 | |
| 576 db.config.detectors['QA_RECIPIENTS'] | |
| 577 | |
| 578 Note that the name grouping applied to the main configuration file is | |
| 579 applied to the extension config files, so if you instead have:: | |
| 580 | |
| 581 [qa] | |
| 582 recipients = email@example.com | |
| 583 | |
| 584 then the above ``db.config.detectors['QA_RECIPIENTS']`` will still work. | |
| 585 | |
| 586 Unlike values in the tracker's main ``config.ini``, the values defined | |
| 587 in these config files are not validated. For example: a setting that | |
| 588 is supposed to be an integer value (e.g. 4) could be the word | |
| 589 "foo". If you are writing Python code that uses these settings, you | |
| 590 should expect to handle invalid values. | |
| 591 | |
| 592 Also, incorrect values aren't discovered until the config setting is | |
| 593 used. This can be long after the tracker is started and the error may | |
| 594 not be seen in the logs. | |
| 595 | |
| 596 It is possible to validate these settings. Validation involves calling | |
| 597 the ``update_options`` method on the configuration option. This can be | |
| 598 done from the ``init()`` function in the Python files implementing | |
| 599 extensions_ or detectors_. | |
| 600 | |
| 601 As an example, adding the following to an extension:: | |
| 602 | |
| 603 from roundup.configuration import SecretMandatoryOption | |
| 604 | |
| 605 def init(instance): | |
| 606 instance.config.ext.update_option('RECAPTCHA_SECRET', | |
| 607 SecretMandatoryOption,description="Secret securing reCaptcha.") | |
| 608 | |
| 609 similarly for a detector:: | |
| 610 | |
| 611 from roundup.configuration import MailAddressOption | |
| 612 | |
| 613 def init(db): | |
| 614 try: | |
| 615 db.config.detectors.update_option('QA_RECIPIENTS', | |
| 616 MailAddressOption, | |
| 617 description="Email used for QA comment followup.") | |
| 618 except KeyError: | |
| 619 # COMMENT_EMAIL setting is not found, but it's optional | |
| 620 # so continue | |
| 621 pass | |
| 622 | |
| 623 will allow reading the secret from a file or append the tracker domain | |
| 624 to an email address if it does not have a domain. | |
| 625 | |
| 626 Running ``roundup-admin -i tracker_home display user1`` will validate | |
| 627 the settings for both config.ini`s. Otherwise detector options are not | |
| 628 validated until the first request to the web interface (or email | |
| 629 gateway). | |
| 630 | |
| 631 There are 4 arguments for ``update_option``: | |
| 632 | |
| 633 1. config setting name - string (positional, mandatory) | |
| 634 2. option type - Option derived class from configuration.py | |
| 635 (positional, mandatory) | |
| 636 3. default value - string (optional, named default) | |
| 637 4. description - string (optional, named description) | |
| 638 | |
| 639 The first argument is the config setting name as described at the | |
| 640 beginning of this section. | |
| 641 | |
| 642 The second argument is a class in the roundup.configuration module. | |
| 643 There are a number of these classes: BooleanOption, | |
| 644 IntegerNumberOption, RegExpOption.... Please see the configuration | |
| 645 module for all Option validators and their descriptions. You can also | |
| 646 define your own custom validator in `interfaces.py`_. | |
| 647 | |
| 648 The third and fourth arguments are strings and are optional. They are | |
| 649 printed if there is an error and may help the user correct the problem. | |
| 650 | |
| 651 .. index:: ! schema | |
| 652 | |
| 653 Tracker Schema | |
| 654 ============== | |
| 655 | |
| 656 .. note:: | |
| 657 if you modify the schema, you'll most likely need to edit the | |
| 658 `web interface`_ HTML template files and `detectors`_ to reflect | |
| 659 your changes. | |
| 660 | |
| 661 A tracker schema defines what data is stored in the tracker's database. | |
| 662 Schemas are defined using Python code in the ``schema.py`` module of your | |
| 663 tracker. | |
| 664 | |
| 665 What you can/can't do to the schema | |
| 666 ----------------------------------- | |
| 667 | |
| 668 Your schema may be changed at any time before or after the tracker has been | |
| 669 initialised (or used). You may: | |
| 670 | |
| 671 **Add new properties to classes, or add whole new classes** | |
| 672 This is painless and easy to do - there are generally no repercussions | |
| 673 from adding new information to a tracker's schema. | |
| 674 | |
| 675 **Remove properties** | |
| 676 Removing properties is a little more tricky - you need to make sure that | |
| 677 the property is no longer used in the `web interface`_ *or* by the | |
| 678 detectors_. | |
| 679 | |
| 680 You must never: | |
| 681 | |
| 682 **Remove the user class** | |
| 683 This class is the only *required* class in Roundup. | |
| 684 | |
| 685 **Remove the "username", "address", "password" or "realname" user properties** | |
| 686 Various parts of Roundup require these properties. Don't remove them. | |
| 687 | |
| 688 **Change the type of a property** | |
| 689 Property types must *never* [1]_ be changed - the database simply | |
| 690 doesn't take this kind of action into account. Note that you can't | |
| 691 just remove a property and re-add it as a new type either. If you | |
| 692 wanted to make the assignedto property a Multilink, you'd need to | |
| 693 create a new property assignedto_list and remove the old assignedto | |
| 694 property. | |
| 695 | |
| 696 .. [1] If you shut down the tracker, `export the database`_, modify the | |
| 697 exported csv property data to be compatible with the new type, | |
| 698 change the property type in the schema, and finally import the | |
| 699 changed exported data, you can change the property type. It is | |
| 700 not trivial nor for the faint of heart. But it can be done. | |
| 701 | |
| 702 .. _export the database: admin_guide.html#using-roundup-admin | |
| 703 | |
| 704 The ``schema.py`` and ``initial_data.py`` modules | |
| 705 ------------------------------------------------- | |
| 706 | |
| 707 The schema.py module is used to define what your tracker looks like | |
| 708 on the inside, the schema of the tracker. It defines the Classes | |
| 709 and properties on each class. It also defines the security for | |
| 710 those Classes. The next few sections describe how schemas work | |
| 711 and what you can do with them. | |
| 712 | |
| 713 The initial_data.py module sets up the initial state of your | |
| 714 tracker. It’s called exactly once - by the ``roundup-admin initialise`` | |
| 715 command. See the start of the section on `database content`_ for more | |
| 716 info about how this works. | |
| 717 | |
| 718 .. index:: schema; classic - description of | |
| 719 | |
| 720 The "classic" schema | |
| 721 -------------------- | |
| 722 | |
| 723 The "classic" schema looks like this (see section `setkey(property)`_ | |
| 724 below for the meaning of ``'setkey'`` -- you may also want to look into | |
| 725 the sections `setlabelprop(property)`_ and `setorderprop(property)`_ for | |
| 726 specifying (default) labelling and ordering of classes.):: | |
| 727 | |
| 728 pri = Class(db, "priority", name=String(), order=String()) | |
| 729 pri.setkey("name") | |
| 730 | |
| 731 stat = Class(db, "status", name=String(), order=String()) | |
| 732 stat.setkey("name") | |
| 733 | |
| 734 keyword = Class(db, "keyword", name=String()) | |
| 735 keyword.setkey("name") | |
| 736 | |
| 737 user = Class(db, "user", username=String(), organisation=String(), | |
| 738 password=String(), address=String(), realname=String(), | |
| 739 phone=String(), alternate_addresses=String(), | |
| 740 queries=Multilink('query'), roles=String(), timezone=String()) | |
| 741 user.setkey("username") | |
| 742 | |
| 743 msg = FileClass(db, "msg", author=Link("user"), summary=String(), | |
| 744 date=Date(), recipients=Multilink("user"), | |
| 745 files=Multilink("file"), messageid=String(), inreplyto=String()) | |
| 746 | |
| 747 file = FileClass(db, "file", name=String()) | |
| 748 | |
| 749 issue = IssueClass(db, "issue", keyword=Multilink("keyword"), | |
| 750 status=Link("status"), assignedto=Link("user"), | |
| 751 priority=Link("priority")) | |
| 752 issue.setkey('title') | |
| 753 | |
| 754 .. index:: schema; allowed changes | |
| 755 | |
| 756 Classes and Properties - creating a new information store | |
| 757 --------------------------------------------------------- | |
| 758 | |
| 759 In the tracker above, we've defined 7 classes of information: | |
| 760 | |
| 761 priority | |
| 762 Defines the possible levels of urgency for issues. | |
| 763 | |
| 764 status | |
| 765 Defines the possible states of processing the issue may be in. | |
| 766 | |
| 767 keyword | |
| 768 Initially empty, will hold keywords useful for searching issues. | |
| 769 | |
| 770 user | |
| 771 Initially holding the "admin" user, will eventually have an entry | |
| 772 for all users using Roundup. | |
| 773 | |
| 774 msg | |
| 775 Initially empty, will hold all e-mail messages sent to or | |
| 776 generated by Roundup. | |
| 777 | |
| 778 file | |
| 779 Initially empty, will hold all files attached to issues. | |
| 780 | |
| 781 issue | |
| 782 Initially empty, this is where the issue information is stored. | |
| 783 | |
| 784 We define the "priority" and "status" classes to allow two things: | |
| 785 | |
| 786 1. reduction in the amount of information stored on the issue | |
| 787 2. more powerful, accurate searching of issues by priority and status | |
| 788 | |
| 789 By only requiring a link on the issue (which is stored as a single | |
| 790 number) we reduce the chance that someone mis-types a priority or | |
| 791 status - or simply makes a new one up. | |
| 792 | |
| 793 Class names are used to access items of that class in the `REST api`_ | |
| 794 interface. The classic tracker was created before the REST interface | |
| 795 was added. It uses the single form (i.e. issue and user not issues and | |
| 796 users) for its classes. Most REST documentation suggests using plural | |
| 797 forms. However, to make your API consistent, use singular forms for | |
| 798 classes that you add. | |
| 799 | |
| 800 Class and Items | |
| 801 ~~~~~~~~~~~~~~~ | |
| 802 | |
| 803 A Class defines a particular class (or type) of data that will be stored | |
| 804 in the database. A class comprises one or more properties, which gives | |
| 805 the information about the class items. | |
| 806 | |
| 807 The actual data entered into the database, using ``class.create()``, are | |
| 808 called items. They have a special immutable property called ``'id'``. We | |
| 809 sometimes refer to this as the *itemid*. | |
| 810 | |
| 811 | |
| 812 .. index:: schema; property types | |
| 813 | |
| 814 Properties | |
| 815 ~~~~~~~~~~ | |
| 816 | |
| 817 A Class is comprised of one or more properties of the following types: | |
| 818 | |
| 819 String | |
| 820 properties are for storing arbitrary-length strings. | |
| 821 Password | |
| 822 properties are for storing encoded arbitrary-length strings. | |
| 823 The default encoding is defined on the ``roundup.password.Password`` | |
| 824 class. | |
| 825 Date | |
| 826 properties store date-and-time stamps. Their values are Timestamp | |
| 827 objects. | |
| 828 Interval | |
| 829 properties store time periods rather than absolute dates. For | |
| 830 example 2 hours. | |
| 831 Integer | |
| 832 properties store integer values. (Number can store real/float values.) | |
| 833 Number | |
| 834 properties store numeric values. There is an option to use | |
| 835 double-precision floating point numbers. | |
| 836 Boolean | |
| 837 properties store on/off, yes/no, true/false values. | |
| 838 Link | |
| 839 properties refers to a single other item selected from a | |
| 840 specified class. The class is part of the property; the value is an | |
| 841 integer, the id of the chosen item. | |
| 842 Multilink | |
| 843 properties refer to possibly many items in a specified | |
| 844 class. The value is a list of integers. | |
| 845 | |
| 846 Properties can have additional attributes to change the default | |
| 847 behaviour: | |
| 848 | |
| 849 .. index:: triple: schema; property attributes; required | |
| 850 triple: schema; property attributes; default_value | |
| 851 triple: schema; property attributes; quiet | |
| 852 | |
| 853 * All properties support the following attributes: | |
| 854 | |
| 855 - ``required``: see `design documentation`_. Adds the property to | |
| 856 the list returned by calling get_required_props for the class. | |
| 857 - ``default_value``: see `design documentation`_ Sets the default | |
| 858 value if the property is not set. | |
| 859 - ``quiet``: see `design documentation`_. Suppresses user visible | |
| 860 to changes to this property. The property change is not reported: | |
| 861 | |
| 862 - in the change feedback/confirmation message in the web | |
| 863 interface | |
| 864 - the property change section of the nosy email | |
| 865 - the web history at the bottom of an item's page | |
| 866 | |
| 867 This can be used to store state of the user interface (e.g. the | |
| 868 names of elements that are collapsed or hidden from the | |
| 869 user). Making properties that are updated as an indirect result of | |
| 870 a user's change (e.g. updating a blockers property, counting | |
| 871 number of times an issue was reopened or reassigned etc.) should | |
| 872 not be displayed to the user as they can be confusing. | |
| 873 | |
| 874 .. index:: triple: schema; property attributes; indexme | |
| 875 | |
| 876 * String properties can have an ``indexme`` attribute that defines if the | |
| 877 property should be part of the full text index. The default is 'no' but this | |
| 878 can be set to 'yes' to allow a property's contents to be in the full | |
| 879 text index. | |
| 880 | |
| 881 .. index:: triple: schema; property attributes; use_double | |
| 882 | |
| 883 * Number properties can have a ``use_double`` attribute that, when set | |
| 884 to ``True``, will use double precision floating point in the database. | |
| 885 * Link and Multilink properties can have several attributes: | |
| 886 | |
| 887 .. index:: triple: schema; property attributes; do_journal | |
| 888 | |
| 889 - ``do_journal``: By default, every change of a link property is | |
| 890 recorded in the item being linked to (or being unlinked). A typical | |
| 891 use-case for setting ``do_journal='no'`` would be to turn off | |
| 892 journalling of nosy list, message author and message recipient link | |
| 893 and unlink events to prevent the journal from clogged with these | |
| 894 events. | |
| 895 | |
| 896 .. index:: triple: schema; property attributes; try_id_parsing | |
| 897 | |
| 898 - ``try_id_parsing`` is turned on by default. If entering a number | |
| 899 into a Link or Multilink field, Roundup interprets this number as an | |
| 900 ID of the item to link to. Sometimes items can have numeric names | |
| 901 (like, e.g., product codes). For these Roundup needs to match the | |
| 902 numeric name and should never match an ID. In this case you can set | |
| 903 ``try_id_parsing='no'``. | |
| 904 | |
| 905 .. index:: triple: schema; property attributes; rev_multilink | |
| 906 | |
| 907 - The ``rev_multilink`` option takes a property name to be inserted | |
| 908 into the linked-to class. This property is a Multilink property that | |
| 909 links back to the current class. The new Multilink is read-only (it | |
| 910 is automatically modified if the Link or Multilink property defining | |
| 911 it is modified). The new property can be used in normal searches | |
| 912 using the "filter" method of the Class. This means it can be used | |
| 913 like other Multilink properties when searching (in an index | |
| 914 template) or via the REST and XMLRPC APIs. | |
| 915 | |
| 916 As a example, suppose you want to group multiple issues into a | |
| 917 super issue. Each issue can be part of only one super issue. It is | |
| 918 inefficient to find all of the issues that are part of the | |
| 919 super issue by searching through all issues in the system looking | |
| 920 at the part_of link property. To make this more efficient, you | |
| 921 can declare an issue's part_of property as:: | |
| 922 | |
| 923 issue = IssueClass(db, "issue", | |
| 924 ... | |
| 925 part_of = Link("issue", rev_multilink="components"), | |
| 926 ... ) | |
| 927 | |
| 928 This automatically creates the ``components`` multilink on the issue | |
| 929 class. The ``components`` multilink is never explicitly declared in | |
| 930 the issue class, but it has the same effect as though you had | |
| 931 declared the class as:: | |
| 932 | |
| 933 issue = IssueClass(db, "issue", | |
| 934 ... | |
| 935 part_of = Link("issue"), | |
| 936 components = Multilink("issue"), | |
| 937 ... ) | |
| 938 | |
| 939 Then wrote a detector to update the components property on the | |
| 940 corresponding issue. Writing this detector can be tricky. There is | |
| 941 one other difference, you can not explicitly set/modify the | |
| 942 ``components`` multilink. | |
| 943 | |
| 944 The effect of setting ``part_of = 3456`` on issue1234 | |
| 945 automatically adds "1234" to the ``components`` property on | |
| 946 issue3456. You can search the ``components`` multilink just like a | |
| 947 regular multilink, but you can't explicitly assign to it. | |
| 948 Another difference of reverse multilinks to normal multilinks | |
| 949 is that when a linked node is retired, the node vanishes from the | |
| 950 multilink, e.g. in the example above, if an issue with ``part_of`` | |
| 951 set to another issue is retired this issue vanishes from the | |
| 952 ``components`` multilink of the other issue. | |
| 953 | |
| 954 You can also link between different classes. So you can modify | |
| 955 the issue definition to include:: | |
| 956 | |
| 957 issue = IssueClass(db, "issue", | |
| 958 ... | |
| 959 assigned_to = Link("user", rev_multilink="responsibleFor"), | |
| 960 ... ) | |
| 961 | |
| 962 This makes it easy to list all issues that the user is responsible | |
| 963 for (aka assigned_to). | |
| 964 | |
| 965 .. index:: triple: schema; property attributes; msg_header_property | |
| 966 | |
| 967 - The ``msg_header_property`` is used by the mail gateway when sending | |
| 968 out messages. When a link or multilink property of an issue changes, | |
| 969 Roundup creates email headers of the form:: | |
| 970 | |
| 971 X-Roundup-issue-prop: value | |
| 972 | |
| 973 where ``value`` is the ``name`` property for the linked item(s). | |
| 974 For example, if you have a multilink for attached_files in your | |
| 975 issue, you will see a header:: | |
| 976 | |
| 977 X-Roundup-issue-attached_files: MySpecialFile.doc, HisResume.txt | |
| 978 | |
| 979 when the class for attached files is defined as:: | |
| 980 | |
| 981 file = FileClass(db, "file", name=String()) | |
| 982 | |
| 983 ``MySpecialFile.doc`` is the name for the file object. | |
| 984 | |
| 985 If you have an ``assigned_to`` property in your issue class that | |
| 986 links to the user class and you want to add a header:: | |
| 987 | |
| 988 X-Roundup-issue-assigned_to: ... | |
| 989 | |
| 990 so that the mail recipients can filter emails where | |
| 991 ``X-Roundup-issue-assigned_to: name`` that contains their | |
| 992 username. The user class is defined as:: | |
| 993 | |
| 994 user = Class(db, "user", | |
| 995 username=String(), | |
| 996 password=Password(), | |
| 997 address=String(), | |
| 998 realname=String(), | |
| 999 phone=String(), | |
| 1000 organisation=String(), | |
| 1001 alternate_addresses=String(), | |
| 1002 queries=Multilink('query'), | |
| 1003 roles=String(), # comma-separated string of Role names | |
| 1004 timezone=String()) | |
| 1005 | |
| 1006 Because there is no ``name`` parameter for the user class, there | |
| 1007 will be no header. However setting:: | |
| 1008 | |
| 1009 assigned_to=Link("user", msg_header_property="username") | |
| 1010 | |
| 1011 will make the mail gateway generate an ``X-Roundup-issue-assigned_to`` | |
| 1012 using the username property of the linked user. | |
| 1013 | |
| 1014 Assume assigned_to for an issue is linked to the user with | |
| 1015 username=joe_user, setting:: | |
| 1016 | |
| 1017 msg_header_property="username" | |
| 1018 | |
| 1019 for the assigned_to property will generated message headers of the | |
| 1020 form:: | |
| 1021 | |
| 1022 X-Roundup-issue-assigned_to: joe_user | |
| 1023 | |
| 1024 for emails sent on issues where joe_user has been assigned to the issue. | |
| 1025 | |
| 1026 If this property is set to the empty string "", it will prevent | |
| 1027 the header from being generated on outgoing mail. | |
| 1028 | |
| 1029 .. index:: triple: schema; class property; creator | |
| 1030 triple: schema; class property; creation | |
| 1031 triple: schema; class property; actor | |
| 1032 triple: schema; class property; activity | |
| 1033 | |
| 1034 All Classes automatically have a number of properties by default: | |
| 1035 | |
| 1036 *creator* | |
| 1037 Link to the user that created the item. | |
| 1038 *creation* | |
| 1039 Date the item was created. | |
| 1040 *actor* | |
| 1041 Link to the user that last modified the item. | |
| 1042 *activity* | |
| 1043 Date the item was last modified. | |
| 1044 | |
| 1045 | |
| 1046 .. index:: double: schema; class methods | |
| 1047 | |
| 1048 Methods | |
| 1049 ~~~~~~~ | |
| 1050 | |
| 1051 All classes have the following methods. | |
| 1052 | |
| 1053 .. index:: triple: schema; class method; setkey | |
| 1054 | |
| 1055 setkey(property) | |
| 1056 :::::::::::::::: | |
| 1057 | |
| 1058 .. index:: roundup-admin; setting assignedto on an issue | |
| 1059 | |
| 1060 Select a String property of the class to be the key property. The key | |
| 1061 property must be unique, and allows references to the items in the class | |
| 1062 by the content of the key property. That is, we can refer to users by | |
| 1063 their username: for example, let's say that there's an issue in Roundup, | |
| 1064 issue 23. There's also a user, richard, who happens to be user 2. To | |
| 1065 assign an issue to him, we could do either of:: | |
| 1066 | |
| 1067 roundup-admin set issue23 assignedto=2 | |
| 1068 | |
| 1069 or:: | |
| 1070 | |
| 1071 roundup-admin set issue23 assignedto=richard | |
| 1072 | |
| 1073 Note, the same thing can be done in the web and e-mail interfaces. | |
| 1074 | |
| 1075 .. index:: triple: schema; class method; setlabelprop | |
| 1076 | |
| 1077 setlabelprop(property) | |
| 1078 :::::::::::::::::::::: | |
| 1079 | |
| 1080 Select a property of the class to be the label property. The label | |
| 1081 property is used whereever an item should be uniquely identified, e.g., | |
| 1082 when displaying a link to an item. If setlabelprop is not specified for | |
| 1083 a class, the following values are tried for the label: | |
| 1084 | |
| 1085 * the key of the class (see the `setkey(property)`_ section above) | |
| 1086 * the "name" property | |
| 1087 * the "title" property | |
| 1088 * the first property from the sorted property name list | |
| 1089 | |
| 1090 So in most cases you can get away without specifying setlabelprop | |
| 1091 explicitly. | |
| 1092 | |
| 1093 You should make sure that users have View access to this property or | |
| 1094 the id property for a class. If the property can not be viewed by a | |
| 1095 user, looping over items in the class (e.g. messages attached to an | |
| 1096 issue) will not work. | |
| 1097 | |
| 1098 .. index:: triple: schema; class method; setorderprop | |
| 1099 | |
| 1100 setorderprop(property) | |
| 1101 :::::::::::::::::::::: | |
| 1102 | |
| 1103 Select a property of the class to be the order property. The order | |
| 1104 property is used whenever using a default sort order for the class, | |
| 1105 e.g., when grouping or sorting class A by a link to class B in the user | |
| 1106 interface, the order property of class B is used for sorting. If | |
| 1107 setorderprop is not specified for a class, the following values are tried | |
| 1108 for the order property: | |
| 1109 | |
| 1110 * the property named "order" | |
| 1111 * the label property (see `setlabelprop(property)`_ above) | |
| 1112 | |
| 1113 So in most cases you can get away without specifying setorderprop | |
| 1114 explicitly. | |
| 1115 | |
| 1116 .. index:: triple: schema; class method; create | |
| 1117 | |
| 1118 create(information) | |
| 1119 ::::::::::::::::::: | |
| 1120 | |
| 1121 Create an item in the database. This is generally used to create items | |
| 1122 in the :term:`definitional class` like "priority" and "status". | |
| 1123 | |
| 1124 IssueClass | |
| 1125 ~~~~~~~~~~ | |
| 1126 | |
| 1127 IssueClasses automatically include the "messages", "files", "nosy", and | |
| 1128 "superseder" properties. | |
| 1129 | |
| 1130 The messages and files properties list the links to the messages and | |
| 1131 files related to the issue. The nosy property is a list of links to | |
| 1132 users who wish to be informed of changes to the issue - they get "CC'ed" | |
| 1133 e-mails when messages are sent to or generated by the issue. The nosy | |
| 1134 reactor (in the ``'detectors'`` directory) handles this action. The | |
| 1135 superseder link indicates an issue which has superseded this one. | |
| 1136 | |
| 1137 They also have the dynamically generated "creation", "activity" and | |
| 1138 "creator" properties. | |
| 1139 | |
| 1140 The value of the "creation" property is the date when an item was | |
| 1141 created, and the value of the "activity" property is the date when any | |
| 1142 property on the item was last edited (equivalently, these are the dates | |
| 1143 on the first and last records in the item's journal). The "creator" | |
| 1144 property holds a link to the user that created the issue. | |
| 1145 | |
| 1146 .. index:: triple: schema; class property; content | |
| 1147 triple: schema; class property; type | |
| 1148 | |
| 1149 FileClass | |
| 1150 ~~~~~~~~~ | |
| 1151 | |
| 1152 FileClasses save their "content" attribute off in a separate file from | |
| 1153 the rest of the database. This reduces the number of large entries in | |
| 1154 the database, which generally makes databases more efficient, and also | |
| 1155 allows us to use command-line tools to operate on the files. They are | |
| 1156 stored in the files sub-directory of the ``'db'`` directory in your | |
| 1157 tracker. FileClasses also have a "type" attribute to store the MIME | |
| 1158 type of the file. | |
| 1159 | |
| 1160 Roundup by default considers the contents of the file immutable. This | |
| 1161 is to assist in maintaining an accurate record of correspondence. The | |
| 1162 distributed tracker templates do not enforce this. So if you have | |
| 1163 access to the Roundup tracker directory, you can edit the files (make | |
| 1164 sure to preserve mode, owner and group) to remove information (e.g. if | |
| 1165 somebody includes a password or you need to redact proprietary | |
| 1166 information). Obviously the journal for the message/file will not | |
| 1167 report that the file has changed. | |
| 1168 | |
| 1169 Best practice is to remove offending material and leave a | |
| 1170 placeholder. E.G. replace a password with the text:: | |
| 1171 | |
| 1172 [password has been deleted 2020-12-02 --myname] | |
| 1173 | |
| 1174 If you need to delete an entire file, replace the file contents with:: | |
| 1175 | |
| 1176 [file contents deleted due to spam 2020-10-21 --myname] | |
| 1177 | |
| 1178 rather than deleting the file. If you actually delete the file Roundup | |
| 1179 will report an error to the user and email the administrator. If you | |
| 1180 empty the file, a user downloading the file using the direct URL | |
| 1181 (e.g. ``tracker/msg22``) may be confused and think something is broken | |
| 1182 when they receive an empty file. Retiring a file/msg does not prevent | |
| 1183 access to the file using the direct URL. Retiring an item only removes | |
| 1184 it when requesting a list of all items in the class. If you are | |
| 1185 replacing the contents, you probably want to change the content type | |
| 1186 of the file. E.G. from ``image/jpeg`` to ``text/plain``. You can do | |
| 1187 this easily through the web interface, or using the ``roundup-admin`` | |
| 1188 command line interface. | |
| 1189 | |
| 1190 You can also change the contents of a file or message using the REST | |
| 1191 interface. Note that this will NOT result in an entry in the journal, | |
| 1192 so again it allows a silent change. To do this you need to make two | |
| 1193 rest requests. An example using curl is:: | |
| 1194 | |
| 1195 $ curl -u demo:demo -s | |
| 1196 -H "X-requested-with: rest" \ | |
| 1197 -H "Referer: https://tracker.example.com/demo/" \ | |
| 1198 -X GET \ | |
| 1199 https://tracker.example.com/demo/rest/data/file/30/content | |
| 1200 { | |
| 1201 "data": { | |
| 1202 "id": "30", | |
| 1203 "type": "<class 'str'>", | |
| 1204 "link": "https://tracker.example.com/demo/rest/data/file/30/content", | |
| 1205 "data": "hello3", | |
| 1206 "@etag": "\"3f2f8063dbce5b6bd43567e6f4f3c671\"" | |
| 1207 } | |
| 1208 } | |
| 1209 | |
| 1210 using the etag, overwrite the content with:: | |
| 1211 | |
| 1212 $ curl -u demo:demo -s | |
| 1213 -H "X-requested-with: rest" \ | |
| 1214 -H "Referer: https://tracker.example.com/demo/" \ | |
| 1215 -H 'If-Match: "3f2f8063dbce5b6bd43567e6f4f3c671"' \ | |
| 1216 -X PUT \ | |
| 1217 -F "data=@hello" \ | |
| 1218 https://tracker.example.com/demo/rest/data/file/30/content | |
| 1219 | |
| 1220 where ``hello`` is a file on local disk. | |
| 1221 | |
| 1222 You can enforce immutability in your tracker by adding an auditor (see | |
| 1223 detectors_) for the file/msg class that rejects changes to the content | |
| 1224 property. The auditor could also add a journal entry so that a change | |
| 1225 via the Roundup mechanism is reported. Using a mixin (see: | |
| 1226 https://wiki.roundup-tracker.org/MixinClassFileClass) to augment the | |
| 1227 file class allows for other possibilities including signing the file, or | |
| 1228 recording a checksum in the database and validating the file contents | |
| 1229 at the time it gets read. This allows detection of changes done on the | |
| 1230 filesystem outside of the Roundup mechanism. | |
| 1231 | |
| 1232 .. index:: triple: schema; class property; messages | |
| 1233 triple: schema; class property; files | |
| 1234 triple: schema; class property; nosy | |
| 1235 triple: schema; class property; superseder | |
| 1236 | |
| 1237 .. index:: schema; item ordering | |
| 1238 | |
| 1239 A note about ordering | |
| 1240 ~~~~~~~~~~~~~~~~~~~~~ | |
| 1241 | |
| 1242 When we sort items in the hyperdb, we use one of a number of methods, | |
| 1243 depending on the properties being sorted on: | |
| 1244 | |
| 1245 1. If it's a String, Integer, Number, Date or Interval property, we | |
| 1246 just sort the scalar value of the property. Strings are sorted | |
| 1247 case-sensitively. | |
| 1248 2. If it's a Link property, we sort by either the linked item's "order" | |
| 1249 property (if it has one) or the linked item's "id". | |
| 1250 3. Mulitlinks sort similar to #2, but we start with the first Multilink | |
| 1251 list item, and if they're the same, we sort by the second item, and | |
| 1252 so on. | |
| 1253 | |
| 1254 Note that if an "order" property is defined on a Class that is used for | |
| 1255 sorting, all items of that Class *must* have a value against the "order" | |
| 1256 property, or sorting will result in random ordering. | |
| 1257 | |
| 1258 | |
| 1259 Examples of adding to your schema | |
| 1260 --------------------------------- | |
| 1261 | |
| 1262 Some examples are in the :ref:`CustomExamples` section. | |
| 1263 | |
| 1264 Also you can start with `Roundup wiki CategorySchema`_ to see a list | |
| 1265 of additional examples of how schemas can be customised to add new | |
| 1266 functionality. | |
| 1267 | |
| 1268 .. _Roundup wiki CategorySchema: | |
| 1269 https://wiki.roundup-tracker.org/CategorySchema | |
| 1270 | |
| 1271 .. index:: !detectors | |
| 1272 .. _detectors: | |
| 1273 .. _Auditors and reactors: | |
| 1274 | |
| 1275 Detectors - adding behaviour to your tracker | |
| 1276 ============================================ | |
| 1277 | |
| 1278 Detectors are initialised every time you open your tracker database, so | |
| 1279 you're free to add and remove them any time, even after the database is | |
| 1280 initialised via the ``roundup-admin initialise`` command. | |
| 1281 | |
| 1282 The detectors in your tracker fire *before* (**auditors**) and *after* | |
| 1283 (**reactors**) changes to the contents of your database. They are Python | |
| 1284 modules that sit in your tracker's ``detectors`` directory. You will | |
| 1285 have some installed by default - have a look. You can write new | |
| 1286 detectors or modify the existing ones. The existing detectors installed | |
| 1287 for you are: | |
| 1288 | |
| 1289 .. index:: detectors; installed | |
| 1290 | |
| 1291 **nosyreaction.py** | |
| 1292 This provides the automatic nosy list maintenance and email sending. | |
| 1293 The nosy reactor (``nosyreaction``) fires when new messages are added | |
| 1294 to issues. The nosy auditor (``updatenosy``) fires when issues are | |
| 1295 changed, and figures out what changes need to be made to the nosy list | |
| 1296 (such as adding new authors, etc.) | |
| 1297 **statusauditor.py** | |
| 1298 This provides the ``chatty`` auditor which changes the issue status | |
| 1299 from ``unread`` or ``closed`` to ``chatting`` if new messages appear. | |
| 1300 It also provides the ``presetunread`` auditor which pre-sets the | |
| 1301 status to ``unread`` on new items if the status isn't explicitly | |
| 1302 defined. | |
| 1303 **messagesummary.py** | |
| 1304 Generates the ``summary`` property for new messages based on the message | |
| 1305 content. | |
| 1306 **userauditor.py** | |
| 1307 Verifies the content of some of the user fields (email addresses and | |
| 1308 roles lists). | |
| 1309 | |
| 1310 If you don't want this default behaviour, you're completely free to change | |
| 1311 or remove these detectors. | |
| 1312 | |
| 1313 See the detectors section in the `design document`__ for details of the | |
| 1314 interface for detectors. | |
| 1315 | |
| 1316 __ design.html | |
| 1317 | |
| 1318 | |
| 1319 .. index:: detectors; writing api | |
| 1320 | |
| 1321 Detector API | |
| 1322 ------------ | |
| 1323 | |
| 1324 .. index:: pair: detectors; auditors | |
| 1325 single: auditors; function signature | |
| 1326 single: auditors; defining | |
| 1327 single: auditors; arguments | |
| 1328 | |
| 1329 Auditors are called with the arguments:: | |
| 1330 | |
| 1331 audit(db, cl, itemid, newdata) | |
| 1332 | |
| 1333 where ``db`` is the database, ``cl`` is an instance of Class or | |
| 1334 IssueClass within the database, and ``newdata`` is a dictionary mapping | |
| 1335 property names to values. | |
| 1336 | |
| 1337 For a ``create()`` operation, the ``itemid`` argument is None and | |
| 1338 newdata contains all of the initial property values with which the item | |
| 1339 is about to be created. | |
| 1340 | |
| 1341 For a ``set()`` operation, newdata contains only the names and values of | |
| 1342 properties that are about to be changed. | |
| 1343 | |
| 1344 For a ``retire()`` or ``restore()`` operation, newdata is None. | |
| 1345 | |
| 1346 .. index:: pair: detectors; reactor | |
| 1347 single: reactors; function signature | |
| 1348 single: reactors; defining | |
| 1349 single: reactors; arguments | |
| 1350 | |
| 1351 Reactors are called with the arguments:: | |
| 1352 | |
| 1353 react(db, cl, itemid, olddata) | |
| 1354 | |
| 1355 where ``db`` is the database, ``cl`` is an instance of Class or | |
| 1356 IssueClass within the database, and ``olddata`` is a dictionary mapping | |
| 1357 property names to values. | |
| 1358 | |
| 1359 For a ``create()`` operation, the ``itemid`` argument is the id of the | |
| 1360 newly-created item and ``olddata`` is None. | |
| 1361 | |
| 1362 For a ``set()`` operation, ``olddata`` contains the names and previous | |
| 1363 values of properties that were changed. | |
| 1364 | |
| 1365 For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of | |
| 1366 the retired or restored item and ``olddata`` is None. | |
| 1367 | |
| 1368 .. index:: detectors; additional | |
| 1369 | |
| 1370 Additional Detectors Ready For Use | |
| 1371 ---------------------------------- | |
| 1372 | |
| 1373 Sample additional detectors that have been found useful will appear in | |
| 1374 the ``'detectors'`` directory of the Roundup distribution. If you want | |
| 1375 to use one, copy it to the ``'detectors'`` of your tracker instance: | |
| 1376 | |
| 1377 **irker.py** | |
| 1378 This detector sends notification on IRC through an irker daemon | |
| 1379 (http://www.catb.org/esr/irker/) when issues are created or messages | |
| 1380 are added. In order to use it you need to install irker, start the | |
| 1381 irkerd daemon, and add an ``[irker]`` section in ``detectors/config.ini`` | |
| 1382 that contains a comma-separated list of channels where the messages should | |
| 1383 be sent, e.g. ``channels = irc://chat.freenode.net/channelname``. | |
| 1384 **newissuecopy.py** | |
| 1385 This detector sends an email to a team address whenever a new issue is | |
| 1386 created. The address is hard-coded into the detector, so edit it | |
| 1387 before you use it (look for the text 'team@team.host') or you'll get | |
| 1388 email errors! | |
| 1389 **creator_resolution.py** | |
| 1390 Catch attempts to set the status to "resolved" - if the assignedto | |
| 1391 user isn't the creator, then set the status to "confirm-done". Note that | |
| 1392 "classic" Roundup doesn't have that status, so you'll have to add it. If | |
| 1393 you don't want to though, it'll just use "in-progress" instead. | |
| 1394 **email_auditor.py** | |
| 1395 If a file added to an issue is of type message/rfc822, we tack on the | |
| 1396 extension .eml. | |
| 1397 The reason for this is that Microsoft Internet Explorer will not open | |
| 1398 things with a .eml attachment, as they deem it 'unsafe'. Worse yet, | |
| 1399 they'll just give you an incomprehensible error message. For more | |
| 1400 information, see the detector code - it has a lengthy explanation. | |
| 1401 | |
| 1402 | |
| 1403 .. index:: auditors; rules for use | |
| 1404 single: reactors; rules for use | |
| 1405 | |
| 1406 Auditor or Reactor? | |
| 1407 ------------------- | |
| 1408 | |
| 1409 Generally speaking, the following rules should be observed: | |
| 1410 | |
| 1411 **Auditors** | |
| 1412 Are used for `vetoing creation of or changes to items`_. They might | |
| 1413 also make automatic changes to item properties. | |
| 1414 **Reactors** | |
| 1415 Detect changes in the database and react accordingly. They should avoid | |
| 1416 making changes to the database where possible, as this could create | |
| 1417 detector loops. | |
| 1418 | |
| 1419 | |
| 1420 Vetoing creation of or changes to items | |
| 1421 --------------------------------------- | |
| 1422 | |
| 1423 Auditors may raise the ``Reject`` exception to prevent the creation of | |
| 1424 or changes to items in the database. The mail gateway, for example, will | |
| 1425 not attach files or messages to issues when the creation of those files or | |
| 1426 messages are prevented through the ``Reject`` exception. It'll also not create | |
| 1427 users if that creation is ``Reject``'ed too. | |
| 1428 | |
| 1429 To use, simply add at the top of your auditor:: | |
| 1430 | |
| 1431 from roundup.exceptions import Reject | |
| 1432 | |
| 1433 And then when your rejection criteria have been detected, simply:: | |
| 1434 | |
| 1435 raise Reject('Description of error') | |
| 1436 | |
| 1437 Error messages raised with ``Reject`` automatically have any HTML content | |
| 1438 escaped before being displayed to the user. To display an error message to the | |
| 1439 user without performing any HTML escaping the ``RejectRaw`` should be used. All | |
| 1440 security implications should be carefully considering before using | |
| 1441 ``RejectRaw``. | |
| 1442 | |
| 1443 | |
| 1444 Generating email from Roundup | |
| 1445 ----------------------------- | |
| 1446 | |
| 1447 The module ``roundup.mailer`` contains most of the nuts-n-bolts required | |
| 1448 to generate email messages from Roundup. | |
| 1449 | |
| 1450 In addition, the ``IssueClass`` methods ``nosymessage()`` and | |
| 1451 ``send_message()`` are used to generate nosy messages, and may generate | |
| 1452 messages which only consist of a change note (ie. the message id parameter | |
| 1453 is not required - this is referred to as a "System Message" because it | |
| 1454 comes from "the system" and not a user). | |
| 1455 | |
| 1456 | |
| 1457 .. index:: extensions | |
| 1458 .. index:: extensions; add python functions to tracker | |
| 1459 .. _extensions: | |
| 1460 .. _actions: | |
| 1461 | |
| 1462 Extensions - adding capabilities to your tracker | |
| 1463 ================================================ | |
| 1464 | |
| 1465 While detectors_ add new behavior by reacting to changes in tracked | |
| 1466 objects, `extensions` add new actions and utilities to Roundup, which | |
| 1467 are mostly used to enhance web interface. | |
| 1468 | |
| 1469 You can create an extension by creating Python file in your tracker | |
| 1470 ``extensions`` directory. All files from this dir are loaded when | |
| 1471 tracker instance is created, at which point it calls ``init(instance)`` | |
| 1472 from each file supplying itself as a first argument. | |
| 1473 | |
| 1474 Note that at this point web interface is not loaded, but extensions still | |
| 1475 can register actions for in tracker instance. This may be fixed in | |
| 1476 Roundup 1.6 by introducing ``init_web(client)`` callback or a more | |
| 1477 flexible extension point mechanism. | |
| 1478 | |
| 1479 * ``instance.registerUtil`` is used for adding `templating utilities`_ | |
| 1480 (see `adding a time log to your issues | |
| 1481 <customizing.html#adding-a-time-log-to-your-issues-4>`_ for an example) | |
| 1482 | |
| 1483 * ``instance.registerAction`` is used to add more actions to instance | |
| 1484 and to web interface. See `Defining new web actions`_ for details. | |
| 1485 Generic action can be added by inheriting from ``action.Action`` | |
| 1486 instead of ``cgi.action.Action``. | |
| 1487 | |
| 1488 .. _interfaces.py: | |
| 1489 .. _modifying the core of Roundup: | |
| 1490 | |
| 1491 interfaces.py - hooking into the core of Roundup | |
| 1492 ================================================ | |
| 1493 | |
| 1494 There is a magic trick for hooking into the core of Roundup. Using | |
| 1495 this you can: | |
| 1496 | |
| 1497 * modify class data structures | |
| 1498 * monkey patch core code to add new functionality | |
| 1499 * modify the email gateway | |
| 1500 * add new rest endpoints | |
| 1501 * enable experimental or future features | |
| 1502 | |
| 1503 but with great power comes great responsibility. | |
| 1504 | |
| 1505 Interfaces.py has been around since the earliest releases of Roundup | |
| 1506 and used to be the main way to get a lot of customization done. In | |
| 1507 modern Roundup, the extensions_ mechanism is used to `add actions | |
| 1508 <#defining-new-web-actions>`_ and `templating utilities`_. But there are | |
| 1509 places where interfaces.py is still useful. Note that the tracker | |
| 1510 directories are not on the Python system path when interfaces.py is | |
| 1511 evaluated. You need to add library directories explictly by | |
| 1512 modifying sys.path. | |
| 1513 | |
| 1514 See `Changing How the Core Code Works | |
| 1515 <customizing.html#changing-how-the-core-code-works>`_ for examples. | |
| 1516 | |
| 1517 Database Content | |
| 1518 ================ | |
| 1519 | |
| 1520 .. note:: | |
| 1521 If you modify the content of a :term:`definitional class`, you will | |
| 1522 need to edit the tracker `detectors`_ if they reference a value of | |
| 1523 a definitional class. (E.g. if a detector checks to see if an issue | |
| 1524 has a status of "open", and you change the "open" definition to be | |
| 1525 "working", you need to change the check.) | |
| 1526 | |
| 1527 Customisation of the special :term:`definitional classes <definitional | |
| 1528 class>` (eg. status, | |
| 1529 priority, resolution, ...) may be done either before or after the | |
| 1530 tracker is initialised. The actual method of doing so is completely | |
| 1531 different in each case though, so be careful to use the right one. | |
| 1532 | |
| 1533 **Changing content before tracker initialisation** | |
| 1534 Edit the initial_data.py module in your tracker to alter the items | |
| 1535 created using the ``create( ... )`` methods. | |
| 1536 | |
| 1537 **Changing content after tracker initialisation** | |
| 1538 As the "admin" user, click on the "class list" link in the web | |
| 1539 interface to bring up a list of all database classes. Click on the | |
| 1540 name of the class you wish to change the content of. | |
| 1541 | |
| 1542 You may also use the ``roundup-admin`` interface's create, set and | |
| 1543 retire methods to add, alter or remove items from the classes in | |
| 1544 question. | |
| 1545 | |
| 1546 See "`adding a new field to the classic schema | |
| 1547 <customizing.html#adding-a-new-field-to-the-classic-schema>`_" for an | |
| 1548 example that requires database content changes. | |
| 1549 | |
| 1550 | |
| 1551 Security / Access Controls | |
| 1552 ========================== | |
| 1553 | |
| 1554 A set of Permissions is built into the security module by default. For | |
| 1555 each Class defined in the tracker schema, the following permissions | |
| 1556 are defined: | |
| 1557 | |
| 1558 - Create (everything) | |
| 1559 - Edit (everything) | |
| 1560 - Search (everything) - (used if View does not permit access) | |
| 1561 - Retire (everything) | |
| 1562 - Restore (everything) | |
| 1563 - View (everything) | |
| 1564 | |
| 1565 All of these are assigned to the "Admin" Role by default for every | |
| 1566 class. They allow a user to do anything. The web and email interfaces | |
| 1567 also define: | |
| 1568 | |
| 1569 Email Access | |
| 1570 If defined, the user may use the email interface. Used by default to deny | |
| 1571 Anonymous users access to the email interface. When granted to the | |
| 1572 Anonymous user, they will be automatically registered by the email | |
| 1573 interface (see also the ``new_email_user_roles`` configuration option). | |
| 1574 | |
| 1575 Web Access | |
| 1576 If defined, the user may use the web interface. This is usually | |
| 1577 assigned to the Anonymous role as well to allow authorized users to | |
| 1578 access the form based login. If some other authorization mode (basic | |
| 1579 auth, SSO, etc.) is used Web Access can be removed from the | |
| 1580 Anonymous user. | |
| 1581 | |
| 1582 Web Roles | |
| 1583 Controls user access to editing the "roles" property of the "user" class. | |
| 1584 TODO: deprecate in favour of a property-based control. | |
| 1585 | |
| 1586 Rest Access |br| Xmlrpc Access | |
| 1587 These control access to the Rest and Xmlrpc endpoints. The Admin and User | |
| 1588 roles have these by default in the classic tracker. See the | |
| 1589 `directions in the rest interface documentation`_ and the | |
| 1590 `xmlrpc interface documentation`_. | |
| 1591 | |
| 1592 Register | |
| 1593 This is assigned to the anonymous user and allows automatic user | |
| 1594 registration by email or web. | |
| 1595 | |
| 1596 These are hooked into the default Roles: | |
| 1597 | |
| 1598 - Admin (Create, Edit, Retire, Restore, Search, View for everything; Web Roles) | |
| 1599 - User (Web Access; Email Access) | |
| 1600 - Anonymous (Web Access) | |
| 1601 | |
| 1602 Finally, the "admin" user gets the "Admin" Role, and the "anonymous" | |
| 1603 user gets the "Anonymous" Role assigned when the tracker is installed. | |
| 1604 | |
| 1605 For the "User" Role, the "classic" tracker defines: | |
| 1606 | |
| 1607 - Create, Edit and View issue, file, msg, query, keyword | |
| 1608 - View priority, status | |
| 1609 - View user | |
| 1610 - Edit their own user record | |
| 1611 | |
| 1612 And the "Anonymous" Role is defined as: | |
| 1613 | |
| 1614 - Web interface access | |
| 1615 - Register user (for registration) | |
| 1616 - View issue, file, msg, query, keyword, priority, status | |
| 1617 | |
| 1618 Put together, these settings appear in the tracker's ``schema.py`` file:: | |
| 1619 | |
| 1620 # | |
| 1621 # TRACKER SECURITY SETTINGS | |
| 1622 # | |
| 1623 # See the configuration and customisation document for information | |
| 1624 # about security setup. | |
| 1625 | |
| 1626 # | |
| 1627 # REGULAR USERS | |
| 1628 # | |
| 1629 # Give the regular users access to the web and email interface | |
| 1630 db.security.addPermissionToRole('User', 'Web Access') | |
| 1631 db.security.addPermissionToRole('User', 'Email Access') | |
| 1632 db.security.addPermissionToRole('User', 'Rest Access') | |
| 1633 db.security.addPermissionToRole('User', 'Xmlrpc Access') | |
| 1634 | |
| 1635 # Assign the access and edit Permissions for issue, file and message | |
| 1636 # to regular users now | |
| 1637 for cl in 'issue', 'file', 'msg', 'keyword': | |
| 1638 db.security.addPermissionToRole('User', 'View', cl) | |
| 1639 db.security.addPermissionToRole('User', 'Edit', cl) | |
| 1640 db.security.addPermissionToRole('User', 'Create', cl) | |
| 1641 for cl in 'priority', 'status': | |
| 1642 db.security.addPermissionToRole('User', 'View', cl) | |
| 1643 | |
| 1644 # May users view other user information? Comment these lines out | |
| 1645 # if you don't want them to | |
| 1646 p = db.security.addPermission(name='View', klass='user', | |
| 1647 properties=('id', 'organisation', 'phone', 'realname', 'timezone', | |
| 1648 'username')) | |
| 1649 db.security.addPermissionToRole('User', p) | |
| 1650 | |
| 1651 # Users should be able to edit their own details -- this permission is | |
| 1652 # limited to only the situation where the Viewed or Edited item is their own. | |
| 1653 def own_record(db, userid, itemid, **ctx): | |
| 1654 '''Determine whether the userid matches the item being accessed.''' | |
| 1655 return userid == itemid | |
| 1656 p = db.security.addPermission(name='View', klass='user', check=own_record, | |
| 1657 description="User is allowed to view their own user details") | |
| 1658 db.security.addPermissionToRole('User', p) | |
| 1659 p = db.security.addPermission(name='Edit', klass='user', check=own_record, | |
| 1660 properties=('username', 'password', 'address', 'realname', 'phone', | |
| 1661 'organisation', 'alternate_addresses', 'queries', 'timezone'), | |
| 1662 description="User is allowed to edit their own user details") | |
| 1663 db.security.addPermissionToRole('User', p) | |
| 1664 | |
| 1665 # Users should be able to edit and view their own queries. They should also | |
| 1666 # be able to view any marked as not private. They should not be able to | |
| 1667 # edit others' queries, even if they're not private | |
| 1668 def view_query(db, userid, itemid): | |
| 1669 private_for = db.query.get(itemid, 'private_for') | |
| 1670 if not private_for: return True | |
| 1671 return userid == private_for | |
| 1672 def edit_query(db, userid, itemid): | |
| 1673 return userid == db.query.get(itemid, 'creator') | |
| 1674 p = db.security.addPermission(name='View', klass='query', check=view_query, | |
| 1675 description="User is allowed to view their own and public queries") | |
| 1676 db.security.addPermissionToRole('User', p) | |
| 1677 p = db.security.addPermission(name='Search', klass='query') | |
| 1678 db.security.addPermissionToRole('User', p) | |
| 1679 p = db.security.addPermission(name='Edit', klass='query', check=edit_query, | |
| 1680 description="User is allowed to edit their queries") | |
| 1681 db.security.addPermissionToRole('User', p) | |
| 1682 p = db.security.addPermission(name='Retire', klass='query', check=edit_query, | |
| 1683 description="User is allowed to retire their queries") | |
| 1684 db.security.addPermissionToRole('User', p) | |
| 1685 p = db.security.addPermission(name='Restore', klass='query', check=edit_query, | |
| 1686 description="User is allowed to restore their queries") | |
| 1687 db.security.addPermissionToRole('User', p) | |
| 1688 p = db.security.addPermission(name='Create', klass='query', | |
| 1689 description="User is allowed to create queries") | |
| 1690 db.security.addPermissionToRole('User', p) | |
| 1691 | |
| 1692 # | |
| 1693 # ANONYMOUS USER PERMISSIONS | |
| 1694 # | |
| 1695 # Let anonymous users access the web interface. Note that almost all | |
| 1696 # trackers will need this Permission. The only situation where it's not | |
| 1697 # required is in a tracker that uses an HTTP Basic Authenticated front-end. | |
| 1698 db.security.addPermissionToRole('Anonymous', 'Web Access') | |
| 1699 | |
| 1700 # Let anonymous users access the email interface (note that this implies | |
| 1701 # that they will be registered automatically, hence they will need the | |
| 1702 # "Create" user Permission below) | |
| 1703 # This is disabled by default to stop spam from auto-registering users on | |
| 1704 # public trackers. | |
| 1705 #db.security.addPermissionToRole('Anonymous', 'Email Access') | |
| 1706 | |
| 1707 # Assign the appropriate permissions to the anonymous user's Anonymous | |
| 1708 # Role. Choices here are: | |
| 1709 # - Allow anonymous users to register | |
| 1710 db.security.addPermissionToRole('Anonymous', 'Register', 'user') | |
| 1711 | |
| 1712 # Allow anonymous users access to view issues (and the related, linked | |
| 1713 # information) | |
| 1714 for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status': | |
| 1715 db.security.addPermissionToRole('Anonymous', 'View', cl) | |
| 1716 | |
| 1717 # Allow the anonymous user to use the "Show Unassigned" search. | |
| 1718 # It acts like "Show Open" if this permission is not available. | |
| 1719 # If you are running a tracker that does not allow read access for | |
| 1720 # anonymous, you should remove this entry as it can be used to perform | |
| 1721 # a username guessing attack against a roundup install. | |
| 1722 p = db.security.addPermission(name='Search', klass='user') | |
| 1723 db.security.addPermissionToRole ('Anonymous', p) | |
| 1724 | |
| 1725 # [OPTIONAL] | |
| 1726 # Allow anonymous users access to create or edit "issue" items (and the | |
| 1727 # related file and message items) | |
| 1728 #for cl in 'issue', 'file', 'msg': | |
| 1729 # db.security.addPermissionToRole('Anonymous', 'Create', cl) | |
| 1730 # db.security.addPermissionToRole('Anonymous', 'Edit', cl) | |
| 1731 | |
| 1732 .. index:: | |
| 1733 single: roundup-admin; view class permissions | |
| 1734 | |
| 1735 You can use ``roundup-admin security`` to verify the permissions | |
| 1736 defined in the schema. It also verifies that properties specified in | |
| 1737 permissions are valid for the class. This helps detect typos that can | |
| 1738 cause baffling permission issues. | |
| 1739 | |
| 1740 Automatic Permission Checks | |
| 1741 --------------------------- | |
| 1742 | |
| 1743 Permissions are automatically checked when information is rendered | |
| 1744 through the web. This includes: | |
| 1745 | |
| 1746 1. View checks for properties when being rendered via the ``plain()`` or | |
| 1747 similar methods. If the check fails, the text "[hidden]" will be | |
| 1748 displayed. | |
| 1749 2. Edit checks for properties when the edit field is being rendered via | |
| 1750 the ``field()`` or similar methods. If the check fails, the property | |
| 1751 will be rendered via the ``plain()`` method (see point 1. for subsequent | |
| 1752 checking performed) | |
| 1753 3. View checks are performed in index pages for each item being displayed | |
| 1754 such that if the user does not have permission, the row is not rendered. | |
| 1755 4. View checks are performed at the top of item pages for the Item being | |
| 1756 displayed. If the user does not have permission, the text "You are not | |
| 1757 allowed to view this page." will be displayed. | |
| 1758 5. View checks are performed at the top of index pages for the Class being | |
| 1759 displayed. If the user does not have permission, the text "You are not | |
| 1760 allowed to view this page." will be displayed. | |
| 1761 | |
| 1762 | |
| 1763 New User Roles | |
| 1764 -------------- | |
| 1765 | |
| 1766 New users are assigned the Roles defined in the config file as: | |
| 1767 | |
| 1768 - NEW_WEB_USER_ROLES | |
| 1769 - NEW_EMAIL_USER_ROLES | |
| 1770 | |
| 1771 The `users may only edit their issues | |
| 1772 <customizing.html#users-may-only-edit-their-issues>`_ example shows | |
| 1773 customisation of these parameters. | |
| 1774 | |
| 1775 | |
| 1776 Changing Access Controls | |
| 1777 ------------------------ | |
| 1778 | |
| 1779 You may alter the configuration variables to change the Role that new | |
| 1780 web or email users get, for example to not give them access to the web | |
| 1781 interface if they register through email. | |
| 1782 | |
| 1783 You may use the ``roundup-admin`` "``security``" command to display the | |
| 1784 current Role and Permission configuration in your tracker. | |
| 1785 | |
| 1786 | |
| 1787 Adding a new Permission | |
| 1788 ~~~~~~~~~~~~~~~~~~~~~~~ | |
| 1789 | |
| 1790 When adding a new Permission, you will need to: | |
| 1791 | |
| 1792 1. add it to your tracker's ``schema.py`` so it is created, using | |
| 1793 ``security.addPermission``, for example:: | |
| 1794 | |
| 1795 db.security.addPermission(name="View", klass='frozzle', | |
| 1796 description="User is allowed to access frozzles") | |
| 1797 | |
| 1798 will set up a new "View" permission on the Class "frozzle". | |
| 1799 2. enable it for the Roles that should have it (verify with | |
| 1800 "``roundup-admin security``") | |
| 1801 3. add it to the relevant HTML interface templates | |
| 1802 4. add it to the appropriate xxxPermission methods on in your tracker | |
| 1803 interfaces module | |
| 1804 | |
| 1805 The ``addPermission`` method takes a few optional parameters: | |
| 1806 | |
| 1807 **properties** | |
| 1808 A sequence of property names that are the only properties to apply the | |
| 1809 new Permission to (eg. ``... klass='user', properties=('name', | |
| 1810 'email') ...``) | |
| 1811 **props_only** | |
| 1812 A boolean value (set to false by default) that is a new feature | |
| 1813 in Roundup 1.6. | |
| 1814 A permission defined using: | |
| 1815 | |
| 1816 ``properties=('list', 'of', 'property', 'names')`` | |
| 1817 | |
| 1818 is used to determine access for things other than just those | |
| 1819 properties. For example a check for View permission on the issue | |
| 1820 class or an issue item can use any View permission for the issue | |
| 1821 class even if that permission has a property list. This can be | |
| 1822 confusing and surprising as you would think that a permission | |
| 1823 including properties would be used only for determining the | |
| 1824 access permission for those properties. | |
| 1825 | |
| 1826 ``roundup-admin security`` will report invalid properties for the | |
| 1827 class. For example a permission with an invalid summary property is | |
| 1828 presented as:: | |
| 1829 | |
| 1830 Allowed to see content of object regardless of spam status | |
| 1831 (View for "file": ('content', 'summary') only) | |
| 1832 | |
| 1833 **Invalid properties for file: ['summary'] | |
| 1834 | |
| 1835 Setting ``props_only=True`` will make the permission valid only for | |
| 1836 those properties. | |
| 1837 | |
| 1838 If you use a lot of permissions with property checks, it can be | |
| 1839 difficult to change all of them. Calling the function: | |
| 1840 | |
| 1841 db.security.set_props_only_default(True) | |
| 1842 | |
| 1843 at the top of ``schema.py`` will make every permission creation | |
| 1844 behave as though props_only was set to True. It is expected that the | |
| 1845 default of True will become the default in a future Roundup release. | |
| 1846 **check** | |
| 1847 A function to be executed which returns boolean determining whether | |
| 1848 the Permission is allowed. If it returns True, the permission is | |
| 1849 allowed, if it returns False the permission is denied. The function | |
| 1850 can have one of two signatures:: | |
| 1851 | |
| 1852 check(db, userid, itemid) | |
| 1853 | |
| 1854 or:: | |
| 1855 | |
| 1856 check(db, userid, itemid, **ctx) | |
| 1857 | |
| 1858 where ``db`` is a handle on the open database, ``userid`` is | |
| 1859 the user attempting access and ``itemid`` is the specific item being | |
| 1860 accessed. If the second form is used the ``ctx`` dictionary is | |
| 1861 defined with the following values:: | |
| 1862 | |
| 1863 ctx['property'] the name of the property being checked or None if | |
| 1864 it's a class check. | |
| 1865 | |
| 1866 ctx['classname'] the name of the class that is being checked | |
| 1867 (issue, query ....). | |
| 1868 | |
| 1869 ctx['permission'] the name of the permission (e.g. View, Edit...). | |
| 1870 | |
| 1871 The second form is preferred as it makes it easier to implement more | |
| 1872 complex permission schemes. An `example in upgrading.html | |
| 1873 <upgrading.html#enhancement-to-check-command-for-permissions>`_ | |
| 1874 shows the use of ``ctx``. | |
| 1875 | |
| 1876 Example Scenarios | |
| 1877 ~~~~~~~~~~~~~~~~~ | |
| 1878 | |
| 1879 See the `examples <customizing.html#examples>`_ section for longer | |
| 1880 examples of customisation. | |
| 1881 | |
| 1882 **anonymous access through the e-mail gateway** | |
| 1883 Give the "anonymous" user the "Email Access", ("Edit", "issue") and | |
| 1884 ("Create", "msg") Permissions but do not not give them the ("Create", | |
| 1885 "user") Permission. This means that when an unknown user sends email | |
| 1886 into the tracker, they're automatically logged in as "anonymous". | |
| 1887 Since they don't have the ("Create", "user") Permission, they won't | |
| 1888 be automatically registered, but since "anonymous" has permission to | |
| 1889 use the gateway, they'll still be able to submit issues. Note that | |
| 1890 the Sender information - their email address - will not be available | |
| 1891 - they're *anonymous*. | |
| 1892 | |
| 1893 **automatic registration of users in the e-mail gateway** | |
| 1894 By giving the "anonymous" user the ("Register", "user") Permission, any | |
| 1895 unidentified user will automatically be registered with the tracker | |
| 1896 (with no password, so they won't be able to log in through | |
| 1897 the web until an admin sets their password). By default new Roundup | |
| 1898 trackers don't allow this as it opens them up to spam. It may be enabled | |
| 1899 by uncommenting the appropriate addPermissionToRole in your tracker's | |
| 1900 ``schema.py`` file. The new user is given the Roles list defined in the | |
| 1901 "new_email_user_roles" config variable. | |
| 1902 | |
| 1903 **only developers may be assigned issues** | |
| 1904 Create a new Permission called "Fixer" for the "issue" class. Create a | |
| 1905 new Role "Developer" which has that Permission, and assign that to the | |
| 1906 appropriate users. Filter the list of users available in the assignedto | |
| 1907 list to include only those users. Enforce the Permission with an | |
| 1908 auditor. See the example | |
| 1909 `restricting the list of users that are assignable to a task | |
| 1910 <customizing.html#restricting-the-list-of-users-that-are-assignable-to-a-task>`_. | |
| 1911 | |
| 1912 **only managers may sign off issues as complete** | |
| 1913 Create a new Permission called "Closer" for the "issue" class. Create a | |
| 1914 new Role "Manager" which has that Permission, and assign that to the | |
| 1915 appropriate users. In your web interface, only display the "resolved" | |
| 1916 issue state option when the user has the "Closer" Permissions. Enforce | |
| 1917 the Permission with an auditor. This is very similar to the previous | |
| 1918 example, except that the web interface check would look like:: | |
| 1919 | |
| 1920 <option tal:condition="python:request.user.hasPermission('Closer')" | |
| 1921 value="resolved">Resolved</option> | |
| 1922 | |
| 1923 **don't give web access to users who register through email** | |
| 1924 Create a new Role called "Email User" which has all the Permissions of | |
| 1925 the normal "User" Role minus the "Web Access" Permission. This will | |
| 1926 allow users to send in emails to the tracker, but not access the web | |
| 1927 interface. | |
| 1928 | |
| 1929 **let some users edit the details of all users** | |
| 1930 Create a new Role called "User Admin" which has the Permission for | |
| 1931 editing users:: | |
| 1932 | |
| 1933 db.security.addRole(name='User Admin', description='Managing users') | |
| 1934 p = db.security.getPermission('Edit', 'user') | |
| 1935 db.security.addPermissionToRole('User Admin', p) | |
| 1936 | |
| 1937 and assign the Role to the users who need the permission. | |
| 1938 | |
| 1939 | |
| 1940 Web Interface | |
| 1941 ============= | |
| 1942 | |
| 1943 .. contents:: | |
| 1944 :local: | |
| 1945 | |
| 1946 The web interface is provided by the ``roundup.cgi.client`` module and | |
| 1947 is used by ``roundup.cgi``, ``roundup-server`` and ``ZRoundup`` | |
| 1948 (``ZRoundup`` is broken, until further notice). In all cases, we | |
| 1949 determine which tracker is being accessed (the first part of the URL | |
| 1950 path inside the scope of the CGI handler) and pass control on to the | |
| 1951 ``roundup.cgi.client.Client`` class - which handles the rest of the | |
| 1952 access through its ``main()`` method. This means that you can do pretty | |
| 1953 much anything you want as a web interface to your tracker. | |
| 1954 | |
| 1955 | |
| 1956 | |
| 1957 Repercussions of changing the tracker schema | |
| 1958 --------------------------------------------- | |
| 1959 | |
| 1960 If you choose to change the `tracker schema`_ you will need to ensure | |
| 1961 the web interface knows about it: | |
| 1962 | |
| 1963 1. Index, item and search pages for the relevant classes may need to | |
| 1964 have properties added or removed, | |
| 1965 2. The "page" template may require links to be changed, as might the | |
| 1966 "home" page's content arguments. | |
| 1967 | |
| 1968 | |
| 1969 How requests are processed | |
| 1970 -------------------------- | |
| 1971 | |
| 1972 The basic processing of a web request proceeds as follows: | |
| 1973 | |
| 1974 1. figure out who we are, defaulting to the "anonymous" user | |
| 1975 2. figure out what the request is for - we call this the "context" | |
| 1976 3. handle any requested action (item edit, search, ...) | |
| 1977 4. render the template requested by the context, resulting in HTML | |
| 1978 output | |
| 1979 | |
| 1980 In some situations, exceptions occur: | |
| 1981 | |
| 1982 - HTTP Redirect (generally raised by an action) | |
| 1983 the URL to redirect to is the first argument of the exception. | |
| 1984 - SendFile (generally raised by ``determine_context``) | |
| 1985 here we serve up a FileClass "content" property | |
| 1986 - SendStaticFile (generally raised by ``determine_context``) | |
| 1987 here we serve up a file from the tracker "html" directory | |
| 1988 - Unauthorised (generally raised by an action) | |
| 1989 here the action is cancelled, the request is rendered and an error | |
| 1990 message is displayed indicating that permission was not granted for | |
| 1991 the action to take place | |
| 1992 - NotFound (raised wherever it needs to be) | |
| 1993 this exception percolates up to the CGI interface that called the | |
| 1994 client | |
| 1995 | |
| 1996 | |
| 1997 Roundup URL design | |
| 1998 ------------------ | |
| 1999 | |
| 2000 Each tracker has several hardcoded URLs. These three are equivalent and | |
| 2001 lead to the main tracker page: | |
| 2002 | |
| 2003 1. ``/`` | |
| 2004 2. ``/index`` | |
| 2005 3. ``/home`` | |
| 2006 | |
| 2007 The following prefix is used to access static resources: | |
| 2008 | |
| 2009 4. ``/@@file/`` | |
| 2010 | |
| 2011 Two additional url's are used for the API's. | |
| 2012 The `REST api`_ is accessed via: | |
| 2013 | |
| 2014 5. ``/rest/`` | |
| 2015 | |
| 2016 .. _`REST api`: rest.html | |
| 2017 | |
| 2018 and the `XMLRPC api`_ is available at: | |
| 2019 | |
| 2020 6. ``/xmlrpc`` | |
| 2021 | |
| 2022 .. _`XMLRPC api`: xmlrpc.html | |
| 2023 | |
| 2024 All other URLs depend on the classes configured in Roundup database. | |
| 2025 Each class receives two URLs - one for the class itself and another | |
| 2026 for specific items of that class. Example for class URL: | |
| 2027 | |
| 2028 7. ``/issue`` | |
| 2029 | |
| 2030 This is usually used to show listings of class items. The URL for | |
| 2031 for specific object of issue class with id 1 will look like: | |
| 2032 | |
| 2033 8. ``/issue1`` | |
| 2034 | |
| 2035 .. _strip_zeros: | |
| 2036 | |
| 2037 Note that a leading string of 0's will be stripped from the id part of | |
| 2038 the object :term:`designator` in the URL. E.G. ``/issue001`` is the | |
| 2039 same as ``/issue1``. Similarly for ``/file01`` etc. However you | |
| 2040 should generate URL's without the extra zeros. | |
| 2041 | |
| 2042 Determining web context | |
| 2043 ----------------------- | |
| 2044 | |
| 2045 To determine the "context" of a request (what request is for), we look at | |
| 2046 the URL path after the tracker root and at ``@template`` request | |
| 2047 parameter. Typical URL paths look like: | |
| 2048 | |
| 2049 1. ``/tracker/issue`` | |
| 2050 2. ``/tracker/issue1`` | |
| 2051 3. ``/tracker/@@file/style.css`` | |
| 2052 4. ``/cgi-bin/roundup.cgi/tracker/file1`` | |
| 2053 5. ``/cgi-bin/roundup.cgi/tracker/file1/kitten.png`` | |
| 2054 | |
| 2055 where tracker root is ``/tracker/`` or ``/cgi-bin/roundup.cgi/tracker/`` | |
| 2056 We're looking at "issue", "issue1", "@@file/style.css", "file1" and | |
| 2057 "file1/kitten.png" in the cases above. | |
| 2058 | |
| 2059 1. with is no path we are in the "home" context. See `the "home" | |
| 2060 context`_ below for details. "index" or "home" paths may also be used | |
| 2061 to switch into "home" context. | |
| 2062 2. for paths starting with "@@file" the additional path entry ("style.css" | |
| 2063 in the example above) specifies the static file to be served | |
| 2064 from the tracker TEMPLATES directory (or STATIC_FILES, if configured). | |
| 2065 This is usually the tracker's "html" directory. Internally this works | |
| 2066 by raising SendStaticFile exception. | |
| 2067 3. if there is something in the path (as in example 1, "issue"), it | |
| 2068 identifies the tracker class to display. | |
| 2069 4. if the path is an item designator (as in examples 2 and 4, "issue1" | |
| 2070 and "file1"), then we're to display a specific item. | |
| 2071 :ref:`Note. <strip_zeros>` | |
| 2072 5. if the path starts with an item designator and is longer than one | |
| 2073 entry (as in example 5, "file1/kitten.png"), then we're assumed to be | |
| 2074 handling an item of a ``FileClass``, and the extra path information | |
| 2075 gives the filename that the client is going to label the download | |
| 2076 with (i.e. "file1/kitten.png" is nicer to download than "file1"). | |
| 2077 This raises a ``SendFile`` exception. | |
| 2078 | |
| 2079 Neither 2. or 5. use templates and stop before the template is | |
| 2080 determined. For other contexts the template used is specified by the | |
| 2081 ``@template`` variable, which defaults to: | |
| 2082 | |
| 2083 - only classname supplied: "index" | |
| 2084 - full item designator supplied: "item" | |
| 2085 | |
| 2086 | |
| 2087 The "home" Context | |
| 2088 ------------------ | |
| 2089 | |
| 2090 The "home" context is special because it allows you to add templated | |
| 2091 pages to your tracker that don't rely on a class or item (ie. an issues | |
| 2092 list or specific issue). | |
| 2093 | |
| 2094 Let's say you wish to add frames to control the layout of your tracker's | |
| 2095 interface. You'd probably have: | |
| 2096 | |
| 2097 - A top-level frameset page. This page probably wouldn't be templated, so | |
| 2098 it could be served as a static file (see `serving static content`_) | |
| 2099 - A sidebar frame that is templated. Let's call this page | |
| 2100 "home.navigation.html" in your tracker's "html" directory. To load that | |
| 2101 page up, you use the URL:: | |
| 2102 | |
| 2103 <tracker url>/home?@template=navigation | |
| 2104 | |
| 2105 | |
| 2106 Serving static content | |
| 2107 ---------------------- | |
| 2108 | |
| 2109 See the previous section `determining web context`_ where it describes | |
| 2110 ``@@file`` paths. | |
| 2111 | |
| 2112 These files are served without any permission checks. Any user on the | |
| 2113 internet with the url can download the file. | |
| 2114 | |
| 2115 This is rarely an issue since the html templates are just source code | |
| 2116 and much of it can be found in the Roundup repository. Other | |
| 2117 decoration (logos, stylesheets) are similarly not security sensitive. | |
| 2118 You can use the static_files setting in config.ini to eliminate | |
| 2119 access to the templates directory if desired. | |
| 2120 | |
| 2121 If a file resolves to a symbolic link, it is not served. | |
| 2122 | |
| 2123 Performing actions in web requests | |
| 2124 ---------------------------------- | |
| 2125 | |
| 2126 When a user requests a web page, they may optionally also request for an | |
| 2127 action to take place. As described in `how requests are processed`_, the | |
| 2128 action is performed before the requested page is generated. Actions are | |
| 2129 triggered by using a ``@action`` CGI variable, where the value is one | |
| 2130 of: | |
| 2131 | |
| 2132 **login** | |
| 2133 Attempt to log a user in. | |
| 2134 | |
| 2135 **logout** | |
| 2136 Log the user out - make them "anonymous". | |
| 2137 | |
| 2138 **register** | |
| 2139 Attempt to create a new user based on the contents of the form and then | |
| 2140 log them in. | |
| 2141 | |
| 2142 **edit** | |
| 2143 Perform an edit of an item in the database. There are some `special form | |
| 2144 variables`_ you may use. Also you can set the ``__redirect_to`` form | |
| 2145 variable to the URL that should be displayed after the edit is succesfully | |
| 2146 completed. If you wanted to edit a sequence of issues, users etc. this | |
| 2147 could be used to display the next item in the sequence to the user. | |
| 2148 | |
| 2149 **new** | |
| 2150 Add a new item to the database. You may use the same `special form | |
| 2151 variables`_ as in the "edit" action. Also you can set the | |
| 2152 ``__redirect_to`` form variable to the URL that should be displayed after | |
| 2153 the new item is created. This is useful if you want to create another | |
| 2154 item rather than edit the newly created item. | |
| 2155 | |
| 2156 **retire** | |
| 2157 Retire the item in the database. | |
| 2158 | |
| 2159 **editCSV** | |
| 2160 Performs an edit of all of a class' items in one go. See also the | |
| 2161 *class*.csv templating method which generates the CSV data to be | |
| 2162 edited, and the ``'_generic.index'`` template which uses both of these | |
| 2163 features. | |
| 2164 | |
| 2165 **search** | |
| 2166 Mangle some of the form variables: | |
| 2167 | |
| 2168 - Set the form ":filter" variable based on the values of the filter | |
| 2169 variables - if they're set to anything other than "dontcare" then add | |
| 2170 them to :filter. | |
| 2171 | |
| 2172 - Also handle the ":queryname" variable and save off the query to the | |
| 2173 user's query list. | |
| 2174 | |
| 2175 Each of the actions is implemented by a corresponding ``*XxxAction*`` (where | |
| 2176 "Xxx" is the name of the action) class in the ``roundup.cgi.actions`` module. | |
| 2177 These classes are registered with ``roundup.cgi.client.Client``. If you need | |
| 2178 to define new actions, you may add them there (see `defining new | |
| 2179 web actions`_). | |
| 2180 | |
| 2181 Each action class also has a ``*permission*`` method which determines whether | |
| 2182 the action is permissible given the current user. The base permission checks | |
| 2183 for each action are: | |
| 2184 | |
| 2185 **login** | |
| 2186 Determine whether the user has the "Web Access" Permission. | |
| 2187 **logout** | |
| 2188 No permission checks are made. | |
| 2189 **register** | |
| 2190 Determine whether the user has the ("Create", "user") Permission. | |
| 2191 **edit** | |
| 2192 Determine whether the user has permission to edit this item. If we're | |
| 2193 editing the "user" class, users are allowed to edit their own details - | |
| 2194 unless they try to edit the "roles" property, which requires the | |
| 2195 special Permission "Web Roles". | |
| 2196 **new** | |
| 2197 Determine whether the user has permission to create this item. No | |
| 2198 additional property checks are made. Additionally, new user items may | |
| 2199 be created if the user has the ("Create", "user") Permission. | |
| 2200 **editCSV** | |
| 2201 Determine whether the user has permission to edit this class. | |
| 2202 **search** | |
| 2203 Determine whether the user has permission to view this class. | |
| 2204 | |
| 2205 Protecting users from web application attacks | |
| 2206 --------------------------------------------- | |
| 2207 | |
| 2208 There is a class of attacks known as Cross Site Request Forgeries | |
| 2209 (CSRF). Malicious code running in the browser can making a | |
| 2210 request to Roundup while you are logged into Roundup. The | |
| 2211 malicious code piggy backs on your existing Roundup session to | |
| 2212 make changes without your knowledge. Roundup 1.6 has support for | |
| 2213 defending against this by analyzing the | |
| 2214 | |
| 2215 * Referer, | |
| 2216 * Origin, and | |
| 2217 * Host or | |
| 2218 * X-Forwarded-Host | |
| 2219 | |
| 2220 HTTP headers. It compares the headers to the value of the web setting | |
| 2221 in the [tracker] section of the tracker's ``config.ini``. | |
| 2222 | |
| 2223 Also a per form token (also called a nonce) can be enabled for | |
| 2224 the tracker using the ``csrf_enforce_token`` option in | |
| 2225 config.ini. When enabled, Roundup will validate a hidden form | |
| 2226 field called ``@csrf``. If the validation fails (or the token | |
| 2227 is used more than once) the request is rejected. The ``@csrf`` | |
| 2228 input field is added automatically by calling the ``submit`` | |
| 2229 function/path. It can also be added manually by calling | |
| 2230 anti_csrf_nonce() directly. For example:: | |
| 2231 | |
| 2232 <input name="@csrf" type="hidden" | |
| 2233 tal:attributes="value python:utils.anti_csrf_nonce(lifetime=10)"> | |
| 2234 | |
| 2235 By default a nonce lifetime is 2 weeks. However the lifetime (in | |
| 2236 minutes) can be set by passing a lifetime argument as shown | |
| 2237 above. The example above makes the nonce lifetime 10 minutes. | |
| 2238 | |
| 2239 Search for @csrf in this document for more examples. There are | |
| 2240 more examples and information in ``upgrading.txt``. | |
| 2241 | |
| 2242 The token protects you because malicious code supplied by another | |
| 2243 site is unable to obtain the token. Thus many attempts they make | |
| 2244 to submit a request are rejected. | |
| 2245 | |
| 2246 The protection on the xmlrpc interface is untested, but is based | |
| 2247 on a valid header check against the Roundup url and the presence | |
| 2248 of the ``X-REQUESTED-WITH`` header. Work to improve this is a | |
| 2249 future project after the 1.6 release. | |
| 2250 | |
| 2251 The enforcement levels can be modified in ``config.ini``. Refer to | |
| 2252 that file for details. | |
| 2253 | |
| 2254 Special form variables | |
| 2255 ---------------------- | |
| 2256 | |
| 2257 Item properties and their values are edited with html FORM | |
| 2258 variables and their values. You can: | |
| 2259 | |
| 2260 - Change the value of some property of the current item. | |
| 2261 - Create a new item of any class, and edit the new item's | |
| 2262 properties, | |
| 2263 - Attach newly created items to a multilink property of the | |
| 2264 current item. | |
| 2265 - Remove items from a multilink property of the current item. | |
| 2266 - Specify that some properties are required for the edit | |
| 2267 operation to be successful. | |
| 2268 - Redirect to a different page after creating a new item (new action | |
| 2269 only, not edit action). Usually you end up on the page for the | |
| 2270 created item. | |
| 2271 - Set up user interface locale. | |
| 2272 | |
| 2273 These operations will only take place if the form action (the | |
| 2274 ``@action`` variable) is "edit" or "new". | |
| 2275 | |
| 2276 In the following, <bracketed> values are variable, "@" may be | |
| 2277 either ":" or "@", and other text "required" is fixed. | |
| 2278 | |
| 2279 Two special form variables are used to specify user language preferences: | |
| 2280 | |
| 2281 ``@language`` | |
| 2282 value may be locale name or ``none``. If this variable is set to | |
| 2283 locale name, web interface language is changed to given value | |
| 2284 (provided that appropriate translation is available), the value | |
| 2285 is stored in the browser cookie and will be used for all following | |
| 2286 requests. If value is ``none`` the cookie is removed and the | |
| 2287 language is changed to the tracker default, set up in the tracker | |
| 2288 configuration or OS environment. | |
| 2289 | |
| 2290 ``@charset`` | |
| 2291 value may be character set name or ``none``. Character set name | |
| 2292 is stored in the browser cookie and sets output encoding for all | |
| 2293 HTML pages generated by Roundup. If value is ``none`` the cookie | |
| 2294 is removed and HTML output is reset to Roundup internal encoding | |
| 2295 (UTF-8). | |
| 2296 | |
| 2297 Most properties are specified as form variables: | |
| 2298 | |
| 2299 ``<propname>`` | |
| 2300 property on the current context item | |
| 2301 | |
| 2302 ``<designator>"@"<propname>`` | |
| 2303 property on the indicated item (for editing related information) | |
| 2304 | |
| 2305 Designators name a specific item of a class. | |
| 2306 | |
| 2307 ``<classname><N>`` | |
| 2308 Name an existing item of class <classname>. | |
| 2309 | |
| 2310 ``<classname>"-"<N>`` | |
| 2311 Name the <N>th new item of class <classname>. If the form | |
| 2312 submission is successful, a new item of <classname> is | |
| 2313 created. Within the submitted form, a particular | |
| 2314 designator of this form always refers to the same new | |
| 2315 item. | |
| 2316 | |
| 2317 Once we have determined the "propname", we look at it to see | |
| 2318 if it's special: | |
| 2319 | |
| 2320 ``@required`` | |
| 2321 The associated form value is a comma-separated list of | |
| 2322 property names that must be specified when the form is | |
| 2323 submitted for the edit operation to succeed. | |
| 2324 | |
| 2325 When the <designator> is missing, the properties are | |
| 2326 for the current context item. When <designator> is | |
| 2327 present, they are for the item specified by | |
| 2328 <designator>. | |
| 2329 | |
| 2330 The "@required" specifier must come before any of the | |
| 2331 properties it refers to are assigned in the form. | |
| 2332 | |
| 2333 ``@remove@<propname>=id(s)`` or ``@add@<propname>=id(s)`` | |
| 2334 The "@add@" and "@remove@" edit actions apply only to | |
| 2335 Multilink properties. The form value must be a | |
| 2336 comma-separate list of keys for the class specified by | |
| 2337 the simple form variable. The listed items are added | |
| 2338 to (respectively, removed from) the specified | |
| 2339 property. | |
| 2340 | |
| 2341 ``@link@<propname>=<designator>`` | |
| 2342 If the edit action is "@link@", the simple form | |
| 2343 variable must specify a Link or Multilink property. | |
| 2344 The form value is a comma-separated list of | |
| 2345 designators. The item corresponding to each | |
| 2346 designator is linked to the property given by simple | |
| 2347 form variable. | |
| 2348 | |
| 2349 None of the above (ie. just a simple form value) | |
| 2350 The value of the form variable is converted | |
| 2351 appropriately, depending on the type of the property. | |
| 2352 | |
| 2353 For a Link('klass') property, the form value is a | |
| 2354 single key for 'klass', where the key field is | |
| 2355 specified in schema.py. | |
| 2356 | |
| 2357 For a Multilink('klass') property, the form value is a | |
| 2358 comma-separated list of keys for 'klass', where the | |
| 2359 key field is specified in schema.py. | |
| 2360 | |
| 2361 Note that for simple-form-variables specifiying Link | |
| 2362 and Multilink properties, the linked-to class must | |
| 2363 have a key field. | |
| 2364 | |
| 2365 For a String() property specifying a filename, the | |
| 2366 file named by the form value is uploaded. This means we | |
| 2367 try to set additional properties "filename" and "type" (if | |
| 2368 they are valid for the class). Otherwise, the property | |
| 2369 is set to the form value. | |
| 2370 | |
| 2371 For Date(), Interval(), Boolean(), Integer() and Number() | |
| 2372 properties, the form value is converted to the | |
| 2373 appropriate value. | |
| 2374 | |
| 2375 Any of the form variables may be prefixed with a classname or | |
| 2376 designator. | |
| 2377 | |
| 2378 Setting the form variable: ``__redirect_to=`` to a url when | |
| 2379 @action=new redirects the user to the specified url after successfully | |
| 2380 creating the new item. This is useful if you want the user to create | |
| 2381 another item rather than edit the newly created item. Note that the | |
| 2382 url assigned to ``__redirect_to`` must be url encoded/quoted and be | |
| 2383 under the tracker's base url. If the base_url uses http, you can set | |
| 2384 the url to https. | |
| 2385 | |
| 2386 Two special form values are supported for backwards compatibility: | |
| 2387 | |
| 2388 @note | |
| 2389 This is equivalent to:: | |
| 2390 | |
| 2391 @link@messages=msg-1 | |
| 2392 msg-1@content=value | |
| 2393 | |
| 2394 which is equivalent to the html:: | |
| 2395 | |
| 2396 <textarea name="msg-1@content"></textarea> | |
| 2397 <input type="hidden" name="@link@messages" value="msg-1"> | |
| 2398 | |
| 2399 except that in addition, the "author" and "date" properties of | |
| 2400 "msg-1" are set to the userid of the submitter, and the current | |
| 2401 time, respectively. | |
| 2402 | |
| 2403 @file | |
| 2404 This is equivalent to:: | |
| 2405 | |
| 2406 @link@files=file-1 | |
| 2407 file-1@content=value | |
| 2408 | |
| 2409 by adding the HTML:: | |
| 2410 | |
| 2411 <input type="file" name="file-1@content"> | |
| 2412 <input type="hidden" name="@link@files" value="file-1"> | |
| 2413 | |
| 2414 The String content value is handled as described above for file | |
| 2415 uploads. | |
| 2416 | |
| 2417 If both the "@note" and "@file" form variables are | |
| 2418 specified, the action:: | |
| 2419 | |
| 2420 msg-1@link@files=file-1 | |
| 2421 | |
| 2422 is also performed. This would be expressed in HTML with:: | |
| 2423 | |
| 2424 <input type="hidden" name="msg-1@link@files" value="file-1"> | |
| 2425 | |
| 2426 We also check that FileClass items have a "content" property with | |
| 2427 actual content, otherwise we remove them from all_props before | |
| 2428 returning. | |
| 2429 | |
| 2430 | |
| 2431 Default templates | |
| 2432 ----------------- | |
| 2433 | |
| 2434 The default templates are html4 compliant. If you wish to change them to be | |
| 2435 xhtml compliant, you'll need to change the ``html_version`` configuration | |
| 2436 variable in ``config.ini`` to ``'xhtml'`` instead of ``'html4'``. | |
| 2437 | |
| 2438 Most customisation of the web view can be done by modifying the | |
| 2439 templates in the tracker ``'html'`` directory. There are several types | |
| 2440 of files in there. The *minimal* template includes: | |
| 2441 | |
| 2442 **page.html** | |
| 2443 This template usually defines the overall look of your tracker. When | |
| 2444 you view an issue, it appears inside this template. When you view an | |
| 2445 index, it also appears inside this template. This template defines a | |
| 2446 macro called "icing" which is used by almost all other templates as a | |
| 2447 coating for their content, using its "content" slot. It also defines | |
| 2448 the "head_title" and "body_title" slots to allow setting of the page | |
| 2449 title. | |
| 2450 **home.html** | |
| 2451 the default page displayed when no other page is indicated by the user | |
| 2452 **home.classlist.html** | |
| 2453 a special version of the default page that lists the classes in the | |
| 2454 tracker | |
| 2455 **classname.item.html** | |
| 2456 displays an item of the *classname* class | |
| 2457 **classname.index.html** | |
| 2458 displays a list of *classname* items | |
| 2459 **classname.search.html** | |
| 2460 displays a search page for *classname* items | |
| 2461 **_generic.index.html** | |
| 2462 used to display a list of items where there is no | |
| 2463 ``*classname*.index`` available | |
| 2464 **_generic.help.html** | |
| 2465 used to display a "class help" page where there is no | |
| 2466 ``*classname*.help`` | |
| 2467 **user.register.html** | |
| 2468 a special page just for the user class, that renders the registration | |
| 2469 page | |
| 2470 **style.css** | |
| 2471 a static file that is served up as-is | |
| 2472 | |
| 2473 The *classic* template has a number of additional templates. | |
| 2474 | |
| 2475 Remember that you can create any template extension you want to, | |
| 2476 so if you just want to play around with the templating for new issues, | |
| 2477 you can copy the current "issue.item" template to "issue.test", and then | |
| 2478 access the test template using the "@template" URL argument:: | |
| 2479 | |
| 2480 http://your.tracker.example/tracker/issue?@template=test | |
| 2481 | |
| 2482 and it won't affect your users using the "issue.item" template. | |
| 2483 | |
| 2484 You can also put templates into a subdirectory of the template | |
| 2485 directory. So if you specify:: | |
| 2486 | |
| 2487 http://your.tracker.example/tracker/issue?@template=test/item | |
| 2488 | |
| 2489 you will use the template at: ``test/issue.item.html``. If that | |
| 2490 template doesn't exit it will try to use | |
| 2491 ``test/_generic.item.html``. If that template doesn't exist | |
| 2492 it will return an error. | |
| 2493 | |
| 2494 Implementing Modal Editing Using @template | |
| 2495 ------------------------------------------ | |
| 2496 | |
| 2497 Many item templates allow you to edit the item. They contain | |
| 2498 code that renders edit boxes if the user has edit permissions. | |
| 2499 Otherwise the template will just display the item information. | |
| 2500 | |
| 2501 In some cases you want to do a modal edit. The user has to take some | |
| 2502 action (click a button or follow a link) to shift from display mode to | |
| 2503 edit mode. When the changes are submitted, ending the edit mode, | |
| 2504 the user is returned to display mode. | |
| 2505 | |
| 2506 Modal workflows usually slow things down and are not implemented by | |
| 2507 default templates. However for some workflows a modal edit is useful. | |
| 2508 For example a batch edit mode that allows the user to edit a number of | |
| 2509 issues all from one form could be implemented as a modal workflow of: | |
| 2510 | |
| 2511 * search for issues to modify | |
| 2512 * switch to edit mode and change values | |
| 2513 * exit back to the results of the search | |
| 2514 | |
| 2515 To implement the modal edit, assume you have an issue.edit.html | |
| 2516 template that implements an edit form. On the display page (a version | |
| 2517 of issue.item.html modified to only display information) add a link | |
| 2518 that calls the display url, but adds ``@template=edit`` to the link. | |
| 2519 | |
| 2520 This will now display the edit page. On the edit page you want to add | |
| 2521 a hidden text field to your form named ``@template`` with the value: | |
| 2522 ``item|edit``. When the form is submitted it is validated. If the | |
| 2523 form is correct the user will see the item rendered using the item | |
| 2524 template. If there is an error (validation failed) the item will be | |
| 2525 rendered using the edit template. The edit template that is rendered | |
| 2526 will display all the changes that the user made to the form before it | |
| 2527 was submitted. The user can correct the error and resubmit the changes | |
| 2528 until the form validates. | |
| 2529 | |
| 2530 If the form failed to validate but the ``@template`` field had the | |
| 2531 value ``item`` the user would still see the error, but all of the data | |
| 2532 the user entered would be discarded. The user would have to redo all | |
| 2533 the edits again. | |
| 2534 | |
| 2535 | |
| 2536 How the templates work | |
| 2537 ---------------------- | |
| 2538 | |
| 2539 | |
| 2540 Templating engines | |
| 2541 ~~~~~~~~~~~~~~~~~~ | |
| 2542 | |
| 2543 Since version 1.4.20 Roundup supports two templating engines: | |
| 2544 | |
| 2545 * the original `Template Attribute Language`_ (TAL) engine from Zope | |
| 2546 * the standalone Chameleon templating engine. Chameleon is intended | |
| 2547 as a replacement for the original TAL engine, and supports the | |
| 2548 same syntax, but they are not 100% compatible. The major (and most | |
| 2549 likely the only) incompatibility is the default expression type being | |
| 2550 ``python:`` instead of ``path:``. See also "Incompatibilities and | |
| 2551 differences" section of `Chameleon documentation`__. | |
| 2552 | |
| 2553 Version 1.5.0 added experimental support for the `jinja2`_ templating | |
| 2554 language. You must install the `jinja2`_ module in order to use it. The | |
| 2555 ``jinja2`` template supplied with Roundup has the templates rewritten | |
| 2556 to use ``jinja2`` rather than TAL. A number of trackers are running | |
| 2557 using ``jinja2`` templating so it is considered less experimental than | |
| 2558 Chameleon templating. | |
| 2559 | |
| 2560 .. _jinja2: https://palletsprojects.com/p/jinja/ | |
| 2561 | |
| 2562 | |
| 2563 **NOTE1**: For historical reasons, examples given below assumes path | |
| 2564 expression as default expression type. With Chameleon you have to manually | |
| 2565 resolve the path expressions. A Chameleon-based, z3c.pt, that is fully | |
| 2566 compatible with the old TAL implementation, is planned to be included in a | |
| 2567 future release. | |
| 2568 | |
| 2569 **NOTE2**: As of 1.4.20 Chameleon support is highly experimental and **not** | |
| 2570 recommended for production use. | |
| 2571 | |
| 2572 .. _Chameleon: | |
| 2573 https://pypi.org/project/Chameleon/ | |
| 2574 .. _z3c.pt: | |
| 2575 https://pypi.org/project/z3c.pt/ | |
| 2576 __ https://chameleon.readthedocs.io/en/latest/reference.html?highlight=differences#incompatibilities-and-differences | |
| 2577 .. _TAL: | |
| 2578 .. _Template Attribute Language: | |
| 2579 https://pagetemplates.readthedocs.io/en/latest/history/TALSpecification14.html | |
| 2580 | |
| 2581 | |
| 2582 Basic Templating Actions | |
| 2583 ~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 2584 | |
| 2585 Roundup's templates consist of special attributes on the HTML tags. | |
| 2586 These attributes form the **Template Attribute Language**, or TAL. | |
| 2587 The basic TAL commands are: | |
| 2588 | |
| 2589 **tal:define="variable expression; variable expression; ..."** | |
| 2590 Define a new variable that is local to this tag and its contents. For | |
| 2591 example:: | |
| 2592 | |
| 2593 <html tal:define="title request/description"> | |
| 2594 <head><title tal:content="title"></title></head> | |
| 2595 </html> | |
| 2596 | |
| 2597 In this example, the variable "title" is defined as the result of the | |
| 2598 expression "request/description". The "tal:content" command inside the | |
| 2599 <html> tag may then use the "title" variable. | |
| 2600 | |
| 2601 **tal:condition="expression"** | |
| 2602 Only keep this tag and its contents if the expression is true. For | |
| 2603 example:: | |
| 2604 | |
| 2605 <p tal:condition="python:request.user.hasPermission('View', 'issue')"> | |
| 2606 Display some issue information. | |
| 2607 </p> | |
| 2608 | |
| 2609 In the example, the <p> tag and its contents are only displayed if | |
| 2610 the user has the "View" permission for issues. We consider the number | |
| 2611 zero, a blank string, an empty list, and the built-in variable | |
| 2612 nothing to be false values. Nearly every other value is true, | |
| 2613 including non-zero numbers, and strings with anything in them (even | |
| 2614 spaces!). | |
| 2615 | |
| 2616 **tal:repeat="variable expression"** | |
| 2617 Repeat this tag and its contents for each element of the sequence | |
| 2618 that the expression returns, defining a new local variable and a | |
| 2619 special "repeat" variable for each element. For example:: | |
| 2620 | |
| 2621 <tr tal:repeat="u user/list"> | |
| 2622 <td tal:content="u/id"></td> | |
| 2623 <td tal:content="u/username"></td> | |
| 2624 <td tal:content="u/realname"></td> | |
| 2625 </tr> | |
| 2626 | |
| 2627 The example would iterate over the sequence of users returned by | |
| 2628 "user/list" and define the local variable "u" for each entry. Using | |
| 2629 the repeat command creates a new variable called "repeat" which you | |
| 2630 may access to gather information about the iteration. See the section | |
| 2631 below on `the repeat variable`_. | |
| 2632 | |
| 2633 **tal:replace="expression"** | |
| 2634 Replace this tag with the result of the expression. For example:: | |
| 2635 | |
| 2636 <span tal:replace="request/user/realname" /> | |
| 2637 | |
| 2638 The example would replace the <span> tag and its contents with the | |
| 2639 user's realname. If the user's realname was "Bruce", then the | |
| 2640 resultant output would be "Bruce". | |
| 2641 | |
| 2642 **tal:content="expression"** | |
| 2643 Replace the contents of this tag with the result of the expression. | |
| 2644 For example:: | |
| 2645 | |
| 2646 <span tal:content="request/user/realname">user's name appears here | |
| 2647 </span> | |
| 2648 | |
| 2649 The example would replace the contents of the <span> tag with the | |
| 2650 user's realname. If the user's realname was "Bruce" then the | |
| 2651 resultant output would be "<span>Bruce</span>". | |
| 2652 | |
| 2653 **tal:attributes="attribute expression; attribute expression; ..."** | |
| 2654 Set attributes on this tag to the results of expressions. For | |
| 2655 example:: | |
| 2656 | |
| 2657 <a tal:attributes="href string:user${request/user/id}">My Details</a> | |
| 2658 | |
| 2659 In the example, the "href" attribute of the <a> tag is set to the | |
| 2660 value of the "string:user${request/user/id}" expression, which will | |
| 2661 be something like "user123". | |
| 2662 | |
| 2663 **tal:omit-tag="expression"** | |
| 2664 Remove this tag (but not its contents) if the expression is true. For | |
| 2665 example:: | |
| 2666 | |
| 2667 <span tal:omit-tag="python:1">Hello, world!</span> | |
| 2668 | |
| 2669 would result in output of:: | |
| 2670 | |
| 2671 Hello, world! | |
| 2672 | |
| 2673 Note that the commands on a given tag are evaulated in the order above, | |
| 2674 so *define* comes before *condition*, and so on. | |
| 2675 | |
| 2676 Additionally, you may include tags such as <tal:block>, which are | |
| 2677 removed from output. Its content is kept, but the tag itself is not (so | |
| 2678 don't go using any "tal:attributes" commands on it). This is useful for | |
| 2679 making arbitrary blocks of HTML conditional or repeatable (very handy | |
| 2680 for repeating multiple table rows, which would otherwise require an | |
| 2681 illegal tag placement to effect the repeat). | |
| 2682 | |
| 2683 | |
| 2684 Templating Expressions | |
| 2685 ~~~~~~~~~~~~~~~~~~~~~~ | |
| 2686 | |
| 2687 Templating Expressions are covered by `Template Attribute Language | |
| 2688 Expression Syntax`_, or TALES. The expressions you may use in the | |
| 2689 attribute values may be one of the following forms: | |
| 2690 | |
| 2691 **Path Expressions** - eg. ``item/status/checklist`` | |
| 2692 These are object attribute / item accesses. Roughly speaking, the | |
| 2693 path ``item/status/checklist`` is broken into parts ``item``, | |
| 2694 ``status`` and ``checklist``. The ``item`` part is the root of the | |
| 2695 expression. We then look for a ``status`` attribute on ``item``, or | |
| 2696 failing that, a ``status`` item (as in ``item['status']``). If that | |
| 2697 fails, the path expression fails. When we get to the end, the object | |
| 2698 we're left with is evaluated to get a string - if it is a method, it | |
| 2699 is called; if it is an object, it is stringified. Path expressions | |
| 2700 may have an optional ``path:`` prefix, but they are the default | |
| 2701 expression type, so it's not necessary. | |
| 2702 | |
| 2703 If an expression evaluates to ``default``, then the expression is | |
| 2704 "cancelled" - whatever HTML already exists in the template will | |
| 2705 remain (tag content in the case of ``tal:content``, attributes in the | |
| 2706 case of ``tal:attributes``). | |
| 2707 | |
| 2708 If an expression evaluates to ``nothing`` then the target of the | |
| 2709 expression is removed (tag content in the case of ``tal:content``, | |
| 2710 attributes in the case of ``tal:attributes`` and the tag itself in | |
| 2711 the case of ``tal:replace``). | |
| 2712 | |
| 2713 If an element in the path may not exist, then you can use the ``|`` | |
| 2714 operator in the expression to provide an alternative. So, the | |
| 2715 expression ``request/form/foo/value | default`` would simply leave | |
| 2716 the current HTML in place if the "foo" form variable doesn't exist. | |
| 2717 | |
| 2718 You may use the python function ``path``, as in | |
| 2719 ``path("item/status")``, to embed path expressions in Python | |
| 2720 expressions. | |
| 2721 | |
| 2722 **String Expressions** - eg. ``string:hello ${user/name}`` | |
| 2723 These expressions are simple string interpolations - though they can | |
| 2724 be just plain strings with no interpolation if you want. The | |
| 2725 expression in the ``${ ... }`` is just a path expression as above. | |
| 2726 | |
| 2727 **Python Expressions** - eg. ``python: 1+1`` | |
| 2728 These expressions give the full power of Python. All the "root level" | |
| 2729 variables are available, so ``python:item.status.checklist()`` would | |
| 2730 be equivalent to ``item/status/checklist``, assuming that | |
| 2731 ``checklist`` is a method. | |
| 2732 | |
| 2733 Modifiers: | |
| 2734 | |
| 2735 **structure** - eg. ``structure python:msg.content.plain(hyperlink=1)`` | |
| 2736 The result of expressions are normally *escaped* to be safe for HTML | |
| 2737 display (all "<", ">" and "&" are turned into special entities). The | |
| 2738 ``structure`` expression modifier turns off this escaping - the | |
| 2739 result of the expression is now assumed to be HTML, which is passed | |
| 2740 to the web browser for rendering. | |
| 2741 | |
| 2742 **not:** - eg. ``not:python:1=1`` | |
| 2743 This simply inverts the logical true/false value of another | |
| 2744 expression. | |
| 2745 | |
| 2746 .. _TALES: | |
| 2747 .. _Template Attribute Language Expression Syntax: | |
| 2748 https://pagetemplates.readthedocs.io/en/latest/history/TALESSpecification13.html | |
| 2749 | |
| 2750 | |
| 2751 Template Macros | |
| 2752 ~~~~~~~~~~~~~~~ | |
| 2753 | |
| 2754 Macros are used in Roundup to save us from repeating the same common | |
| 2755 page stuctures over and over. The most common (and probably only) macro | |
| 2756 you'll use is the "icing" macro defined in the "page" template. | |
| 2757 | |
| 2758 Macros are generated and used inside your templates using special | |
| 2759 attributes similar to the `basic templating actions`_. In this case, | |
| 2760 though, the attributes belong to the `Macro Expansion Template | |
| 2761 Attribute Language`_, or METAL. The macro commands are: | |
| 2762 | |
| 2763 **metal:define-macro="macro name"** | |
| 2764 Define that the tag and its contents are now a macro that may be | |
| 2765 inserted into other templates using the *use-macro* command. For | |
| 2766 example:: | |
| 2767 | |
| 2768 <html metal:define-macro="page"> | |
| 2769 ... | |
| 2770 </html> | |
| 2771 | |
| 2772 defines a macro called "page" using the ``<html>`` tag and its | |
| 2773 contents. Once defined, macros are stored on the template they're | |
| 2774 defined on in the ``macros`` attribute. You can access them later on | |
| 2775 through the ``templates`` variable, eg. the most common | |
| 2776 ``templates/page/macros/icing`` to access the "page" macro of the | |
| 2777 "page" template. | |
| 2778 | |
| 2779 **metal:use-macro="path expression"** | |
| 2780 Use a macro, which is identified by the path expression (see above). | |
| 2781 This will replace the current tag with the identified macro contents. | |
| 2782 For example:: | |
| 2783 | |
| 2784 <tal:block metal:use-macro="templates/page/macros/icing"> | |
| 2785 ... | |
| 2786 </tal:block> | |
| 2787 | |
| 2788 will replace the tag and its contents with the "page" macro of the | |
| 2789 "page" template. | |
| 2790 | |
| 2791 **metal:define-slot="slot name"** and **metal:fill-slot="slot name"** | |
| 2792 To define *dynamic* parts of the macro, you define "slots" which may | |
| 2793 be filled when the macro is used with a *use-macro* command. For | |
| 2794 example, the ``templates/page/macros/icing`` macro defines a slot like | |
| 2795 so:: | |
| 2796 | |
| 2797 <title metal:define-slot="head_title">title goes here</title> | |
| 2798 | |
| 2799 In your *use-macro* command, you may now use a *fill-slot* command | |
| 2800 like this:: | |
| 2801 | |
| 2802 <title metal:fill-slot="head_title">My Title</title> | |
| 2803 | |
| 2804 where the tag that fills the slot completely replaces the one defined | |
| 2805 as the slot in the macro. | |
| 2806 | |
| 2807 Note that you may not mix `METAL`_ and `TAL`_ commands on the same tag, but | |
| 2808 TAL commands may be used freely inside METAL-using tags (so your | |
| 2809 *fill-slots* tags may have all manner of TAL inside them). | |
| 2810 | |
| 2811 .. _METAL: | |
| 2812 .. _Macro Expansion Template Attribute Language: | |
| 2813 https://pagetemplates.readthedocs.io/en/latest/history/TALESSpecification13.html | |
| 2814 | |
| 2815 Information available to templates | |
| 2816 ---------------------------------- | |
| 2817 | |
| 2818 This is implemented by ``roundup.cgi.templating.RoundupPageTemplate`` | |
| 2819 | |
| 2820 The following variables are available to templates. | |
| 2821 | |
| 2822 **context** | |
| 2823 The current context. This is either None, a `hyperdb class wrapper`_ | |
| 2824 or a `hyperdb item wrapper`_ | |
| 2825 | |
| 2826 **request** | |
| 2827 Includes information about the current request, including: | |
| 2828 | |
| 2829 - the current index information (``filterspec``, ``filter`` | |
| 2830 args, ``properties``, etc) parsed out of the form. | |
| 2831 - methods for easy filterspec link generation | |
| 2832 - "form" | |
| 2833 The current CGI form information as a mapping of form argument name | |
| 2834 to value (specifically a cgi.FieldStorage) | |
| 2835 - "env" the CGI environment variables | |
| 2836 - "base" the base URL for this instance | |
| 2837 - "user" a HTMLItem instance for the current user | |
| 2838 - "language" as determined by the browser or config | |
| 2839 - "classname" the current classname (possibly None) | |
| 2840 - "template" the current template (suffix, also possibly None) | |
| 2841 **config** | |
| 2842 This variable holds all the values defined in the tracker config.ini | |
| 2843 file (eg. TRACKER_NAME, etc.) | |
| 2844 **db** | |
| 2845 The current database, used to access arbitrary database items. | |
| 2846 **templates** | |
| 2847 Access to all the tracker templates by name. Used mainly in | |
| 2848 *use-macro* commands. | |
| 2849 **utils** | |
| 2850 This variable makes available some utility functions like batching. | |
| 2851 **nothing** | |
| 2852 This is a special variable - if an expression evaluates to this, then | |
| 2853 the tag (in the case of a ``tal:replace``), its contents (in the case | |
| 2854 of ``tal:content``) or some attributes (in the case of | |
| 2855 ``tal:attributes``) will not appear in the the output. So, for | |
| 2856 example:: | |
| 2857 | |
| 2858 <span tal:attributes="class nothing">Hello, World!</span> | |
| 2859 | |
| 2860 would result in:: | |
| 2861 | |
| 2862 <span>Hello, World!</span> | |
| 2863 | |
| 2864 **default** | |
| 2865 Also a special variable - if an expression evaluates to this, then the | |
| 2866 existing HTML in the template will not be replaced or removed, it will | |
| 2867 remain. So:: | |
| 2868 | |
| 2869 <span tal:replace="default">Hello, World!</span> | |
| 2870 | |
| 2871 would result in:: | |
| 2872 | |
| 2873 <span>Hello, World!</span> | |
| 2874 | |
| 2875 **true**, **false** | |
| 2876 Boolean constants that may be used in `templating expressions`_ | |
| 2877 instead of ``python:1`` and ``python:0``. | |
| 2878 **i18n** | |
| 2879 Internationalization service, providing two string translation methods: | |
| 2880 | |
| 2881 **gettext** (*message*) | |
| 2882 Return the localized translation of message | |
| 2883 **ngettext** (*singular*, *plural*, *number*) | |
| 2884 Like ``gettext()``, but consider plural forms. If a translation | |
| 2885 is found, apply the plural formula to *number*, and return the | |
| 2886 resulting message (some languages have more than two plural forms). | |
| 2887 If no translation is found, return singular if *number* is 1; | |
| 2888 return plural otherwise. | |
| 2889 | |
| 2890 The context variable | |
| 2891 ~~~~~~~~~~~~~~~~~~~~ | |
| 2892 | |
| 2893 The *context* variable is one of three things based on the current | |
| 2894 context (see `determining web context`_ for how we figure this out): | |
| 2895 | |
| 2896 1. if we're looking at a "home" page, then it's None | |
| 2897 2. if we're looking at a specific hyperdb class, it's a | |
| 2898 `hyperdb class wrapper`_. | |
| 2899 3. if we're looking at a specific hyperdb item, it's a | |
| 2900 `hyperdb item wrapper`_. | |
| 2901 | |
| 2902 If the context is not None, we can access the properties of the class or | |
| 2903 item. The only real difference between cases 2 and 3 above are: | |
| 2904 | |
| 2905 1. the properties may have a real value behind them, and this will | |
| 2906 appear if the property is displayed through ``context/property`` or | |
| 2907 ``context/property/field``. | |
| 2908 2. the context's "id" property will be a false value in the second case, | |
| 2909 but a real, or true value in the third. Thus we can determine whether | |
| 2910 we're looking at a real item from the hyperdb by testing | |
| 2911 "context/id". | |
| 2912 | |
| 2913 Hyperdb class wrapper | |
| 2914 ::::::::::::::::::::: | |
| 2915 | |
| 2916 This is implemented by the ``roundup.cgi.templating.HTMLClass`` | |
| 2917 class. | |
| 2918 | |
| 2919 This wrapper object provides access to a hyperdb class. It is used | |
| 2920 primarily in both index view and new item views, but it's also usable | |
| 2921 anywhere else that you wish to access information about a class, or the | |
| 2922 items of a class, when you don't have a specific item of that class in | |
| 2923 mind. | |
| 2924 | |
| 2925 We allow access to properties. There will be no "id" property. The value | |
| 2926 accessed through the property will be the current value of the same name | |
| 2927 from the CGI form. | |
| 2928 | |
| 2929 There are several methods available on these wrapper objects: | |
| 2930 | |
| 2931 =========== ============================================================= | |
| 2932 Method Description | |
| 2933 =========== ============================================================= | |
| 2934 properties return a `hyperdb property wrapper`_ for all of this class's | |
| 2935 properties that are searchable by the user. You can use | |
| 2936 the argument cansearch=False to get all properties. | |
| 2937 list lists all of the active (not retired) items in the class. | |
| 2938 csv return the items of this class as a chunk of CSV text. | |
| 2939 propnames lists the names of the properties of this class. | |
| 2940 filter lists of items from this class, filtered and sorted. Two | |
| 2941 options are available for sorting: | |
| 2942 | |
| 2943 1. by the current *request* filterspec/filter/sort/group args | |
| 2944 2. by the "filterspec", "sort" and "group" keyword args. | |
| 2945 "filterspec" is ``{propname: value(s)}``. "sort" and | |
| 2946 "group" are an optionally empty list ``[(dir, prop)]`` | |
| 2947 where dir is '+', '-' or None | |
| 2948 and prop is a prop name or None. | |
| 2949 | |
| 2950 The propname in filterspec and prop in a sort/group spec | |
| 2951 may be transitive, i.e., it may contain properties of | |
| 2952 the form link.link.link.name. | |
| 2953 | |
| 2954 eg. All issues with a priority of "1" with messages added in | |
| 2955 the last week, sorted by activity date: | |
| 2956 ``issue.filter(filterspec={"priority": "1", | |
| 2957 'messages.creation' : '.-1w;'}, sort=[('activity', '+')])`` | |
| 2958 | |
| 2959 Note that when searching for Link and Multilink values, the | |
| 2960 special value '-1' searches for empty Link or Multilink | |
| 2961 values. For both, Links and Multilinks, multiple values | |
| 2962 given in a filter call are combined with 'OR' by default. | |
| 2963 For Multilinks a postfix expression syntax using negative ID | |
| 2964 numbers (as strings) as operators is supported. Each | |
| 2965 non-negative number (or '-1') is pushed on an operand stack. | |
| 2966 A negative number pops the required number of arguments from | |
| 2967 the stack, applies the operator, and pushes the result. The | |
| 2968 following operators are supported: | |
| 2969 | |
| 2970 - '-2' stands for 'NOT' and takes one argument | |
| 2971 - '-3' stands for 'AND' and takes two arguments | |
| 2972 - '-4' stands for 'OR' and takes two arguments | |
| 2973 | |
| 2974 Note that this special handling of ID arguments is applied only | |
| 2975 when a negative number smaller than -1 is encountered as an ID | |
| 2976 in the filter call. Otherwise the implicit OR default | |
| 2977 applies. | |
| 2978 Examples of using Multilink expressions would be | |
| 2979 | |
| 2980 - '1', '2', '-4', '3', '4', '-4', '-3' | |
| 2981 would search for IDs (1 or 2) and (3 or 4) | |
| 2982 - '-1' '-2' would search for all non-empty Multilinks | |
| 2983 | |
| 2984 filter_sql **Only in SQL backends** | |
| 2985 | |
| 2986 Lists the items that match the SQL provided. The SQL is a | |
| 2987 complete "select" statement. | |
| 2988 | |
| 2989 The SQL select must include the item id as the first column. | |
| 2990 | |
| 2991 This function **does not** filter out retired items, add | |
| 2992 on a where clause "__retired__ <> 1" if you don't want | |
| 2993 retired nodes. | |
| 2994 | |
| 2995 classhelp display a link to a javascript popup containing this class' | |
| 2996 "help" template. | |
| 2997 | |
| 2998 This generates a link to a popup window which displays the | |
| 2999 properties indicated by "properties" of the class named by | |
| 3000 "classname". The "properties" should be a comma-separated list | |
| 3001 (eg. 'id,name,description'). Properties defaults to all the | |
| 3002 properties of a class (excluding id, creator, created and | |
| 3003 activity). | |
| 3004 | |
| 3005 You may optionally override the "label" displayed, the "width", | |
| 3006 the "height", the number of items per page ("pagesize") and | |
| 3007 the field on which the list is sorted ("sort"). | |
| 3008 | |
| 3009 With the "filter" arg it is possible to specify a filter for | |
| 3010 which items are supposed to be displayed. It has to be of | |
| 3011 the format "<field>=<values>;<field>=<values>;...". | |
| 3012 | |
| 3013 The popup window will be resizable and scrollable. | |
| 3014 | |
| 3015 If the "property" arg is given, it's passed through to the | |
| 3016 javascript help_window function. This allows updating of a | |
| 3017 property in the calling HTML page. | |
| 3018 | |
| 3019 If the "form" arg is given, it's passed through to the | |
| 3020 javascript help_window function - it's the name of the form | |
| 3021 the "property" belongs to. | |
| 3022 | |
| 3023 submit generate a submit button (and action and @csrf hidden elements) | |
| 3024 renderWith render this class with the given template. | |
| 3025 history returns 'New node - no history' :) | |
| 3026 is_edit_ok is the user allowed to Edit the current class? | |
| 3027 is_view_ok is the user allowed to View the current class? | |
| 3028 =========== ============================================================= | |
| 3029 | |
| 3030 Note that if you have a property of the same name as one of the above | |
| 3031 methods, you'll need to access it using a python "item access" | |
| 3032 expression. For example:: | |
| 3033 | |
| 3034 python:context['list'] | |
| 3035 | |
| 3036 will access the "list" property, rather than the list method. | |
| 3037 | |
| 3038 | |
| 3039 Hyperdb item wrapper | |
| 3040 :::::::::::::::::::: | |
| 3041 | |
| 3042 This is implemented by the ``roundup.cgi.templating.HTMLItem`` | |
| 3043 class. | |
| 3044 | |
| 3045 This wrapper object provides access to a hyperdb item. | |
| 3046 | |
| 3047 We allow access to properties. There will be no "id" property. The value | |
| 3048 accessed through the property will be the current value of the same name | |
| 3049 from the CGI form. | |
| 3050 | |
| 3051 There are several methods available on these wrapper objects: | |
| 3052 | |
| 3053 =============== ======================================================== | |
| 3054 Method Description | |
| 3055 =============== ======================================================== | |
| 3056 submit generate a submit button (and action and @csrf hidden elements) | |
| 3057 journal return the journal of the current item (**not | |
| 3058 implemented**) | |
| 3059 history render the journal of the current item as | |
| 3060 HTML. By default properties marked as "quiet" (see | |
| 3061 `design documentation`_) are not shown unless the | |
| 3062 function is called with the showall=True parameter. | |
| 3063 Properties that are not Viewable to the user are not | |
| 3064 shown. | |
| 3065 renderQueryForm specific to the "query" class - render the search form | |
| 3066 for the query | |
| 3067 hasPermission specific to the "user" class - determine whether the | |
| 3068 user has a Permission. The signature is:: | |
| 3069 | |
| 3070 hasPermission(self, permission, [classname=], | |
| 3071 [property=], [itemid=]) | |
| 3072 | |
| 3073 where the classname defaults to the current context. | |
| 3074 hasRole specific to the "user" class - determine whether the | |
| 3075 user has a Role. The signature is:: | |
| 3076 | |
| 3077 hasRole(self, rolename) | |
| 3078 | |
| 3079 is_edit_ok is the user allowed to Edit the current item? | |
| 3080 is_view_ok is the user allowed to View the current item? | |
| 3081 is_retired is the item retired? | |
| 3082 download_url generate a url-quoted link for download of FileClass | |
| 3083 item contents (ie. file<id>/<name>) | |
| 3084 copy_url generate a url-quoted link for creating a copy | |
| 3085 of this item. By default, the copy will acquire | |
| 3086 all properties of the current item except for | |
| 3087 ``messages`` and ``files``. This can be overridden | |
| 3088 by passing ``exclude`` argument which contains a list | |
| 3089 (or any iterable) of property names that shall not be | |
| 3090 copied. Database-driven properties like ``id`` or | |
| 3091 ``activity`` cannot be copied. | |
| 3092 =============== ======================================================== | |
| 3093 | |
| 3094 Note that if you have a property of the same name as one of the above | |
| 3095 methods, you'll need to access it using a python "item access" | |
| 3096 expression. For example:: | |
| 3097 | |
| 3098 python:context['journal'] | |
| 3099 | |
| 3100 will access the "journal" property, rather than the journal method. | |
| 3101 | |
| 3102 | |
| 3103 Hyperdb property wrapper | |
| 3104 :::::::::::::::::::::::: | |
| 3105 | |
| 3106 This is implemented by subclasses of the | |
| 3107 ``roundup.cgi.templating.HTMLProperty`` class (``HTMLStringProperty``, | |
| 3108 ``HTMLNumberProperty``, and so on). | |
| 3109 | |
| 3110 This wrapper object provides access to a single property of a class. Its | |
| 3111 value may be either: | |
| 3112 | |
| 3113 1. if accessed through a `hyperdb item wrapper`_, then it's a value from | |
| 3114 the hyperdb | |
| 3115 2. if access through a `hyperdb class wrapper`_, then it's a value from | |
| 3116 the CGI form | |
| 3117 | |
| 3118 | |
| 3119 The property wrapper has some useful attributes: | |
| 3120 | |
| 3121 =============== ======================================================== | |
| 3122 Attribute Description | |
| 3123 =============== ======================================================== | |
| 3124 _name the name of the property | |
| 3125 _value the value of the property if any - this is the actual | |
| 3126 value retrieved from the hyperdb for this property | |
| 3127 =============== ======================================================== | |
| 3128 | |
| 3129 There are several methods available on these wrapper objects: | |
| 3130 | |
| 3131 =========== ================================================================ | |
| 3132 Method Description | |
| 3133 =========== ================================================================ | |
| 3134 plain render a "plain" representation of the property. This method | |
| 3135 may take two arguments: | |
| 3136 | |
| 3137 escape | |
| 3138 If true, escape the text so it is HTML safe (default: no). The | |
| 3139 reason this defaults to off is that text is usually escaped | |
| 3140 at a later stage by the TAL commands, unless the "structure" | |
| 3141 option is used in the template. The following ``tal:content`` | |
| 3142 expressions are all equivalent:: | |
| 3143 | |
| 3144 "structure python:msg.content.plain(escape=1)" | |
| 3145 "python:msg.content.plain()" | |
| 3146 "msg/content/plain" | |
| 3147 "msg/content" | |
| 3148 | |
| 3149 Usually you'll only want to use the escape option in a | |
| 3150 complex expression. | |
| 3151 | |
| 3152 hyperlink | |
| 3153 If true, turn URLs, email addresses and hyperdb item | |
| 3154 designators in the text into hyperlinks (default: no). Note | |
| 3155 that you'll need to use the "structure" TAL option if you | |
| 3156 want to use this ``tal:content`` expression:: | |
| 3157 | |
| 3158 "structure python:msg.content.plain(hyperlink=1)" | |
| 3159 | |
| 3160 The text is automatically HTML-escaped before the hyperlinking | |
| 3161 transformation done in the plain() method. | |
| 3162 | |
| 3163 hyperlinked The same as msg.content.plain(hyperlink=1), but nicer:: | |
| 3164 | |
| 3165 "structure msg/content/hyperlinked" | |
| 3166 | |
| 3167 field render an appropriate form edit field for the property - for | |
| 3168 most types this is a text entry box, but for Booleans it's a | |
| 3169 tri-state yes/no/neither selection. This method may take some | |
| 3170 arguments: | |
| 3171 | |
| 3172 size | |
| 3173 Sets the width in characters of the edit field | |
| 3174 | |
| 3175 format (Date properties only) | |
| 3176 Sets the format of the date in the field - uses the same | |
| 3177 format string argument as supplied to the ``pretty`` method | |
| 3178 below. | |
| 3179 | |
| 3180 popcal (Date properties only) | |
| 3181 Include the Javascript-based popup calendar for date | |
| 3182 selection. Defaults to on. | |
| 3183 | |
| 3184 stext only on String properties - render the value of the property | |
| 3185 as StructuredText (requires the StructureText module to be | |
| 3186 installed separately) | |
| 3187 multiline only on String properties - render a multiline form edit | |
| 3188 field for the property | |
| 3189 email only on String properties - render the value of the property | |
| 3190 as an obscured email address | |
| 3191 url_quote only on String properties. It quotes any characters in the | |
| 3192 string so it is safe to use in a url. E.G. a space is | |
| 3193 replaced with %20. | |
| 3194 confirm only on Password properties - render a second form edit field | |
| 3195 for the property, used for confirmation that the user typed | |
| 3196 the password correctly. Generates a field with name | |
| 3197 "name:confirm". | |
| 3198 now only on Date properties - return the current date as a new | |
| 3199 property | |
| 3200 reldate only on Date properties - render the interval between the date | |
| 3201 and now | |
| 3202 local only on Date properties - return this date as a new property | |
| 3203 with some timezone offset, for example:: | |
| 3204 | |
| 3205 python:context.creation.local(10) | |
| 3206 | |
| 3207 will render the date with a +10 hour offset. | |
| 3208 pretty Date properties - render the date as "dd Mon YYYY" (eg. "19 | |
| 3209 Mar 2004"). Takes an optional format argument, for example:: | |
| 3210 | |
| 3211 python:context.activity.pretty('%Y-%m-%d') | |
| 3212 | |
| 3213 Will format as "2004-03-19" instead. | |
| 3214 | |
| 3215 Interval properties - render the interval in a pretty | |
| 3216 format (eg. "yesterday"). The format arguments are those used | |
| 3217 in the standard ``strftime`` call (see the `Python Library | |
| 3218 Reference: time module`__) | |
| 3219 | |
| 3220 Number properties - takes a printf style format argument | |
| 3221 (default: '%0.3f') and formats the number accordingly. | |
| 3222 If the value can't be converted, '' is returned if the | |
| 3223 value is ``None`` otherwise it is converted to a string. | |
| 3224 popcal Generate a link to a popup calendar which may be used to | |
| 3225 edit the date field, for example:: | |
| 3226 | |
| 3227 <span tal:replace="structure context/due/popcal" /> | |
| 3228 | |
| 3229 you still need to include the ``field`` for the property, so | |
| 3230 typically you'd have:: | |
| 3231 | |
| 3232 <span tal:replace="structure context/due/field" /> | |
| 3233 <span tal:replace="structure context/due/popcal" /> | |
| 3234 | |
| 3235 menu only on Link and Multilink properties - render a form select | |
| 3236 list for this property. Takes a number of optional arguments | |
| 3237 | |
| 3238 size | |
| 3239 is used to limit the length of the list labels | |
| 3240 height | |
| 3241 is used to set the <select> tag's "size" attribute | |
| 3242 showid | |
| 3243 includes the item ids in the list labels | |
| 3244 additional | |
| 3245 lists properties which should be included in the label | |
| 3246 sort_on | |
| 3247 indicates the property to sort the list on as (direction, | |
| 3248 (direction, property) where direction is '+' or '-'. A | |
| 3249 single string with the direction prepended may be used. | |
| 3250 For example: ('-', 'order'), '+name'. | |
| 3251 value | |
| 3252 gives a default value to preselect in the menu | |
| 3253 | |
| 3254 The remaining keyword arguments are used as conditions for | |
| 3255 filtering the items in the list - they're passed as the | |
| 3256 "filterspec" argument to a Class.filter() call. For example:: | |
| 3257 | |
| 3258 <span tal:replace="structure context/status/menu" /> | |
| 3259 | |
| 3260 <span tal:replace="python:context.status.menu(order='+name", | |
| 3261 value='chatting', | |
| 3262 filterspec={'status': '1,2,3,4'}" /> | |
| 3263 | |
| 3264 sorted only on Multilink properties - produce a list of the linked | |
| 3265 items sorted by some property, for example:: | |
| 3266 | |
| 3267 python:context.files.sorted('creation') | |
| 3268 | |
| 3269 Will list the files by upload date. While:: | |
| 3270 | |
| 3271 python:context.files.sorted('creation', reverse=True) | |
| 3272 | |
| 3273 Will list the files by upload date in reverse order from | |
| 3274 the prior example. If the property can be unset, you can | |
| 3275 use the ``NoneFirst`` parameter to sort the None/Unset | |
| 3276 values at the front or the end of the list. For example:: | |
| 3277 | |
| 3278 python:context.files.sorted('creation', NoneFirst=True) | |
| 3279 | |
| 3280 will sort files by creation date with files missing a | |
| 3281 creation date at the start of the list. The default for | |
| 3282 ``NoneFirst`` is False so these files will sort at the end | |
| 3283 by default. (Note creation date is never unset, but you | |
| 3284 get the idea.) If you combine NoneFirst with | |
| 3285 ``reverse=True`` the meaning of NoneFirst is inverted: | |
| 3286 True sorts None/unset at the end and False sorts at the | |
| 3287 beginning. | |
| 3288 reverse only on Multilink properties - produce a list of the linked | |
| 3289 items in reverse order | |
| 3290 isset returns True if the property has been set to a value | |
| 3291 =========== ================================================================ | |
| 3292 | |
| 3293 __ https://docs.python.org/2/library/time.html | |
| 3294 | |
| 3295 All of the above functions perform checks for permissions required to | |
| 3296 display or edit the data they are manipulating. The simplest case is | |
| 3297 editing an issue title. Including the expression:: | |
| 3298 | |
| 3299 context/title/field | |
| 3300 | |
| 3301 Will present the user with an edit field, if they have edit permission. If | |
| 3302 not, then they will be presented with a static display if they have view | |
| 3303 permission. If they don't even have view permission, then an error message | |
| 3304 is raised, preventing the display of the page, indicating that they don't | |
| 3305 have permission to view the information. | |
| 3306 | |
| 3307 | |
| 3308 The request variable | |
| 3309 ~~~~~~~~~~~~~~~~~~~~ | |
| 3310 | |
| 3311 This is implemented by the ``roundup.cgi.templating.HTMLRequest`` | |
| 3312 class. | |
| 3313 | |
| 3314 The request variable is packed with information about the current | |
| 3315 request. | |
| 3316 | |
| 3317 .. taken from ``roundup.cgi.templating.HTMLRequest`` docstring | |
| 3318 | |
| 3319 =========== ============================================================ | |
| 3320 Variable Holds | |
| 3321 =========== ============================================================ | |
| 3322 form the CGI form as a cgi.FieldStorage | |
| 3323 env the CGI environment variables | |
| 3324 base the base URL for this tracker | |
| 3325 user a HTMLUser instance for this user | |
| 3326 classname the current classname (possibly None) | |
| 3327 template the current template (suffix, also possibly None) | |
| 3328 form the current CGI form variables in a FieldStorage | |
| 3329 =========== ============================================================ | |
| 3330 | |
| 3331 **Index page specific variables (indexing arguments)** | |
| 3332 | |
| 3333 =========== ============================================================ | |
| 3334 Variable Holds | |
| 3335 =========== ============================================================ | |
| 3336 columns dictionary of the columns to display in an index page | |
| 3337 show a convenience access to columns - request/show/colname will | |
| 3338 be true if the columns should be displayed, false otherwise | |
| 3339 sort index sort columns [(direction, column name)] | |
| 3340 group index grouping properties [(direction, column name)] | |
| 3341 filter properties to filter the index on | |
| 3342 filterspec values to filter the index on (property=value, eg | |
| 3343 ``priority=1`` or ``messages.author=42`` | |
| 3344 search_text text to perform a full-text search on for an index | |
| 3345 =========== ============================================================ | |
| 3346 | |
| 3347 There are several methods available on the request variable: | |
| 3348 | |
| 3349 =============== ======================================================== | |
| 3350 Method Description | |
| 3351 =============== ======================================================== | |
| 3352 description render a description of the request - handle for the | |
| 3353 page title | |
| 3354 indexargs_form render the current index args as form elements | |
| 3355 indexargs_url render the current index args as a URL | |
| 3356 base_javascript render some javascript that is used by other components | |
| 3357 of the templating | |
| 3358 batch run the current index args through a filter and return a | |
| 3359 list of items (see `hyperdb item wrapper`_, and | |
| 3360 `batching`_) | |
| 3361 =============== ======================================================== | |
| 3362 | |
| 3363 The form variable | |
| 3364 ::::::::::::::::: | |
| 3365 | |
| 3366 The form variable is a bit special because it's actually a python | |
| 3367 FieldStorage object. That means that you have two ways to access its | |
| 3368 contents. For example, to look up the CGI form value for the variable | |
| 3369 "name", use the path expression:: | |
| 3370 | |
| 3371 request/form/name/value | |
| 3372 | |
| 3373 or the python expression:: | |
| 3374 | |
| 3375 python:request.form['name'].value | |
| 3376 | |
| 3377 Note the "item" access used in the python case, and also note the | |
| 3378 explicit "value" attribute we have to access. That's because the form | |
| 3379 variables are stored as MiniFieldStorages. If there's more than one | |
| 3380 "name" value in the form, then the above will break since | |
| 3381 ``request/form/name`` is actually a *list* of MiniFieldStorages. So it's | |
| 3382 best to know beforehand what you're dealing with. | |
| 3383 | |
| 3384 | |
| 3385 The db variable | |
| 3386 ~~~~~~~~~~~~~~~ | |
| 3387 | |
| 3388 This is implemented by the ``roundup.cgi.templating.HTMLDatabase`` | |
| 3389 class. | |
| 3390 | |
| 3391 Allows access to all hyperdb classes as attributes of this variable. If | |
| 3392 you want access to the "user" class, for example, you would use:: | |
| 3393 | |
| 3394 db/user | |
| 3395 python:db.user | |
| 3396 | |
| 3397 Also, the current id of the current user is available as | |
| 3398 ``db.getuid()``. This isn't so useful in templates (where you have | |
| 3399 ``request/user``), but it can be useful in detectors or interfaces. | |
| 3400 | |
| 3401 The access results in a `hyperdb class wrapper`_. | |
| 3402 | |
| 3403 | |
| 3404 The templates variable | |
| 3405 ~~~~~~~~~~~~~~~~~~~~~~ | |
| 3406 | |
| 3407 This was implemented by the ``roundup.cgi.templating.Templates`` | |
| 3408 class before 1.4.20. In later versions it is the instance of appropriate | |
| 3409 template engine loader class. | |
| 3410 | |
| 3411 This variable is used to access other templates in expressions and | |
| 3412 template macros. It doesn't have any useful methods defined. The | |
| 3413 templates can be accessed using the following path expression:: | |
| 3414 | |
| 3415 templates/name | |
| 3416 | |
| 3417 or the python expression:: | |
| 3418 | |
| 3419 templates[name] | |
| 3420 | |
| 3421 where "name" is the name of the template you wish to access. The | |
| 3422 template has one useful attribute, namely "macros". To access a specific | |
| 3423 macro (called "macro_name"), use the path expression:: | |
| 3424 | |
| 3425 templates/name/macros/macro_name | |
| 3426 | |
| 3427 or the python expression:: | |
| 3428 | |
| 3429 templates[name].macros[macro_name] | |
| 3430 | |
| 3431 The repeat variable | |
| 3432 ~~~~~~~~~~~~~~~~~~~ | |
| 3433 | |
| 3434 The repeat variable holds an entry for each active iteration. That is, if | |
| 3435 you have a ``tal:repeat="user db/users"`` command, then there will be a | |
| 3436 repeat variable entry called "user". This may be accessed as either:: | |
| 3437 | |
| 3438 repeat/user | |
| 3439 python:repeat['user'] | |
| 3440 | |
| 3441 The "user" entry has a number of methods available for information: | |
| 3442 | |
| 3443 =============== ========================================================= | |
| 3444 Method Description | |
| 3445 =============== ========================================================= | |
| 3446 first True if the current item is the first in the sequence. | |
| 3447 last True if the current item is the last in the sequence. | |
| 3448 even True if the current item is an even item in the sequence. | |
| 3449 odd True if the current item is an odd item in the sequence. | |
| 3450 number Current position in the sequence, starting from 1. | |
| 3451 letter Current position in the sequence as a letter, a through | |
| 3452 z, then aa through zz, and so on. | |
| 3453 Letter Same as letter(), except uppercase. | |
| 3454 roman Current position in the sequence as lowercase roman | |
| 3455 numerals. | |
| 3456 Roman Same as roman(), except uppercase. | |
| 3457 =============== ========================================================= | |
| 3458 | |
| 3459 .. _templating utilities: | |
| 3460 | |
| 3461 The utils variable | |
| 3462 ~~~~~~~~~~~~~~~~~~ | |
| 3463 | |
| 3464 This is implemented by the | |
| 3465 ``roundup.cgi.templating.TemplatingUtils`` class, which may be extended | |
| 3466 with additional methods by extensions_. | |
| 3467 | |
| 3468 =============== ======================================================== | |
| 3469 Method Description | |
| 3470 =============== ======================================================== | |
| 3471 Batch return a batch object using the supplied list | |
| 3472 url_quote quote some text as safe for a URL (ie. space, %, ...) | |
| 3473 html_quote quote some text as safe in HTML (ie. <, >, ...) | |
| 3474 html_calendar renders an HTML calendar used by the | |
| 3475 ``_generic.calendar.html`` template (itself invoked by | |
| 3476 the popupCalendar DateHTMLProperty method | |
| 3477 anti_csrf_nonce returns the random noncue generated for this session | |
| 3478 =============== ======================================================== | |
| 3479 | |
| 3480 | |
| 3481 Batching | |
| 3482 :::::::: | |
| 3483 | |
| 3484 Use Batch to turn a list of items, or item ids of a given class, into a | |
| 3485 series of batches. Its usage is:: | |
| 3486 | |
| 3487 python:utils.Batch(sequence, size, start, end=0, orphan=0, | |
| 3488 overlap=0) | |
| 3489 | |
| 3490 or, to get the current index batch:: | |
| 3491 | |
| 3492 request/batch | |
| 3493 | |
| 3494 The parameters are: | |
| 3495 | |
| 3496 ========= ============================================================== | |
| 3497 Parameter Usage | |
| 3498 ========= ============================================================== | |
| 3499 sequence a list of HTMLItems | |
| 3500 size how big to make the sequence. | |
| 3501 start where to start (0-indexed) in the sequence. | |
| 3502 end where to end (0-indexed) in the sequence. | |
| 3503 orphan if the next batch would contain less items than this value, | |
| 3504 then it is combined with this batch | |
| 3505 overlap the number of items shared between adjacent batches | |
| 3506 ========= ============================================================== | |
| 3507 | |
| 3508 All of the parameters are assigned as attributes on the batch object. In | |
| 3509 addition, it has several more attributes: | |
| 3510 | |
| 3511 =============== ======================================================== | |
| 3512 Attribute Description | |
| 3513 =============== ======================================================== | |
| 3514 start indicates the start index of the batch. *Unlike | |
| 3515 the argument, is a 1-based index (I know, lame)* | |
| 3516 first indicates the start index of the batch *as a 0-based | |
| 3517 index* | |
| 3518 length the actual number of elements in the batch | |
| 3519 sequence_length the length of the original, unbatched, sequence. | |
| 3520 =============== ======================================================== | |
| 3521 | |
| 3522 And several methods: | |
| 3523 | |
| 3524 =============== ======================================================== | |
| 3525 Method Description | |
| 3526 =============== ======================================================== | |
| 3527 previous returns a new Batch with the previous batch settings | |
| 3528 next returns a new Batch with the next batch settings | |
| 3529 propchanged detect if the named property changed on the current item | |
| 3530 when compared to the last item | |
| 3531 =============== ======================================================== | |
| 3532 | |
| 3533 An example of batching:: | |
| 3534 | |
| 3535 <table class="otherinfo"> | |
| 3536 <tr><th colspan="4" class="header">Existing Keywords</th></tr> | |
| 3537 <tr tal:define="keywords db/keyword/list" | |
| 3538 tal:repeat="start python:range(0, len(keywords), 4)"> | |
| 3539 <td tal:define="batch python:utils.Batch(keywords, 4, start)" | |
| 3540 tal:repeat="keyword batch" tal:content="keyword/name"> | |
| 3541 keyword here</td> | |
| 3542 </tr> | |
| 3543 </table> | |
| 3544 | |
| 3545 ... which will produce a table with four columns containing the items of | |
| 3546 the "keyword" class (well, their "name" anyway). | |
| 3547 | |
| 3548 | |
| 3549 Translations | |
| 3550 ~~~~~~~~~~~~ | |
| 3551 | |
| 3552 Should you wish to enable multiple languages in template content that you | |
| 3553 create you'll need to add new locale files in the tracker home under a | |
| 3554 ``locale`` directory. Use the `translation instructions in the | |
| 3555 developer's guide <developers.html#extracting-translatable-messages>`_ to | |
| 3556 create the locale files. | |
| 3557 | |
| 3558 | |
| 3559 Displaying Properties | |
| 3560 --------------------- | |
| 3561 | |
| 3562 Properties appear in the user interface in three contexts: in indices, | |
| 3563 in editors, and as search arguments. For each type of property, there | |
| 3564 are several display possibilities. For example, in an index view, a | |
| 3565 string property may just be printed as a plain string, but in an editor | |
| 3566 view, that property may be displayed in an editable field. | |
| 3567 | |
| 3568 | |
| 3569 Index Views | |
| 3570 ----------- | |
| 3571 | |
| 3572 This is one of the class context views. It is also the default view for | |
| 3573 classes. The template used is "*classname*.index". | |
| 3574 | |
| 3575 | |
| 3576 Index View Specifiers | |
| 3577 ~~~~~~~~~~~~~~~~~~~~~ | |
| 3578 | |
| 3579 An index view specifier (URL fragment) looks like this (whitespace has | |
| 3580 been added for clarity):: | |
| 3581 | |
| 3582 /issue?status=unread,in-progress,resolved& | |
| 3583 keyword=security,ui& | |
| 3584 @group=priority,-status& | |
| 3585 @sort=-activity& | |
| 3586 @filters=status,keyword& | |
| 3587 @columns=title,status,fixer | |
| 3588 | |
| 3589 The index view is determined by two parts of the specifier: the layout | |
| 3590 part and the filter part. The layout part consists of the query | |
| 3591 parameters that begin with colons, and it determines the way that the | |
| 3592 properties of selected items are displayed. The filter part consists of | |
| 3593 all the other query parameters, and it determines the criteria by which | |
| 3594 items are selected for display. The filter part is interactively | |
| 3595 manipulated with the form widgets displayed in the filter section. The | |
| 3596 layout part is interactively manipulated by clicking on the column | |
| 3597 headings in the table. | |
| 3598 | |
| 3599 The filter part selects the union of the sets of items with values | |
| 3600 matching any specified Link properties and the intersection of the sets | |
| 3601 of items with values matching any specified Multilink properties. | |
| 3602 | |
| 3603 The example specifies an index of "issue" items. Only items with a | |
| 3604 "status" of either "unread" or "in-progress" or "resolved" are | |
| 3605 displayed, and only items with "keyword" values including both "security" | |
| 3606 and "ui" are displayed. The items are grouped by priority arranged in | |
| 3607 ascending order and in descending order by status; and within | |
| 3608 groups, sorted by activity, arranged in descending order. The filter | |
| 3609 section shows filters for the "status" and "keyword" properties, and the | |
| 3610 table includes columns for the "title", "status", and "fixer" | |
| 3611 properties. | |
| 3612 | |
| 3613 ============ ============================================================= | |
| 3614 Argument Description | |
| 3615 ============ ============================================================= | |
| 3616 @sort sort by prop name, optionally preceeded with '-' to give | |
| 3617 descending or nothing for ascending sorting. Several | |
| 3618 properties can be specified delimited with comma. | |
| 3619 Internally a search-page using several sort properties may | |
| 3620 use @sort0, @sort1 etc. with option @sortdir0, @sortdir1 | |
| 3621 etc. for the direction of sorting (a non-empty value of | |
| 3622 sortdir0 specifies reverse order). | |
| 3623 @group group by prop name, optionally preceeded with '-' or to sort | |
| 3624 in descending or nothing for ascending order. Several | |
| 3625 properties can be specified delimited with comma. | |
| 3626 Internally a search-page using several grouping properties may | |
| 3627 use @group0, @group1 etc. with option @groupdir0, @groupdir1 | |
| 3628 etc. for the direction of grouping (a non-empty value of | |
| 3629 groupdir0 specifies reverse order). | |
| 3630 @columns selects the columns that should be displayed. Default is | |
| 3631 all. | |
| 3632 @filter indicates which properties are being used in filtering. | |
| 3633 Default is none. | |
| 3634 propname selects the values the item properties given by propname must | |
| 3635 have (very basic search/filter). | |
| 3636 @search_text if supplied, performs a full-text search (message bodies, | |
| 3637 issue titles, etc) | |
| 3638 ============ ============================================================= | |
| 3639 | |
| 3640 | |
| 3641 Searching Views | |
| 3642 --------------- | |
| 3643 | |
| 3644 .. note:: | |
| 3645 if you add a new column to the ``@columns`` form variable potentials | |
| 3646 then you will need to add the column to the appropriate `index views`_ | |
| 3647 template so that it is actually displayed. | |
| 3648 | |
| 3649 This is one of the class context views. The template used is typically | |
| 3650 "*classname*.search". The form on this page should have "search" as its | |
| 3651 ``@action`` variable. The "search" action: | |
| 3652 | |
| 3653 - sets up additional filtering, as well as performing indexed text | |
| 3654 searching | |
| 3655 - sets the ``@filter`` variable correctly | |
| 3656 - saves the query off if ``@query_name`` is set. | |
| 3657 | |
| 3658 The search page should lay out any fields that you wish to allow the | |
| 3659 user to search on. If your schema contains a large number of properties, | |
| 3660 you should be wary of making all of those properties available for | |
| 3661 searching, as this can cause confusion. If the additional properties are | |
| 3662 Strings, consider having their value indexed, and then they will be | |
| 3663 searchable using the full text indexed search. This is both faster, and | |
| 3664 more useful for the end user. | |
| 3665 | |
| 3666 If the search view does specify the "search" ``@action``, then it may also | |
| 3667 provide an additional argument: | |
| 3668 | |
| 3669 ============ ============================================================= | |
| 3670 Argument Description | |
| 3671 ============ ============================================================= | |
| 3672 @query_name if supplied, the index parameters (including @search_text) | |
| 3673 will be saved off as a the query item and registered against | |
| 3674 the user's queries property. Note that the *classic* template | |
| 3675 schema has this ability, but the *minimal* template schema | |
| 3676 does not. | |
| 3677 ============ ============================================================= | |
| 3678 | |
| 3679 | |
| 3680 Item Views | |
| 3681 ---------- | |
| 3682 | |
| 3683 The basic view of a hyperdb item is provided by the "*classname*.item" | |
| 3684 template. It generally has three sections; an "editor", a "spool" and a | |
| 3685 "history" section. | |
| 3686 | |
| 3687 | |
| 3688 Editor Section | |
| 3689 ~~~~~~~~~~~~~~ | |
| 3690 | |
| 3691 The editor section is used to manipulate the item - it may be a static | |
| 3692 display if the user doesn't have permission to edit the item. | |
| 3693 | |
| 3694 Here's an example of a basic editor template (this is the default | |
| 3695 "classic" template issue item edit form - from the "issue.item.html" | |
| 3696 template):: | |
| 3697 | |
| 3698 <table class="form"> | |
| 3699 <tr> | |
| 3700 <th>Title</th> | |
| 3701 <td colspan="3" tal:content="structure python:context.title.field(size=60)">title</td> | |
| 3702 </tr> | |
| 3703 | |
| 3704 <tr> | |
| 3705 <th>Priority</th> | |
| 3706 <td tal:content="structure context/priority/menu">priority</td> | |
| 3707 <th>Status</th> | |
| 3708 <td tal:content="structure context/status/menu">status</td> | |
| 3709 </tr> | |
| 3710 | |
| 3711 <tr> | |
| 3712 <th>Superseder</th> | |
| 3713 <td> | |
| 3714 <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" /> | |
| 3715 <span tal:replace="structure python:db.issue.classhelp('id,title')" /> | |
| 3716 <span tal:condition="context/superseder"> | |
| 3717 <br>View: <span tal:replace="structure python:context.superseder.link(showid=1)" /> | |
| 3718 </span> | |
| 3719 </td> | |
| 3720 <th>Nosy List</th> | |
| 3721 <td> | |
| 3722 <span tal:replace="structure context/nosy/field" /> | |
| 3723 <span tal:replace="structure python:db.user.classhelp('username,realname,address,phone')" /> | |
| 3724 </td> | |
| 3725 </tr> | |
| 3726 | |
| 3727 <tr> | |
| 3728 <th>Assigned To</th> | |
| 3729 <td tal:content="structure context/assignedto/menu"> | |
| 3730 assignedto menu | |
| 3731 </td> | |
| 3732 <td> </td> | |
| 3733 <td> </td> | |
| 3734 </tr> | |
| 3735 | |
| 3736 <tr> | |
| 3737 <th>Change Note</th> | |
| 3738 <td colspan="3"> | |
| 3739 <textarea name=":note" wrap="hard" rows="5" cols="60"></textarea> | |
| 3740 </td> | |
| 3741 </tr> | |
| 3742 | |
| 3743 <tr> | |
| 3744 <th>File</th> | |
| 3745 <td colspan="3"><input type="file" name=":file" size="40"></td> | |
| 3746 </tr> | |
| 3747 | |
| 3748 <tr> | |
| 3749 <td> </td> | |
| 3750 <td colspan="3" tal:content="structure context/submit"> | |
| 3751 submit button will go here | |
| 3752 </td> | |
| 3753 </tr> | |
| 3754 </table> | |
| 3755 | |
| 3756 | |
| 3757 When a change is submitted, the system automatically generates a message | |
| 3758 describing the changed properties. As shown in the example, the editor | |
| 3759 template can use the ":note" and ":file" fields, which are added to the | |
| 3760 standard changenote message generated by Roundup. | |
| 3761 | |
| 3762 | |
| 3763 Form values | |
| 3764 ::::::::::: | |
| 3765 | |
| 3766 We have a number of ways to pull properties out of the form in order to | |
| 3767 meet the various needs of: | |
| 3768 | |
| 3769 1. editing the current item (perhaps an issue item) | |
| 3770 2. editing information related to the current item (eg. messages or | |
| 3771 attached files) | |
| 3772 3. creating new information to be linked to the current item (eg. time | |
| 3773 spent on an issue) | |
| 3774 | |
| 3775 In the following, ``<bracketed>`` values are variable, ":" may be one of | |
| 3776 ":" or "@", and other text ("required") is fixed. | |
| 3777 | |
| 3778 Properties are specified as form variables: | |
| 3779 | |
| 3780 ``<propname>`` | |
| 3781 property on the current context item | |
| 3782 | |
| 3783 ``<designator>:<propname>`` | |
| 3784 property on the indicated item (for editing related information) | |
| 3785 | |
| 3786 ``<classname>-<N>:<propname>`` | |
| 3787 property on the Nth new item of classname (generally for creating new | |
| 3788 items to attach to the current item) | |
| 3789 | |
| 3790 Once we have determined the "propname", we check to see if it is one of | |
| 3791 the special form values: | |
| 3792 | |
| 3793 ``@required`` | |
| 3794 The named property values must be supplied or a ValueError will be | |
| 3795 raised. | |
| 3796 | |
| 3797 ``@remove@<propname>=id(s)`` | |
| 3798 The ids will be removed from the multilink property. | |
| 3799 | |
| 3800 ``:add:<propname>=id(s)`` | |
| 3801 The ids will be added to the multilink property. | |
| 3802 | |
| 3803 ``:link:<propname>=<designator>`` | |
| 3804 Used to add a link to new items created during edit. These are | |
| 3805 collected and returned in ``all_links``. This will result in an | |
| 3806 additional linking operation (either Link set or Multilink append) | |
| 3807 after the edit/create is done using ``all_props`` in ``_editnodes``. | |
| 3808 The <propname> on the current item will be set/appended the id of the | |
| 3809 newly created item of class <designator> (where <designator> must be | |
| 3810 <classname>-<N>). | |
| 3811 | |
| 3812 Any of the form variables may be prefixed with a classname or | |
| 3813 designator. | |
| 3814 | |
| 3815 Two special form values are supported for backwards compatibility: | |
| 3816 | |
| 3817 ``:note`` | |
| 3818 create a message (with content, author and date), linked to the | |
| 3819 context item. This is ALWAYS designated "msg-1". | |
| 3820 ``:file`` | |
| 3821 create a file, attached to the current item and any message created by | |
| 3822 :note. This is ALWAYS designated "file-1". | |
| 3823 | |
| 3824 | |
| 3825 Spool Section | |
| 3826 ~~~~~~~~~~~~~ | |
| 3827 | |
| 3828 The spool section lists related information like the messages and files | |
| 3829 of an issue. | |
| 3830 | |
| 3831 TODO | |
| 3832 | |
| 3833 | |
| 3834 History Section | |
| 3835 ~~~~~~~~~~~~~~~ | |
| 3836 | |
| 3837 The final section displayed is the history of the item - its database | |
| 3838 journal. This is generally generated with the template:: | |
| 3839 | |
| 3840 <tal:block tal:replace="structure context/history" /> | |
| 3841 | |
| 3842 or:: | |
| 3843 | |
| 3844 <tal:block | |
| 3845 tal:replace="structure python:context.history(showall=True)" /> | |
| 3846 | |
| 3847 if you want to show history entries for quiet properties. | |
| 3848 | |
| 3849 *To be done:* | |
| 3850 | |
| 3851 *The actual history entries of the item may be accessed for manual | |
| 3852 templating through the "journal" method of the item*:: | |
| 3853 | |
| 3854 <tal:block tal:repeat="entry context/journal"> | |
| 3855 a journal entry | |
| 3856 </tal:block> | |
| 3857 | |
| 3858 *where each journal entry is an HTMLJournalEntry.* | |
| 3859 | |
| 3860 | |
| 3861 Defining new web actions | |
| 3862 ------------------------ | |
| 3863 | |
| 3864 You may define new actions to be triggered by the ``@action`` form variable. | |
| 3865 These are added to the tracker ``extensions`` directory and registered | |
| 3866 using ``instance.registerAction``. | |
| 3867 | |
| 3868 All the existing Actions are defined in ``roundup.cgi.actions``. | |
| 3869 | |
| 3870 Adding action classes takes three steps; first you `define the new | |
| 3871 action class`_, then you `register the action class`_ with the cgi | |
| 3872 interface so it may be triggered by the ``@action`` form variable. | |
| 3873 Finally you `use the new action`_ in your HTML form. | |
| 3874 | |
| 3875 See `setting up a "wizard" (or "druid") for controlled adding of | |
| 3876 issues | |
| 3877 <customizing.html#setting-up-a-wizard-or-druid-for-controlled-adding-of-issues>`_ for an example. | |
| 3878 | |
| 3879 | |
| 3880 Define the new action class | |
| 3881 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 3882 | |
| 3883 Create a new action class in your tracker's ``extensions`` directory, for | |
| 3884 example ``myaction.py``:: | |
| 3885 | |
| 3886 from roundup.cgi.actions import Action | |
| 3887 | |
| 3888 class MyAction(Action): | |
| 3889 def handle(self): | |
| 3890 ''' Perform some action. No return value is required. | |
| 3891 ''' | |
| 3892 | |
| 3893 The *self.client* attribute is an instance of ``roundup.cgi.client.Client``. | |
| 3894 See the docstring of that class for details of what it can do. | |
| 3895 | |
| 3896 The method will typically check the ``self.form`` variable's contents. | |
| 3897 It may then: | |
| 3898 | |
| 3899 - add information to ``self.client._ok_message`` | |
| 3900 or ``self.client._error_message`` (by using ``self.client.add_ok_message`` | |
| 3901 or ``self.client.add_error_message``, respectively) | |
| 3902 - change the ``self.client.template`` variable to alter what the user will see | |
| 3903 next | |
| 3904 - raise Unauthorised, SendStaticFile, SendFile, NotFound or Redirect | |
| 3905 exceptions (import them from roundup.cgi.exceptions) | |
| 3906 | |
| 3907 | |
| 3908 Register the action class | |
| 3909 ~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 3910 | |
| 3911 The class is now written, but isn't available to the user until you register | |
| 3912 it with the following code appended to your ``myaction.py`` file:: | |
| 3913 | |
| 3914 def init(instance): | |
| 3915 instance.registerAction('myaction', myActionClass) | |
| 3916 | |
| 3917 This maps the action name "myaction" to the action class we defined. | |
| 3918 | |
| 3919 | |
| 3920 Use the new action | |
| 3921 ~~~~~~~~~~~~~~~~~~ | |
| 3922 | |
| 3923 In your HTML form, add a hidden form element like so:: | |
| 3924 | |
| 3925 <input type="hidden" name="@action" value="myaction"> | |
| 3926 | |
| 3927 where "myaction" is the name you registered in the previous step. | |
| 3928 | |
| 3929 Actions may return content to the user | |
| 3930 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| 3931 | |
| 3932 Actions generally perform some database manipulation and then pass control | |
| 3933 on to the rendering of a template in the current context (see `Determining | |
| 3934 web context`_ for how that works.) Some actions will want to generate the | |
| 3935 actual content returned to the user. Action methods may return their own | |
| 3936 content string to be displayed to the user, overriding the templating step. | |
| 3937 In this situation, we assume that the content is HTML by default. You may | |
| 3938 override the content type indicated to the user by calling ``setHeader``:: | |
| 3939 | |
| 3940 self.client.setHeader('Content-Type', 'text/csv') | |
| 3941 | |
| 3942 This example indicates that the value sent back to the user is actually | |
| 3943 comma-separated value content (eg. something to be loaded into a | |
| 3944 spreadsheet or database). | |
| 3945 | |
| 3946 | |
| 3947 8-bit character set support in Web interface | |
| 3948 -------------------------------------------- | |
| 3949 | |
| 3950 The web interface uses UTF-8 default. It may be overridden in both forms | |
| 3951 and a browser cookie. | |
| 3952 | |
| 3953 - In forms, use the ``@charset`` variable. | |
| 3954 - To use the cookie override, have the ``roundup_charset`` cookie set. | |
| 3955 | |
| 3956 In both cases, the value is a valid charset name (eg. ``utf-8`` or | |
| 3957 ``kio8-r``). | |
| 3958 | |
| 3959 Inside Roundup, all strings are stored and processed in utf-8. | |
| 3960 Unfortunately, some older browsers do not work properly with | |
| 3961 utf-8-encoded pages (e.g. Netscape Navigator 4 displays wrong | |
| 3962 characters in form fields). This version allows one to change | |
| 3963 the character set for http transfers. To do so, you may add | |
| 3964 the following code to your ``page.html`` template:: | |
| 3965 | |
| 3966 <tal:block define="uri string:${request/base}${request/env/PATH_INFO}"> | |
| 3967 <a tal:attributes="href python:request.indexargs_url(uri, | |
| 3968 {'@charset':'utf-8'})">utf-8</a> | |
| 3969 <a tal:attributes="href python:request.indexargs_url(uri, | |
| 3970 {'@charset':'koi8-r'})">koi8-r</a> | |
| 3971 </tal:block> | |
| 3972 | |
| 3973 (substitute ``koi8-r`` with appropriate charset for your language). | |
| 3974 Charset preference is kept in the browser cookie ``roundup_charset``. | |
| 3975 | |
| 3976 ``meta http-equiv`` lines added to the tracker templates in version 0.6.0 | |
| 3977 should be changed to include actual character set name:: | |
| 3978 | |
| 3979 <meta http-equiv="Content-Type" | |
| 3980 tal:attributes="content string:text/html;; charset=${request/client/charset}" | |
| 3981 /> | |
| 3982 | |
| 3983 The charset is also sent in the http header. | |
| 3984 | |
| 3985 | |
| 3986 | |
| 3987 | |
| 3988 | |
| 3989 Debugging Trackers | |
| 3990 ================== | |
| 3991 | |
| 3992 There are three switches in tracker configs that turn on debugging in | |
| 3993 Roundup: | |
| 3994 | |
| 3995 1. web :: debug | |
| 3996 2. mail :: debug | |
| 3997 3. logging :: level | |
| 3998 | |
| 3999 See the config.ini file or the `tracker configuration`_ section for | |
| 4000 more information. | |
| 4001 | |
| 4002 Additionally, the ``roundup-server.py`` script has its own debugging mode | |
| 4003 in which it reloads edited templates immediately when they are changed, | |
| 4004 rather than requiring a web server restart. | |
| 4005 | |
| 4006 | |
| 4007 .. _`design documentation`: design.html | |
| 4008 .. _change the rate limiting method: rest.html#creating-custom-rate-limits | |
| 4009 .. _`directions in the rest interface documentation`: rest.html#enabling-the-rest-api | |
| 4010 .. _`xmlrpc interface documentation`: xmlrpc.html#through-roundup | |
| 4011 | |
| 4012 .. allow line breaks in term definitions. | |
| 4013 .. |br| raw:: html | |
| 4014 | |
| 4015 <br/> | |
| 4016 |
