Mercurial > p > roundup > code
comparison doc/design.txt @ 1661:b9c1226cb600
Reflowed text to 72 cols...
...made leading whitespace before headings consistent, and got rid of
references to <display> and <property>.
| author | Jean Jordaan <neaj@users.sourceforge.net> |
|---|---|
| date | Mon, 16 Jun 2003 15:27:15 +0000 |
| parents | f5d53a939b67 |
| children | eb3c348676ed |
comparison
equal
deleted
inserted
replaced
| 1659:2c4ec168e72f | 1661:b9c1226cb600 |
|---|---|
| 9 .. contents:: | 9 .. contents:: |
| 10 | 10 |
| 11 Introduction | 11 Introduction |
| 12 --------------- | 12 --------------- |
| 13 | 13 |
| 14 This document presents a description of the components | 14 This document presents a description of the components of the Roundup |
| 15 of the Roundup system and specifies their interfaces and | 15 system and specifies their interfaces and behaviour in sufficient detail |
| 16 behaviour in sufficient detail to guide an implementation. | 16 to guide an implementation. For the philosophy and rationale behind the |
| 17 For the philosophy and rationale behind the Roundup design, | 17 Roundup design, see the first-round Software Carpentry submission for |
| 18 see the first-round Software Carpentry submission for Roundup. | 18 Roundup. This document fleshes out that design as well as specifying |
| 19 This document fleshes out that design as well as specifying | |
| 20 interfaces so that the components can be developed separately. | 19 interfaces so that the components can be developed separately. |
| 21 | 20 |
| 22 | 21 |
| 23 The Layer Cake | 22 The Layer Cake |
| 24 ----------------- | 23 ----------------- |
| 25 | 24 |
| 26 Lots of software design documents come with a picture of | 25 Lots of software design documents come with a picture of a cake. |
| 27 a cake. Everybody seems to like them. I also like cakes | 26 Everybody seems to like them. I also like cakes (i think they are |
| 28 (i think they are tasty). So i, too, shall include | 27 tasty). So I, too, shall include a picture of a cake here:: |
| 29 a picture of a cake here:: | |
| 30 | 28 |
| 31 _________________________________________________________________________ | 29 _________________________________________________________________________ |
| 32 | E-mail Client | Web Browser | Detector Scripts | Shell | | 30 | E-mail Client | Web Browser | Detector Scripts | Shell | |
| 33 |------------------+-----------------+----------------------+-------------| | 31 |------------------+-----------------+----------------------+-------------| |
| 34 | E-mail User | Web User | Detector | Command | | 32 | E-mail User | Web User | Detector | Command | |
| 38 | Hyperdatabase Layer | | 36 | Hyperdatabase Layer | |
| 39 |-------------------------------------------------------------------------| | 37 |-------------------------------------------------------------------------| |
| 40 | Storage Layer | | 38 | Storage Layer | |
| 41 ------------------------------------------------------------------------- | 39 ------------------------------------------------------------------------- |
| 42 | 40 |
| 43 The colourful parts of the cake are part of our system; | 41 The colourful parts of the cake are part of our system; the faint grey |
| 44 the faint grey parts of the cake are external components. | 42 parts of the cake are external components. |
| 45 | 43 |
| 46 I will now proceed to forgo all table manners and | 44 I will now proceed to forgo all table manners and eat from the bottom of |
| 47 eat from the bottom of the cake to the top. You may want | 45 the cake to the top. You may want to stand back a bit so you don't get |
| 48 to stand back a bit so you don't get covered in crumbs. | 46 covered in crumbs. |
| 49 | 47 |
| 50 | 48 |
| 51 Hyperdatabase | 49 Hyperdatabase |
| 52 ------------- | 50 ------------- |
| 53 | 51 |
| 54 The lowest-level component to be implemented is the hyperdatabase. | 52 The lowest-level component to be implemented is the hyperdatabase. The |
| 55 The hyperdatabase is intended to be | 53 hyperdatabase is intended to be a flexible data store that can hold |
| 56 a flexible data store that can hold configurable data in | 54 configurable data in records which we call items. |
| 57 records which we call items. | 55 |
| 58 | 56 The hyperdatabase is implemented on top of the storage layer, an |
| 59 The hyperdatabase is implemented on top of the storage layer, | 57 external module for storing its data. The storage layer could be a |
| 60 an external module for storing its data. The storage layer could | 58 third-party RDBMS; for a "batteries-included" distribution, implementing |
| 61 be a third-party RDBMS; for a "batteries-included" distribution, | 59 the hyperdatabase on the standard bsddb module is suggested. |
| 62 implementing the hyperdatabase on the standard bsddb | |
| 63 module is suggested. | |
| 64 | 60 |
| 65 Dates and Date Arithmetic | 61 Dates and Date Arithmetic |
| 66 ~~~~~~~~~~~~~~~~~~~~~~~~~ | 62 ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 67 | 63 |
| 68 Before we get into the hyperdatabase itself, we need a | 64 Before we get into the hyperdatabase itself, we need a way of handling |
| 69 way of handling dates. The hyperdatabase module provides | 65 dates. The hyperdatabase module provides Timestamp objects for |
| 70 Timestamp objects for | 66 representing date-and-time stamps and Interval objects for representing |
| 71 representing date-and-time stamps and Interval objects for | 67 date-and-time intervals. |
| 72 representing date-and-time intervals. | 68 |
| 73 | 69 As strings, date-and-time stamps are specified with the date in |
| 74 As strings, date-and-time stamps are specified with | 70 international standard format (``yyyy-mm-dd``) joined to the time |
| 75 the date in international standard format | 71 (``hh:mm:ss``) by a period "``.``". Dates in this form can be easily |
| 76 (``yyyy-mm-dd``) | 72 compared and are fairly readable when printed. An example of a valid |
| 77 joined to the time (``hh:mm:ss``) | 73 stamp is "``2000-06-24.13:03:59``". We'll call this the "full date |
| 78 by a period "``.``". Dates in | 74 format". When Timestamp objects are printed as strings, they appear in |
| 79 this form can be easily compared and are fairly readable | 75 the full date format with the time always given in GMT. The full date |
| 80 when printed. An example of a valid stamp is | 76 format is always exactly 19 characters long. |
| 81 "``2000-06-24.13:03:59``". | 77 |
| 82 We'll call this the "full date format". When Timestamp objects are | 78 For user input, some partial forms are also permitted: the whole time or |
| 83 printed as strings, they appear in the full date format with | 79 just the seconds may be omitted; and the whole date may be omitted or |
| 84 the time always given in GMT. The full date format is always | 80 just the year may be omitted. If the time is given, the time is |
| 85 exactly 19 characters long. | 81 interpreted in the user's local time zone. The Date constructor takes |
| 86 | 82 care of these conversions. In the following examples, suppose that |
| 87 For user input, some partial forms are also permitted: | 83 ``yyyy`` is the current year, ``mm`` is the current month, and ``dd`` is |
| 88 the whole time or just the seconds may be omitted; and the whole date | 84 the current day of the month; and suppose that the user is on Eastern |
| 89 may be omitted or just the year may be omitted. If the time is given, | 85 Standard Time. |
| 90 the time is interpreted in the user's local time zone. | |
| 91 The Date constructor takes care of these conversions. | |
| 92 In the following examples, suppose that ``yyyy`` is the current year, | |
| 93 ``mm`` is the current month, and ``dd`` is the current | |
| 94 day of the month; and suppose that the user is on Eastern Standard Time. | |
| 95 | 86 |
| 96 - "2000-04-17" means <Date 2000-04-17.00:00:00> | 87 - "2000-04-17" means <Date 2000-04-17.00:00:00> |
| 97 - "01-25" means <Date yyyy-01-25.00:00:00> | 88 - "01-25" means <Date yyyy-01-25.00:00:00> |
| 98 - "2000-04-17.03:45" means <Date 2000-04-17.08:45:00> | 89 - "2000-04-17.03:45" means <Date 2000-04-17.08:45:00> |
| 99 - "08-13.22:13" means <Date yyyy-08-14.03:13:00> | 90 - "08-13.22:13" means <Date yyyy-08-14.03:13:00> |
| 103 - "8:47:11" means | 94 - "8:47:11" means |
| 104 - <Date yyyy-mm-dd.13:47:11> | 95 - <Date yyyy-mm-dd.13:47:11> |
| 105 - the special date "." means "right now" | 96 - the special date "." means "right now" |
| 106 | 97 |
| 107 | 98 |
| 108 Date intervals are specified using the suffixes | 99 Date intervals are specified using the suffixes "y", "m", and "d". The |
| 109 "y", "m", and "d". The suffix "w" (for "week") means 7 days. | 100 suffix "w" (for "week") means 7 days. Time intervals are specified in |
| 110 Time intervals are specified in hh:mm:ss format (the seconds | 101 hh:mm:ss format (the seconds may be omitted, but the hours and minutes |
| 111 may be omitted, but the hours and minutes may not). | 102 may not). |
| 112 | 103 |
| 113 - "3y" means three years | 104 - "3y" means three years |
| 114 - "2y 1m" means two years and one month | 105 - "2y 1m" means two years and one month |
| 115 - "1m 25d" means one month and 25 days | 106 - "1m 25d" means one month and 25 days |
| 116 - "2w 3d" means two weeks and three days | 107 - "2w 3d" means two weeks and three days |
| 117 - "1d 2:50" means one day, two hours, and 50 minutes | 108 - "1d 2:50" means one day, two hours, and 50 minutes |
| 118 - "14:00" means 14 hours | 109 - "14:00" means 14 hours |
| 119 - "0:04:33" means four minutes and 33 seconds | 110 - "0:04:33" means four minutes and 33 seconds |
| 120 | 111 |
| 121 | 112 |
| 122 The Date class should understand simple date expressions of the form | 113 The Date class should understand simple date expressions of the form |
| 123 *stamp* ``+`` *interval* and *stamp* ``-`` *interval*. | 114 *stamp* ``+`` *interval* and *stamp* ``-`` *interval*. When adding or |
| 124 When adding or subtracting intervals involving months or years, the | 115 subtracting intervals involving months or years, the components are |
| 125 components are handled separately. For example, when evaluating | 116 handled separately. For example, when evaluating "``2000-06-25 + 1m |
| 126 "``2000-06-25 + 1m 10d``", we first add one month to | 117 10d``", we first add one month to get 2000-07-25, then add 10 days to |
| 127 get 2000-07-25, then add 10 days to get | 118 get 2000-08-04 (rather than trying to decide whether 1m 10d means 38 or |
| 128 2000-08-04 (rather than trying to decide whether | 119 40 or 41 days). |
| 129 1m 10d means 38 or 40 or 41 days). | |
| 130 | 120 |
| 131 Here is an outline of the Date and Interval classes:: | 121 Here is an outline of the Date and Interval classes:: |
| 132 | 122 |
| 133 class Date: | 123 class Date: |
| 134 def __init__(self, spec, offset): | 124 def __init__(self, spec, offset): |
| 165 """Return this interval as a string.""" | 155 """Return this interval as a string.""" |
| 166 | 156 |
| 167 | 157 |
| 168 | 158 |
| 169 Here are some examples of how these classes would behave in practice. | 159 Here are some examples of how these classes would behave in practice. |
| 170 For the following examples, assume that we are on Eastern Standard | 160 For the following examples, assume that we are on Eastern Standard Time |
| 171 Time and the current local time is 19:34:02 on 25 June 2000:: | 161 and the current local time is 19:34:02 on 25 June 2000:: |
| 172 | 162 |
| 173 >>> Date(".") | 163 >>> Date(".") |
| 174 <Date 2000-06-26.00:34:02> | 164 <Date 2000-06-26.00:34:02> |
| 175 >>> _.local(-5) | 165 >>> _.local(-5) |
| 176 "2000-06-25.19:34:02" | 166 "2000-06-25.19:34:02" |
| 190 <Date 2000-06-07.00:34:02> | 180 <Date 2000-06-07.00:34:02> |
| 191 | 181 |
| 192 Items and Classes | 182 Items and Classes |
| 193 ~~~~~~~~~~~~~~~~~ | 183 ~~~~~~~~~~~~~~~~~ |
| 194 | 184 |
| 195 Items contain data in properties. To Python, these | 185 Items contain data in properties. To Python, these properties are |
| 196 properties are presented as the key-value pairs of a dictionary. | 186 presented as the key-value pairs of a dictionary. Each item belongs to a |
| 197 Each item belongs to a class which defines the names | 187 class which defines the names and types of its properties. The database |
| 198 and types of its properties. The database permits the creation | 188 permits the creation and modification of classes as well as items. |
| 199 and modification of classes as well as items. | |
| 200 | 189 |
| 201 Identifiers and Designators | 190 Identifiers and Designators |
| 202 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 191 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 203 | 192 |
| 204 Each item has a numeric identifier which is unique among | 193 Each item has a numeric identifier which is unique among items in its |
| 205 items in its class. The items are numbered sequentially | 194 class. The items are numbered sequentially within each class in order |
| 206 within each class in order of creation, starting from 1. | 195 of creation, starting from 1. The designator for an item is a way to |
| 207 The designator | 196 identify an item in the database, and consists of the name of the item's |
| 208 for an item is a way to identify an item in the database, and | 197 class concatenated with the item's numeric identifier. |
| 209 consists of the name of the item's class concatenated with | 198 |
| 210 the item's numeric identifier. | 199 For example, if "spam" and "eggs" are classes, the first item created in |
| 211 | 200 class "spam" has id 1 and designator "spam1". The first item created in |
| 212 For example, if "spam" and "eggs" are classes, the first | 201 class "eggs" also has id 1 but has the distinct designator "eggs1". |
| 213 item created in class "spam" has id 1 and designator "spam1". | 202 Item designators are conventionally enclosed in square brackets when |
| 214 The first item created in class "eggs" also has id 1 but has | 203 mentioned in plain text. This permits a casual mention of, say, |
| 215 the distinct designator "eggs1". Item designators are | 204 "[patch37]" in an e-mail message to be turned into an active hyperlink. |
| 216 conventionally enclosed in square brackets when mentioned | |
| 217 in plain text. This permits a casual mention of, say, | |
| 218 "[patch37]" in an e-mail message to be turned into an active | |
| 219 hyperlink. | |
| 220 | 205 |
| 221 Property Names and Types | 206 Property Names and Types |
| 222 ~~~~~~~~~~~~~~~~~~~~~~~~ | 207 ~~~~~~~~~~~~~~~~~~~~~~~~ |
| 223 | 208 |
| 224 Property names must begin with a letter. | 209 Property names must begin with a letter. |
| 229 | 214 |
| 230 - Boolean properties are for storing true/false, or yes/no values. | 215 - Boolean properties are for storing true/false, or yes/no values. |
| 231 | 216 |
| 232 - Number properties are for storing numeric values. | 217 - Number properties are for storing numeric values. |
| 233 | 218 |
| 234 - Date properties store date-and-time stamps. | 219 - Date properties store date-and-time stamps. Their values are Timestamp |
| 235 Their values are Timestamp objects. | 220 objects. |
| 236 | 221 |
| 237 - A Link property refers to a single other item | 222 - A Link property refers to a single other item selected from a |
| 238 selected from a specified class. The class is part of the property; | 223 specified class. The class is part of the property; the value is an |
| 239 the value is an integer, the id of the chosen item. | 224 integer, the id of the chosen item. |
| 240 | 225 |
| 241 - A Multilink property refers to possibly many items | 226 - A Multilink property refers to possibly many items in a specified |
| 242 in a specified class. The value is a list of integers. | 227 class. The value is a list of integers. |
| 243 | 228 |
| 244 *None* is also a permitted value for any of these property | 229 *None* is also a permitted value for any of these property types. An |
| 245 types. An attempt to store None into a Multilink property stores an empty list. | 230 attempt to store None into a Multilink property stores an empty list. |
| 246 | 231 |
| 247 A property that is not specified will return as None from a *get* | 232 A property that is not specified will return as None from a *get* |
| 248 operation. | 233 operation. |
| 249 | 234 |
| 250 Hyperdb Interface Specification | 235 Hyperdb Interface Specification |
| 251 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 236 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 252 | 237 |
| 253 TODO: replace the Interface Specifications with links to the pydoc | 238 TODO: replace the Interface Specifications with links to the pydoc |
| 254 | 239 |
| 255 The hyperdb module provides property objects to designate | 240 The hyperdb module provides property objects to designate the different |
| 256 the different kinds of properties. These objects are used when | 241 kinds of properties. These objects are used when specifying what |
| 257 specifying what properties belong in classes:: | 242 properties belong in classes:: |
| 258 | 243 |
| 259 class String: | 244 class String: |
| 260 def __init__(self, indexme='no'): | 245 def __init__(self, indexme='no'): |
| 261 """An object designating a String property.""" | 246 """An object designating a String property.""" |
| 262 | 247 |
| 297 """A database for storing records containing flexible data types.""" | 282 """A database for storing records containing flexible data types.""" |
| 298 | 283 |
| 299 def __init__(self, config, journaltag=None): | 284 def __init__(self, config, journaltag=None): |
| 300 """Open a hyperdatabase given a specifier to some storage. | 285 """Open a hyperdatabase given a specifier to some storage. |
| 301 | 286 |
| 302 The 'storagelocator' is obtained from config.DATABASE. | 287 The 'storagelocator' is obtained from config.DATABASE. The |
| 303 The meaning of 'storagelocator' depends on the particular | 288 meaning of 'storagelocator' depends on the particular |
| 304 implementation of the hyperdatabase. It could be a file name, | 289 implementation of the hyperdatabase. It could be a file |
| 305 a directory path, a socket descriptor for a connection to a | 290 name, a directory path, a socket descriptor for a connection |
| 306 database over the network, etc. | 291 to a database over the network, etc. |
| 307 | 292 |
| 308 The 'journaltag' is a token that will be attached to the journal | 293 The 'journaltag' is a token that will be attached to the |
| 309 entries for any edits done on the database. If 'journaltag' is | 294 journal entries for any edits done on the database. If |
| 310 None, the database is opened in read-only mode: the Class.create(), | 295 'journaltag' is None, the database is opened in read-only |
| 311 Class.set(), Class.retire(), and Class.restore() methods are | 296 mode: the Class.create(), Class.set(), Class.retire(), and |
| 312 disabled. | 297 Class.restore() methods are disabled. |
| 313 """ | 298 """ |
| 314 | 299 |
| 315 def __getattr__(self, classname): | 300 def __getattr__(self, classname): |
| 316 """A convenient way of calling self.getclass(classname).""" | 301 """A convenient way of calling self.getclass(classname).""" |
| 317 | 302 |
| 379 by the find(), list(), or lookup() methods, and other items may | 364 by the find(), list(), or lookup() methods, and other items may |
| 380 reuse the values of their key properties. | 365 reuse the values of their key properties. |
| 381 """ | 366 """ |
| 382 | 367 |
| 383 def restore(self, nodeid): | 368 def restore(self, nodeid): |
| 384 '''Restpre a retired node. | 369 '''Restore a retired node. |
| 385 | 370 |
| 386 Make node available for all operations like it was before retirement. | 371 Make node available for all operations like it was before retirement. |
| 387 ''' | 372 ''' |
| 388 | 373 |
| 389 def history(self, itemid): | 374 def history(self, itemid): |
| 492 ''' | 477 ''' |
| 493 | 478 |
| 494 Hyperdatabase Implementations | 479 Hyperdatabase Implementations |
| 495 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 480 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 496 | 481 |
| 497 Hyperdatabase implementations exist to create the interface described in the | 482 Hyperdatabase implementations exist to create the interface described in |
| 498 `hyperdb interface specification`_ | 483 the `hyperdb interface specification`_ over an existing storage |
| 499 over an existing storage mechanism. Examples are relational databases, | 484 mechanism. Examples are relational databases, \*dbm key-value databases, |
| 500 \*dbm key-value databases, and so on. | 485 and so on. |
| 501 | 486 |
| 502 Several implementations are provided - they belong in the roundup.backends | 487 Several implementations are provided - they belong in the |
| 503 package. | 488 roundup.backends package. |
| 504 | 489 |
| 505 | 490 |
| 506 Application Example | 491 Application Example |
| 507 ~~~~~~~~~~~~~~~~~~~ | 492 ~~~~~~~~~~~~~~~~~~~ |
| 508 | 493 |
| 509 Here is an example of how the hyperdatabase module would work in practice:: | 494 Here is an example of how the hyperdatabase module would work in |
| 495 practice:: | |
| 510 | 496 |
| 511 >>> import hyperdb | 497 >>> import hyperdb |
| 512 >>> db = hyperdb.Database("foo.db", "ping") | 498 >>> db = hyperdb.Database("foo.db", "ping") |
| 513 >>> db | 499 >>> db |
| 514 <hyperdb.Database "foo.db" opened by "ping"> | 500 <hyperdb.Database "foo.db" opened by "ping"> |
| 567 (<Date 2000-06-28.19:11:04>, "ping", "unlink", ("issue", 5, "status"))] | 553 (<Date 2000-06-28.19:11:04>, "ping", "unlink", ("issue", 5, "status"))] |
| 568 >>> db.status.history(2) | 554 >>> db.status.history(2) |
| 569 [(<Date 2000-06-28.19:11:04>, "ping", "link", ("issue", 5, "status"))] | 555 [(<Date 2000-06-28.19:11:04>, "ping", "link", ("issue", 5, "status"))] |
| 570 | 556 |
| 571 | 557 |
| 572 For the purposes of journalling, when a Multilink property is | 558 For the purposes of journalling, when a Multilink property is set to a |
| 573 set to a new list of items, the hyperdatabase compares the old | 559 new list of items, the hyperdatabase compares the old list to the new |
| 574 list to the new list. | 560 list. The journal records "unlink" events for all the items that appear |
| 575 The journal records "unlink" events for all the items that appear | 561 in the old list but not the new list, and "link" events for all the |
| 576 in the old list but not the new list, | 562 items that appear in the new list but not in the old list. |
| 577 and "link" events for | |
| 578 all the items that appear in the new list but not in the old list. | |
| 579 | 563 |
| 580 | 564 |
| 581 Roundup Database | 565 Roundup Database |
| 582 ---------------- | 566 ---------------- |
| 583 | 567 |
| 584 The Roundup database layer is implemented on top of the | 568 The Roundup database layer is implemented on top of the hyperdatabase |
| 585 hyperdatabase and mediates calls to the database. | 569 and mediates calls to the database. Some of the classes in the Roundup |
| 586 Some of the classes in the Roundup database are considered | 570 database are considered issue classes. The Roundup database layer adds |
| 587 issue classes. | 571 detectors and user items, and on issues it provides mail spools, nosy |
| 588 The Roundup database layer adds detectors and user items, | 572 lists, and superseders. |
| 589 and on issues it provides mail spools, nosy lists, and superseders. | |
| 590 | 573 |
| 591 Reserved Classes | 574 Reserved Classes |
| 592 ~~~~~~~~~~~~~~~~ | 575 ~~~~~~~~~~~~~~~~ |
| 593 | 576 |
| 594 Internal to this layer we reserve three special classes | 577 Internal to this layer we reserve three special classes of items that |
| 595 of items that are not issues. | 578 are not issues. |
| 596 | 579 |
| 597 Users | 580 Users |
| 598 """"" | 581 """"" |
| 599 | 582 |
| 600 Users are stored in the hyperdatabase as items of | 583 Users are stored in the hyperdatabase as items of class "user". The |
| 601 class "user". The "user" class has the definition:: | 584 "user" class has the definition:: |
| 602 | 585 |
| 603 hyperdb.Class(db, "user", username=hyperdb.String(), | 586 hyperdb.Class(db, "user", username=hyperdb.String(), |
| 604 password=hyperdb.String(), | 587 password=hyperdb.String(), |
| 605 address=hyperdb.String()) | 588 address=hyperdb.String()) |
| 606 db.user.setkey("username") | 589 db.user.setkey("username") |
| 609 """""""" | 592 """""""" |
| 610 | 593 |
| 611 E-mail messages are represented by hyperdatabase items of class "msg". | 594 E-mail messages are represented by hyperdatabase items of class "msg". |
| 612 The actual text content of the messages is stored in separate files. | 595 The actual text content of the messages is stored in separate files. |
| 613 (There's no advantage to be gained by stuffing them into the | 596 (There's no advantage to be gained by stuffing them into the |
| 614 hyperdatabase, and if messages are stored in ordinary text files, | 597 hyperdatabase, and if messages are stored in ordinary text files, they |
| 615 they can be grepped from the command line.) The text of a message is | 598 can be grepped from the command line.) The text of a message is saved |
| 616 saved in a file named after the message item designator (e.g. "msg23") | 599 in a file named after the message item designator (e.g. "msg23") for the |
| 617 for the sake of the command interface (see below). Attachments are | 600 sake of the command interface (see below). Attachments are stored |
| 618 stored separately and associated with "file" items. | 601 separately and associated with "file" items. The "msg" class has the |
| 619 The "msg" class has the definition:: | 602 definition:: |
| 620 | 603 |
| 621 hyperdb.Class(db, "msg", author=hyperdb.Link("user"), | 604 hyperdb.Class(db, "msg", author=hyperdb.Link("user"), |
| 622 recipients=hyperdb.Multilink("user"), | 605 recipients=hyperdb.Multilink("user"), |
| 623 date=hyperdb.Date(), | 606 date=hyperdb.Date(), |
| 624 summary=hyperdb.String(), | 607 summary=hyperdb.String(), |
| 625 files=hyperdb.Multilink("file")) | 608 files=hyperdb.Multilink("file")) |
| 626 | 609 |
| 627 The "author" property indicates the author of the message | 610 The "author" property indicates the author of the message (a "user" item |
| 628 (a "user" item must exist in the hyperdatabase for any messages | 611 must exist in the hyperdatabase for any messages that are stored in the |
| 629 that are stored in the system). | 612 system). The "summary" property contains a summary of the message for |
| 630 The "summary" property contains a summary of the message for display | 613 display in a message index. |
| 631 in a message index. | |
| 632 | 614 |
| 633 Files | 615 Files |
| 634 """"" | 616 """"" |
| 635 | 617 |
| 636 Submitted files are represented by hyperdatabase | 618 Submitted files are represented by hyperdatabase items of class "file". |
| 637 items of class "file". Like e-mail messages, the file content | 619 Like e-mail messages, the file content is stored in files outside the |
| 638 is stored in files outside the database, | 620 database, named after the file item designator (e.g. "file17"). The |
| 639 named after the file item designator (e.g. "file17"). | 621 "file" class has the definition:: |
| 640 The "file" class has the definition:: | |
| 641 | 622 |
| 642 hyperdb.Class(db, "file", user=hyperdb.Link("user"), | 623 hyperdb.Class(db, "file", user=hyperdb.Link("user"), |
| 643 name=hyperdb.String(), | 624 name=hyperdb.String(), |
| 644 type=hyperdb.String()) | 625 type=hyperdb.String()) |
| 645 | 626 |
| 646 The "user" property indicates the user who submitted the | 627 The "user" property indicates the user who submitted the file, the |
| 647 file, the "name" property holds the original name of the file, | 628 "name" property holds the original name of the file, and the "type" |
| 648 and the "type" property holds the MIME type of the file as received. | 629 property holds the MIME type of the file as received. |
| 649 | 630 |
| 650 Issue Classes | 631 Issue Classes |
| 651 ~~~~~~~~~~~~~ | 632 ~~~~~~~~~~~~~ |
| 652 | 633 |
| 653 All issues have the following standard properties: | 634 All issues have the following standard properties: |
| 660 files hyperdb.Multilink("file") | 641 files hyperdb.Multilink("file") |
| 661 nosy hyperdb.Multilink("user") | 642 nosy hyperdb.Multilink("user") |
| 662 superseder hyperdb.Multilink("issue") | 643 superseder hyperdb.Multilink("issue") |
| 663 =========== ========================== | 644 =========== ========================== |
| 664 | 645 |
| 665 Also, two Date properties named "creation" and "activity" are | 646 Also, two Date properties named "creation" and "activity" are fabricated |
| 666 fabricated by the Roundup database layer. By "fabricated" we | 647 by the Roundup database layer. By "fabricated" we mean that no such |
| 667 mean that no such properties are actually stored in the | 648 properties are actually stored in the hyperdatabase, but when properties |
| 668 hyperdatabase, but when properties on issues are requested, the | 649 on issues are requested, the "creation" and "activity" properties are |
| 669 "creation" and "activity" properties are made available. | 650 made available. The value of the "creation" property is the date when an |
| 670 The value of the "creation" property is the date when an issue was | 651 issue was created, and the value of the "activity" property is the date |
| 671 created, and the value of the "activity" property is the | 652 when any property on the issue was last edited (equivalently, these are |
| 672 date when any property on the issue was last edited (equivalently, | 653 the dates on the first and last records in the issue's journal). |
| 673 these are the dates on the first and last records in the issue's journal). | |
| 674 | 654 |
| 675 Roundupdb Interface Specification | 655 Roundupdb Interface Specification |
| 676 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 656 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 677 | 657 |
| 678 The interface to a Roundup database delegates most method | 658 The interface to a Roundup database delegates most method calls to the |
| 679 calls to the hyperdatabase, except for the following | 659 hyperdatabase, except for the following changes and additional methods:: |
| 680 changes and additional methods:: | |
| 681 | 660 |
| 682 class Database: | 661 class Database: |
| 683 def getuid(self): | 662 def getuid(self): |
| 684 """Return the id of the "user" item associated with the user | 663 """Return the id of the "user" item associated with the user |
| 685 that owns this connection to the hyperdatabase.""" | 664 that owns this connection to the hyperdatabase.""" |
| 688 # Overridden methods: | 667 # Overridden methods: |
| 689 | 668 |
| 690 def create(self, **propvalues): | 669 def create(self, **propvalues): |
| 691 def set(self, **propvalues): | 670 def set(self, **propvalues): |
| 692 def retire(self, itemid): | 671 def retire(self, itemid): |
| 693 """These operations trigger detectors and can be vetoed. Attempts | 672 """These operations trigger detectors and can be vetoed. |
| 694 to modify the "creation" or "activity" properties cause a KeyError. | 673 Attempts to modify the "creation" or "activity" properties |
| 674 cause a KeyError. | |
| 695 """ | 675 """ |
| 696 | 676 |
| 697 # New methods: | 677 # New methods: |
| 698 | 678 |
| 699 def audit(self, event, detector): | 679 def audit(self, event, detector): |
| 702 | 682 |
| 703 class IssueClass(Class): | 683 class IssueClass(Class): |
| 704 # Overridden methods: | 684 # Overridden methods: |
| 705 | 685 |
| 706 def __init__(self, db, classname, **properties): | 686 def __init__(self, db, classname, **properties): |
| 707 """The newly-created class automatically includes the "messages", | 687 """The newly-created class automatically includes the |
| 708 "files", "nosy", and "superseder" properties. If the 'properties' | 688 "messages", "files", "nosy", and "superseder" properties. |
| 709 dictionary attempts to specify any of these properties or a | 689 If the 'properties' dictionary attempts to specify any of |
| 710 "creation" or "activity" property, a ValueError is raised.""" | 690 these properties or a "creation" or "activity" property, a |
| 691 ValueError is raised.""" | |
| 711 | 692 |
| 712 def get(self, itemid, propname): | 693 def get(self, itemid, propname): |
| 713 def getprops(self): | 694 def getprops(self): |
| 714 """In addition to the actual properties on the item, these | 695 """In addition to the actual properties on the item, these |
| 715 methods provide the "creation" and "activity" properties.""" | 696 methods provide the "creation" and "activity" properties.""" |
| 728 """ | 709 """ |
| 729 | 710 |
| 730 def sendmessage(self, itemid, msgid): | 711 def sendmessage(self, itemid, msgid): |
| 731 """Send a message to the members of an issue's nosy list. | 712 """Send a message to the members of an issue's nosy list. |
| 732 | 713 |
| 733 The message is sent only to users on the nosy list who are not | 714 The message is sent only to users on the nosy list who are |
| 734 already on the "recipients" list for the message. These users | 715 not already on the "recipients" list for the message. These |
| 735 are then added to the message's "recipients" list. | 716 users are then added to the message's "recipients" list. |
| 736 """ | 717 """ |
| 737 | 718 |
| 738 | 719 |
| 739 Default Schema | 720 Default Schema |
| 740 ~~~~~~~~~~~~~~ | 721 ~~~~~~~~~~~~~~ |
| 741 | 722 |
| 742 The default schema included with Roundup turns it into a | 723 The default schema included with Roundup turns it into a typical |
| 743 typical software bug tracker. The database is set up like this:: | 724 software bug tracker. The database is set up like this:: |
| 744 | 725 |
| 745 pri = Class(db, "priority", name=hyperdb.String(), order=hyperdb.String()) | 726 pri = Class(db, "priority", name=hyperdb.String(), order=hyperdb.String()) |
| 746 pri.setkey("name") | 727 pri.setkey("name") |
| 747 pri.create(name="critical", order="1") | 728 pri.create(name="critical", order="1") |
| 748 pri.create(name="urgent", order="2") | 729 pri.create(name="urgent", order="2") |
| 766 Class(db, "issue", fixer=hyperdb.Multilink("user"), | 747 Class(db, "issue", fixer=hyperdb.Multilink("user"), |
| 767 topic=hyperdb.Multilink("keyword"), | 748 topic=hyperdb.Multilink("keyword"), |
| 768 priority=hyperdb.Link("priority"), | 749 priority=hyperdb.Link("priority"), |
| 769 status=hyperdb.Link("status")) | 750 status=hyperdb.Link("status")) |
| 770 | 751 |
| 771 (The "order" property hasn't been explained yet. It | 752 (The "order" property hasn't been explained yet. It gets used by the |
| 772 gets used by the Web user interface for sorting.) | 753 Web user interface for sorting.) |
| 773 | 754 |
| 774 The above isn't as pretty-looking as the schema specification | 755 The above isn't as pretty-looking as the schema specification in the |
| 775 in the first-stage submission, but it could be made just as easy | 756 first-stage submission, but it could be made just as easy with the |
| 776 with the addition of a convenience function like Choice | 757 addition of a convenience function like Choice for setting up the |
| 777 for setting up the "priority" and "status" classes:: | 758 "priority" and "status" classes:: |
| 778 | 759 |
| 779 def Choice(name, *options): | 760 def Choice(name, *options): |
| 780 cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String()) | 761 cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String()) |
| 781 for i in range(len(options)): | 762 for i in range(len(options)): |
| 782 cl.create(name=option[i], order=i) | 763 cl.create(name=option[i], order=i) |
| 784 | 765 |
| 785 | 766 |
| 786 Detector Interface | 767 Detector Interface |
| 787 ------------------ | 768 ------------------ |
| 788 | 769 |
| 789 Detectors are Python functions that are triggered on certain | 770 Detectors are Python functions that are triggered on certain kinds of |
| 790 kinds of events. The definitions of the | 771 events. The definitions of the functions live in Python modules placed |
| 791 functions live in Python modules placed in a directory set aside | 772 in a directory set aside for this purpose. Importing the Roundup |
| 792 for this purpose. Importing the Roundup database module also | 773 database module also imports all the modules in this directory, and the |
| 793 imports all the modules in this directory, and the ``init()`` | 774 ``init()`` function of each module is called when a database is opened |
| 794 function of each module is called when a database is opened to | 775 to provide it a chance to register its detectors. |
| 795 provide it a chance to register its detectors. | |
| 796 | 776 |
| 797 There are two kinds of detectors: | 777 There are two kinds of detectors: |
| 798 | 778 |
| 799 1. an auditor is triggered just before modifying an item | 779 1. an auditor is triggered just before modifying an item |
| 800 2. a reactor is triggered just after an item has been modified | 780 2. a reactor is triggered just after an item has been modified |
| 801 | 781 |
| 802 When the Roundup database is about to perform a | 782 When the Roundup database is about to perform a ``create()``, ``set()``, |
| 803 ``create()``, ``set()``, ``retire()``, or ``restore`` | 783 ``retire()``, or ``restore`` operation, it first calls any *auditors* |
| 804 operation, it first calls any *auditors* that | 784 that have been registered for that operation on that class. Any auditor |
| 805 have been registered for that operation on that class. | 785 may raise a *Reject* exception to abort the operation. |
| 806 Any auditor may raise a *Reject* exception | 786 |
| 807 to abort the operation. | 787 If none of the auditors raises an exception, the database proceeds to |
| 808 | 788 carry out the operation. After it's done, it then calls all of the |
| 809 If none of the auditors raises an exception, the database | 789 *reactors* that have been registered for the operation. |
| 810 proceeds to carry out the operation. After it's done, it | |
| 811 then calls all of the *reactors* that have been registered | |
| 812 for the operation. | |
| 813 | 790 |
| 814 Detector Interface Specification | 791 Detector Interface Specification |
| 815 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 792 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 816 | 793 |
| 817 The ``audit()`` and ``react()`` methods | 794 The ``audit()`` and ``react()`` methods register detectors on a given |
| 818 register detectors on a given class of items:: | 795 class of items:: |
| 819 | 796 |
| 820 class Class: | 797 class Class: |
| 821 def audit(self, event, detector): | 798 def audit(self, event, detector): |
| 822 """Register an auditor on this class. | 799 """Register an auditor on this class. |
| 823 | 800 |
| 824 'event' should be one of "create", "set", "retire", or "restore". | 801 'event' should be one of "create", "set", "retire", or |
| 825 'detector' should be a function accepting four arguments. | 802 "restore". 'detector' should be a function accepting four |
| 803 arguments. | |
| 826 """ | 804 """ |
| 827 | 805 |
| 828 def react(self, event, detector): | 806 def react(self, event, detector): |
| 829 """Register a reactor on this class. | 807 """Register a reactor on this class. |
| 830 | 808 |
| 831 'event' should be one of "create", "set", "retire", or "restore". | 809 'event' should be one of "create", "set", "retire", or |
| 832 'detector' should be a function accepting four arguments. | 810 "restore". 'detector' should be a function accepting four |
| 811 arguments. | |
| 833 """ | 812 """ |
| 834 | 813 |
| 835 Auditors are called with the arguments:: | 814 Auditors are called with the arguments:: |
| 836 | 815 |
| 837 audit(db, cl, itemid, newdata) | 816 audit(db, cl, itemid, newdata) |
| 838 | 817 |
| 839 where ``db`` is the database, ``cl`` is an | 818 where ``db`` is the database, ``cl`` is an instance of Class or |
| 840 instance of Class or IssueClass within the database, and ``newdata`` | 819 IssueClass within the database, and ``newdata`` is a dictionary mapping |
| 841 is a dictionary mapping property names to values. | 820 property names to values. |
| 842 | 821 |
| 843 For a ``create()`` | 822 For a ``create()`` operation, the ``itemid`` argument is None and |
| 844 operation, the ``itemid`` argument is None and newdata | 823 newdata contains all of the initial property values with which the item |
| 845 contains all of the initial property values with which the item | |
| 846 is about to be created. | 824 is about to be created. |
| 847 | 825 |
| 848 For a ``set()`` operation, newdata | 826 For a ``set()`` operation, newdata contains only the names and values of |
| 849 contains only the names and values of properties that are about | 827 properties that are about to be changed. |
| 850 to be changed. | |
| 851 | 828 |
| 852 For a ``retire()`` or ``restore()`` operation, newdata is None. | 829 For a ``retire()`` or ``restore()`` operation, newdata is None. |
| 853 | 830 |
| 854 Reactors are called with the arguments:: | 831 Reactors are called with the arguments:: |
| 855 | 832 |
| 856 react(db, cl, itemid, olddata) | 833 react(db, cl, itemid, olddata) |
| 857 | 834 |
| 858 where ``db`` is the database, ``cl`` is an | 835 where ``db`` is the database, ``cl`` is an instance of Class or |
| 859 instance of Class or IssueClass within the database, and ``olddata`` | 836 IssueClass within the database, and ``olddata`` is a dictionary mapping |
| 860 is a dictionary mapping property names to values. | 837 property names to values. |
| 861 | 838 |
| 862 For a ``create()`` | 839 For a ``create()`` operation, the ``itemid`` argument is the id of the |
| 863 operation, the ``itemid`` argument is the id of the | |
| 864 newly-created item and ``olddata`` is None. | 840 newly-created item and ``olddata`` is None. |
| 865 | 841 |
| 866 For a ``set()`` operation, ``olddata`` | 842 For a ``set()`` operation, ``olddata`` contains the names and previous |
| 867 contains the names and previous values of properties that were changed. | 843 values of properties that were changed. |
| 868 | 844 |
| 869 For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of | 845 For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of |
| 870 the retired or restored item and ``olddata`` is None. | 846 the retired or restored item and ``olddata`` is None. |
| 871 | 847 |
| 872 Detector Example | 848 Detector Example |
| 873 ~~~~~~~~~~~~~~~~ | 849 ~~~~~~~~~~~~~~~~ |
| 874 | 850 |
| 875 Here is an example of detectors written for a hypothetical | 851 Here is an example of detectors written for a hypothetical |
| 876 project-management application, where users can signal approval | 852 project-management application, where users can signal approval of a |
| 877 of a project by adding themselves to an "approvals" list, and | 853 project by adding themselves to an "approvals" list, and a project |
| 878 a project proceeds when it has three approvals:: | 854 proceeds when it has three approvals:: |
| 879 | 855 |
| 880 # Permit users only to add themselves to the "approvals" list. | 856 # Permit users only to add themselves to the "approvals" list. |
| 881 | 857 |
| 882 def check_approvals(db, cl, id, newdata): | 858 def check_approvals(db, cl, id, newdata): |
| 883 if newdata.has_key("approvals"): | 859 if newdata.has_key("approvals"): |
| 905 | 881 |
| 906 def init(db): | 882 def init(db): |
| 907 db.project.audit("set", check_approval) | 883 db.project.audit("set", check_approval) |
| 908 db.project.react("set", approve_project) | 884 db.project.react("set", approve_project) |
| 909 | 885 |
| 910 Here is another example of a detector that can allow or prevent | 886 Here is another example of a detector that can allow or prevent the |
| 911 the creation of new items. In this scenario, patches for a software | 887 creation of new items. In this scenario, patches for a software project |
| 912 project are submitted by sending in e-mail with an attached file, | 888 are submitted by sending in e-mail with an attached file, and we want to |
| 913 and we want to ensure that there are text/plain attachments on | 889 ensure that there are text/plain attachments on the message. The |
| 914 the message. The maintainer of the package can then apply the | 890 maintainer of the package can then apply the patch by setting its status |
| 915 patch by setting its status to "applied":: | 891 to "applied":: |
| 916 | 892 |
| 917 # Only accept attempts to create new patches that come with patch files. | 893 # Only accept attempts to create new patches that come with patch files. |
| 918 | 894 |
| 919 def check_new_patch(db, cl, id, newdata): | 895 def check_new_patch(db, cl, id, newdata): |
| 920 if not newdata["files"]: | 896 if not newdata["files"]: |
| 937 | 913 |
| 938 | 914 |
| 939 Command Interface | 915 Command Interface |
| 940 ----------------- | 916 ----------------- |
| 941 | 917 |
| 942 The command interface is a very simple and minimal interface, | 918 The command interface is a very simple and minimal interface, intended |
| 943 intended only for quick searches and checks from the shell prompt. | 919 only for quick searches and checks from the shell prompt. (Anything more |
| 944 (Anything more interesting can simply be written in Python using | 920 interesting can simply be written in Python using the Roundup database |
| 945 the Roundup database module.) | 921 module.) |
| 946 | 922 |
| 947 Command Interface Specification | 923 Command Interface Specification |
| 948 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 924 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 949 | 925 |
| 950 A single command, roundup, provides basic access to | 926 A single command, roundup, provides basic access to the hyperdatabase |
| 951 the hyperdatabase from the command line:: | 927 from the command line:: |
| 952 | 928 |
| 953 roundup-admin help | 929 roundup-admin help |
| 954 roundup-admin get [-list] designator[, designator,...] propname | 930 roundup-admin get [-list] designator[, designator,...] propname |
| 955 roundup-admin set designator[, designator,...] propname=value ... | 931 roundup-admin set designator[, designator,...] propname=value ... |
| 956 roundup-admin find [-list] classname propname=value ... | 932 roundup-admin find [-list] classname propname=value ... |
| 957 | 933 |
| 958 See ``roundup-admin help commands`` for a complete list of commands. | 934 See ``roundup-admin help commands`` for a complete list of commands. |
| 959 | 935 |
| 960 Property values are represented as strings in command arguments | 936 Property values are represented as strings in command arguments and in |
| 961 and in the printed results: | 937 the printed results: |
| 962 | 938 |
| 963 - Strings are, well, strings. | 939 - Strings are, well, strings. |
| 964 | 940 |
| 965 - Numbers are displayed the same as strings. | 941 - Numbers are displayed the same as strings. |
| 966 | 942 |
| 967 - Booleans are displayed as 'Yes' or 'No'. | 943 - Booleans are displayed as 'Yes' or 'No'. |
| 968 | 944 |
| 969 - Date values are printed in the full date format in the local | 945 - Date values are printed in the full date format in the local time |
| 970 time zone, and accepted in the full format or any of the partial | 946 zone, and accepted in the full format or any of the partial formats |
| 971 formats explained above. | 947 explained above. |
| 972 | 948 |
| 973 - Link values are printed as item designators. When given as | 949 - Link values are printed as item designators. When given as an |
| 974 an argument, item designators and key strings are both accepted. | 950 argument, item designators and key strings are both accepted. |
| 975 | 951 |
| 976 - Multilink values are printed as lists of item designators | 952 - Multilink values are printed as lists of item designators joined by |
| 977 joined by commas. When given as an argument, item designators | 953 commas. When given as an argument, item designators and key strings |
| 978 and key strings are both accepted; an empty string, a single item, | 954 are both accepted; an empty string, a single item, or a list of items |
| 979 or a list of items joined by commas is accepted. | 955 joined by commas is accepted. |
| 980 | 956 |
| 981 When multiple items are specified to the | 957 When multiple items are specified to the roundup get or roundup set |
| 982 roundup get or roundup set | 958 commands, the specified properties are retrieved or set on all the |
| 983 commands, the specified properties are retrieved or set | 959 listed items. |
| 984 on all the listed items. | 960 |
| 985 | 961 When multiple results are returned by the roundup get or roundup find |
| 986 When multiple results are returned by the roundup get | 962 commands, they are printed one per line (default) or joined by commas |
| 987 or roundup find commands, they are printed one per | 963 (with the -list) option. |
| 988 line (default) or joined by commas (with the -list) option. | |
| 989 | 964 |
| 990 Usage Example | 965 Usage Example |
| 991 ~~~~~~~~~~~~~ | 966 ~~~~~~~~~~~~~ |
| 992 | 967 |
| 993 To find all messages regarding in-progress issues that | 968 To find all messages regarding in-progress issues that contain the word |
| 994 contain the word "spam", for example, you could execute the | 969 "spam", for example, you could execute the following command from the |
| 995 following command from the directory where the database | 970 directory where the database dumps its files:: |
| 996 dumps its files:: | |
| 997 | 971 |
| 998 shell% for issue in `roundup find issue status=in-progress`; do | 972 shell% for issue in `roundup find issue status=in-progress`; do |
| 999 > grep -l spam `roundup get $issue messages` | 973 > grep -l spam `roundup get $issue messages` |
| 1000 > done | 974 > done |
| 1001 msg23 | 975 msg23 |
| 1016 | 990 |
| 1017 | 991 |
| 1018 E-mail User Interface | 992 E-mail User Interface |
| 1019 --------------------- | 993 --------------------- |
| 1020 | 994 |
| 1021 The Roundup system must be assigned an e-mail address | 995 The Roundup system must be assigned an e-mail address at which to |
| 1022 at which to receive mail. Messages should be piped to | 996 receive mail. Messages should be piped to the Roundup mail-handling |
| 1023 the Roundup mail-handling script by the mail delivery | 997 script by the mail delivery system (e.g. using an alias beginning with |
| 1024 system (e.g. using an alias beginning with "|" for sendmail). | 998 "|" for sendmail). |
| 1025 | 999 |
| 1026 Message Processing | 1000 Message Processing |
| 1027 ~~~~~~~~~~~~~~~~~~ | 1001 ~~~~~~~~~~~~~~~~~~ |
| 1028 | 1002 |
| 1029 Incoming messages are examined for multiple parts. | 1003 Incoming messages are examined for multiple parts. In a multipart/mixed |
| 1030 In a multipart/mixed message or part, each subpart is | 1004 message or part, each subpart is extracted and examined. In a |
| 1031 extracted and examined. In a multipart/alternative | 1005 multipart/alternative message or part, we look for a text/plain subpart |
| 1032 message or part, we look for a text/plain subpart and | 1006 and ignore the other parts. The text/plain subparts are assembled to |
| 1033 ignore the other parts. The text/plain subparts are | 1007 form the textual body of the message, to be stored in the file |
| 1034 assembled to form the textual body of the message, to | 1008 associated with a "msg" class item. Any parts of other types are each |
| 1035 be stored in the file associated with a "msg" class item. | 1009 stored in separate files and given "file" class items that are linked to |
| 1036 Any parts of other types are each stored in separate | |
| 1037 files and given "file" class items that are linked to | |
| 1038 the "msg" item. | 1010 the "msg" item. |
| 1039 | 1011 |
| 1040 The "summary" property on message items is taken from | 1012 The "summary" property on message items is taken from the first |
| 1041 the first non-quoting section in the message body. | 1013 non-quoting section in the message body. The message body is divided |
| 1042 The message body is divided into sections by blank lines. | 1014 into sections by blank lines. Sections where the second and all |
| 1043 Sections where the second and all subsequent lines begin | 1015 subsequent lines begin with a ">" or "|" character are considered |
| 1044 with a ">" or "|" character are considered "quoting | 1016 "quoting sections". The first line of the first non-quoting section |
| 1045 sections". The first line of the first non-quoting | 1017 becomes the summary of the message. |
| 1046 section becomes the summary of the message. | 1018 |
| 1047 | 1019 All of the addresses in the To: and Cc: headers of the incoming message |
| 1048 All of the addresses in the To: and Cc: headers of the | 1020 are looked up among the user items, and the corresponding users are |
| 1049 incoming message are looked up among the user items, and | 1021 placed in the "recipients" property on the new "msg" item. The address |
| 1050 the corresponding users are placed in the "recipients" | 1022 in the From: header similarly determines the "author" property of the |
| 1051 property on the new "msg" item. The address in the From: | 1023 new "msg" item. The default handling for addresses that don't have |
| 1052 header similarly determines the "author" property of the | 1024 corresponding users is to create new users with no passwords and a |
| 1053 new "msg" item. | 1025 username equal to the address. (The web interface does not permit |
| 1054 The default handling for | 1026 logins for users with no passwords.) If we prefer to reject mail from |
| 1055 addresses that don't have corresponding users is to create | 1027 outside sources, we can simply register an auditor on the "user" class |
| 1056 new users with no passwords and a username equal to the | 1028 that prevents the creation of user items with no passwords. |
| 1057 address. (The web interface does not permit logins for | 1029 |
| 1058 users with no passwords.) If we prefer to reject mail from | 1030 The subject line of the incoming message is examined to determine |
| 1059 outside sources, we can simply register an auditor on the | 1031 whether the message is an attempt to create a new issue or to discuss an |
| 1060 "user" class that prevents the creation of user items with | 1032 existing issue. A designator enclosed in square brackets is sought as |
| 1061 no passwords. | 1033 the first thing on the subject line (after skipping any "Fwd:" or "Re:" |
| 1062 | 1034 prefixes). |
| 1063 The subject line of the incoming message is examined to | 1035 |
| 1064 determine whether the message is an attempt to create a new | 1036 If an issue designator (class name and id number) is found there, the |
| 1065 issue or to discuss an existing issue. A designator enclosed | 1037 newly created "msg" item is added to the "messages" property for that |
| 1066 in square brackets is sought as the first thing on the | 1038 issue, and any new "file" items are added to the "files" property for |
| 1067 subject line (after skipping any "Fwd:" or "Re:" prefixes). | 1039 the issue. |
| 1068 | 1040 |
| 1069 If an issue designator (class name and id number) is found | 1041 If just an issue class name is found there, we attempt to create a new |
| 1070 there, the newly created "msg" item is added to the "messages" | 1042 issue of that class with its "messages" property initialized to contain |
| 1071 property for that issue, and any new "file" items are added to | 1043 the new "msg" item and its "files" property initialized to contain any |
| 1072 the "files" property for the issue. | 1044 new "file" items. |
| 1073 | 1045 |
| 1074 If just an issue class name is found there, we attempt to | 1046 Both cases may trigger detectors (in the first case we are calling the |
| 1075 create a new issue of that class with its "messages" property | 1047 set() method to add the message to the issue's spool; in the second case |
| 1076 initialized to contain the new "msg" item and its "files" | 1048 we are calling the create() method to create a new item). If an auditor |
| 1077 property initialized to contain any new "file" items. | 1049 raises an exception, the original message is bounced back to the sender |
| 1078 | 1050 with the explanatory message given in the exception. |
| 1079 Both cases may trigger detectors (in the first case we | |
| 1080 are calling the set() method to add the message to the | |
| 1081 issue's spool; in the second case we are calling the | |
| 1082 create() method to create a new item). If an auditor | |
| 1083 raises an exception, the original message is bounced back to | |
| 1084 the sender with the explanatory message given in the exception. | |
| 1085 | 1051 |
| 1086 Nosy Lists | 1052 Nosy Lists |
| 1087 ~~~~~~~~~~ | 1053 ~~~~~~~~~~ |
| 1088 | 1054 |
| 1089 A standard detector is provided that watches for additions | 1055 A standard detector is provided that watches for additions to the |
| 1090 to the "messages" property. When a new message is added, the | 1056 "messages" property. When a new message is added, the detector sends it |
| 1091 detector sends it to all the users on the "nosy" list for the | 1057 to all the users on the "nosy" list for the issue that are not already |
| 1092 issue that are not already on the "recipients" list of the | 1058 on the "recipients" list of the message. Those users are then appended |
| 1093 message. Those users are then appended to the "recipients" | 1059 to the "recipients" property on the message, so multiple copies of a |
| 1094 property on the message, so multiple copies of a message | 1060 message are never sent to the same user. The journal recorded by the |
| 1095 are never sent to the same user. The journal recorded by | 1061 hyperdatabase on the "recipients" property then provides a log of when |
| 1096 the hyperdatabase on the "recipients" property then provides | 1062 the message was sent to whom. |
| 1097 a log of when the message was sent to whom. | |
| 1098 | 1063 |
| 1099 Setting Properties | 1064 Setting Properties |
| 1100 ~~~~~~~~~~~~~~~~~~ | 1065 ~~~~~~~~~~~~~~~~~~ |
| 1101 | 1066 |
| 1102 The e-mail interface also provides a simple way to set | 1067 The e-mail interface also provides a simple way to set properties on |
| 1103 properties on issues. At the end of the subject line, | 1068 issues. At the end of the subject line, ``propname=value`` pairs can be |
| 1104 ``propname=value`` pairs can be | 1069 specified in square brackets, using the same conventions as for the |
| 1105 specified in square brackets, using the same conventions | 1070 roundup ``set`` shell command. |
| 1106 as for the roundup ``set`` shell command. | |
| 1107 | 1071 |
| 1108 | 1072 |
| 1109 Web User Interface | 1073 Web User Interface |
| 1110 ------------------ | 1074 ------------------ |
| 1111 | 1075 |
| 1112 The web interface is provided by a CGI script that can be | 1076 The web interface is provided by a CGI script that can be run under any |
| 1113 run under any web server. A simple web server can easily be | 1077 web server. A simple web server can easily be built on the standard |
| 1114 built on the standard CGIHTTPServer module, and | 1078 CGIHTTPServer module, and should also be included in the distribution |
| 1115 should also be included in the distribution for quick | 1079 for quick out-of-the-box deployment. |
| 1116 out-of-the-box deployment. | 1080 |
| 1117 | 1081 The user interface is constructed from a number of template files |
| 1118 The user interface is constructed from a number of template | 1082 containing mostly HTML. Among the HTML tags in templates are |
| 1119 files containing mostly HTML. Among the HTML tags in templates | 1083 interspersed some nonstandard tags, which we use as placeholders to be |
| 1120 are interspersed some nonstandard tags, which we use as | 1084 replaced by properties and their values. |
| 1121 placeholders to be replaced by properties and their values. | |
| 1122 | 1085 |
| 1123 Views and View Specifiers | 1086 Views and View Specifiers |
| 1124 ~~~~~~~~~~~~~~~~~~~~~~~~~ | 1087 ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1125 | 1088 |
| 1126 There are two main kinds of views: *index* views and *issue* views. | 1089 There are two main kinds of views: *index* views and *issue* views. An |
| 1127 An index view displays a list of issues of a particular class, | 1090 index view displays a list of issues of a particular class, optionally |
| 1128 optionally sorted and filtered as requested. An issue view | 1091 sorted and filtered as requested. An issue view presents the properties |
| 1129 presents the properties of a particular issue for editing | 1092 of a particular issue for editing and displays the message spool for the |
| 1130 and displays the message spool for the issue. | 1093 issue. |
| 1131 | 1094 |
| 1132 A view specifier is a string that specifies | 1095 A view specifier is a string that specifies all the options needed to |
| 1133 all the options needed to construct a particular view. | 1096 construct a particular view. It goes after the URL to the Roundup CGI |
| 1134 It goes after the URL to the Roundup CGI script or the | 1097 script or the web server to form the complete URL to a view. When the |
| 1135 web server to form the complete URL to a view. When the | 1098 result of selecting a link or submitting a form takes the user to a new |
| 1136 result of selecting a link or submitting a form takes | 1099 view, the Web browser should be redirected to a canonical location |
| 1137 the user to a new view, the Web browser should be redirected | 1100 containing a complete view specifier so that the view can be bookmarked. |
| 1138 to a canonical location containing a complete view specifier | |
| 1139 so that the view can be bookmarked. | |
| 1140 | 1101 |
| 1141 Displaying Properties | 1102 Displaying Properties |
| 1142 ~~~~~~~~~~~~~~~~~~~~~ | 1103 ~~~~~~~~~~~~~~~~~~~~~ |
| 1143 | 1104 |
| 1144 Properties appear in the user interface in three contexts: | 1105 Properties appear in the user interface in three contexts: in indices, |
| 1145 in indices, in editors, and as search filters. For each type of | 1106 in editors, and as search filters. For each type of property, there are |
| 1146 property, there are several display possibilities. For example, | 1107 several display possibilities. For example, in an index view, a string |
| 1147 in an index view, a string property may just be printed as | 1108 property may just be printed as a plain string, but in an editor view, |
| 1148 a plain string, but in an editor view, that property should | 1109 that property should be displayed in an editable field. |
| 1149 be displayed in an editable field. | 1110 |
| 1150 | 1111 The display of a property is handled by functions in the |
| 1151 The display of a property is handled by functions in | 1112 ``cgi.templating`` module. |
| 1152 the ``cgi.templating`` module. | |
| 1153 | 1113 |
| 1154 Displayer functions are triggered by ``tal:content`` or ``tal:replace`` | 1114 Displayer functions are triggered by ``tal:content`` or ``tal:replace`` |
| 1155 tag attributes in templates. The value of the attribute | 1115 tag attributes in templates. The value of the attribute provides an |
| 1156 provides an expression for calling the displayer function. | 1116 expression for calling the displayer function. For example, the |
| 1157 For example, the occurrence of:: | 1117 occurrence of:: |
| 1158 | 1118 |
| 1159 tal:content="context/status/plain" | 1119 tal:content="context/status/plain" |
| 1160 | 1120 |
| 1161 in a template triggers a call to:: | 1121 in a template triggers a call to:: |
| 1162 | 1122 |
| 1163 context['status'].plain() | 1123 context['status'].plain() |
| 1164 | 1124 |
| 1165 where the context would be an item of the "issue" class. The displayer | 1125 where the context would be an item of the "issue" class. The displayer |
| 1166 functions can accept extra arguments to further specify | 1126 functions can accept extra arguments to further specify details about |
| 1167 details about the widgets that should be generated. | 1127 the widgets that should be generated. |
| 1168 | 1128 |
| 1169 Some of the standard displayer functions include: | 1129 Some of the standard displayer functions include: |
| 1170 | 1130 |
| 1171 ========= ==================================================================== | 1131 ========= ============================================================== |
| 1172 Function Description | 1132 Function Description |
| 1173 ========= ==================================================================== | 1133 ========= ============================================================== |
| 1174 plain display a String property directly; | 1134 plain display a String property directly; |
| 1175 display a Date property in a specified time zone with an option | 1135 display a Date property in a specified time zone with an |
| 1176 to omit the time from the date stamp; for a Link or Multilink | 1136 option to omit the time from the date stamp; for a Link or |
| 1177 property, display the key strings of the linked items (or the | 1137 Multilink property, display the key strings of the linked |
| 1178 ids if the linked class has no key property) | 1138 items (or the ids if the linked class has no key property) |
| 1179 field display a property like the plain displayer above, but in a text | 1139 field display a property like the plain displayer above, but in a |
| 1180 field to be edited | 1140 text field to be edited |
| 1181 menu for a Link property, display a menu of the available choices | 1141 menu for a Link property, display a menu of the available choices |
| 1182 ========= ==================================================================== | 1142 ========= ============================================================== |
| 1183 | 1143 |
| 1184 See the `customisation`_ documentation for the complete list. | 1144 See the `customisation`_ documentation for the complete list. |
| 1185 | 1145 |
| 1186 | 1146 |
| 1187 Index Views | 1147 Index Views |
| 1188 ~~~~~~~~~~~ | 1148 ~~~~~~~~~~~ |
| 1189 | 1149 |
| 1190 An index view contains two sections: a filter section | 1150 An index view contains two sections: a filter section and an index |
| 1191 and an index section. | 1151 section. The filter section provides some widgets for selecting which |
| 1192 The filter section provides some widgets for selecting | 1152 issues appear in the index. The index section is a table of issues. |
| 1193 which issues appear in the index. The index section is | |
| 1194 a table of issues. | |
| 1195 | 1153 |
| 1196 Index View Specifiers | 1154 Index View Specifiers |
| 1197 """"""""""""""""""""" | 1155 """"""""""""""""""""" |
| 1198 | 1156 |
| 1199 An index view specifier looks like this (whitespace | 1157 An index view specifier looks like this (whitespace has been added for |
| 1200 has been added for clarity):: | 1158 clarity):: |
| 1201 | 1159 |
| 1202 /issue?status=unread,in-progress,resolved& | 1160 /issue?status=unread,in-progress,resolved& |
| 1203 topic=security,ui& | 1161 topic=security,ui& |
| 1204 :group=priority& | 1162 :group=priority& |
| 1205 :sort=-activity& | 1163 :sort=-activity& |
| 1206 :filters=status,topic& | 1164 :filters=status,topic& |
| 1207 :columns=title,status,fixer | 1165 :columns=title,status,fixer |
| 1208 | 1166 |
| 1209 | 1167 |
| 1210 The index view is determined by two parts of the | 1168 The index view is determined by two parts of the specifier: the layout |
| 1211 specifier: the layout part and the filter part. | 1169 part and the filter part. The layout part consists of the query |
| 1212 The layout part consists of the query parameters that | 1170 parameters that begin with colons, and it determines the way that the |
| 1213 begin with colons, and it determines the way that the | 1171 properties of selected items are displayed. The filter part consists of |
| 1214 properties of selected items are displayed. | 1172 all the other query parameters, and it determines the criteria by which |
| 1215 The filter part consists of all the other query parameters, | 1173 items are selected for display. |
| 1216 and it determines the criteria by which items | 1174 |
| 1217 are selected for display. | 1175 The filter part is interactively manipulated with the form widgets |
| 1218 | 1176 displayed in the filter section. The layout part is interactively |
| 1219 The filter part is interactively manipulated with | 1177 manipulated by clicking on the column headings in the table. |
| 1220 the form widgets displayed in the filter section. The | 1178 |
| 1221 layout part is interactively manipulated by clicking | 1179 The filter part selects the union of the sets of issues with values |
| 1222 on the column headings in the table. | 1180 matching any specified Link properties and the intersection of the sets |
| 1223 | 1181 of issues with values matching any specified Multilink properties. |
| 1224 The filter part selects the union of the | 1182 |
| 1225 sets of issues with values matching any specified Link | 1183 The example specifies an index of "issue" items. Only issues with a |
| 1226 properties and the intersection of the sets | 1184 "status" of either "unread" or "in-progres" or "resolved" are displayed, |
| 1227 of issues with values matching any specified Multilink | 1185 and only issues with "topic" values including both "security" and "ui" |
| 1228 properties. | 1186 are displayed. The issues are grouped by priority, arranged in |
| 1229 | 1187 ascending order; and within groups, sorted by activity, arranged in |
| 1230 The example specifies an index of "issue" items. | 1188 descending order. The filter section shows filters for the "status" and |
| 1231 Only issues with a "status" of either | 1189 "topic" properties, and the table includes columns for the "title", |
| 1232 "unread" or "in-progres" or "resolved" are displayed, | 1190 "status", and "fixer" properties. |
| 1233 and only issues with "topic" values including both | 1191 |
| 1234 "security" and "ui" are displayed. The issues | 1192 Associated with each issue class is a default layout specifier. The |
| 1235 are grouped by priority, arranged in ascending order; | 1193 layout specifier in the above example is the default layout to be |
| 1236 and within groups, sorted by activity, arranged in | 1194 provided with the default bug-tracker schema described above in section |
| 1237 descending order. The filter section shows filters | 1195 4.4. |
| 1238 for the "status" and "topic" properties, and the | |
| 1239 table includes columns for the "title", "status", and | |
| 1240 "fixer" properties. | |
| 1241 | |
| 1242 Associated with each issue class is a default | |
| 1243 layout specifier. The layout specifier in the above | |
| 1244 example is the default layout to be provided with | |
| 1245 the default bug-tracker schema described above in | |
| 1246 section 4.4. | |
| 1247 | 1196 |
| 1248 Index Section | 1197 Index Section |
| 1249 """"""""""""" | 1198 """"""""""""" |
| 1250 | 1199 |
| 1251 The template for an index section describes one row of | 1200 The template for an index section describes one row of the index table. |
| 1252 the index table. | 1201 Fragments protected by a ``tal:condition="request/show/<property>"`` are |
| 1253 Fragments enclosed in ``<property>...</property>`` | 1202 included or omitted depending on whether the view specifier requests a |
| 1254 tags are included or omitted depending on whether the | 1203 column for a particular property. The table cells are filled by the |
| 1255 view specifier requests a column for a particular property. | 1204 ``tal:content="context/<property>"`` directive, which displays the value |
| 1256 The table cells should contain <display> tags | 1205 of the property. |
| 1257 to display the values of the issue's properties. | |
| 1258 | 1206 |
| 1259 Here's a simple example of an index template:: | 1207 Here's a simple example of an index template:: |
| 1260 | 1208 |
| 1261 <tr> | 1209 <tr> |
| 1262 <td tal:condition="request/show/title" tal:content="contex/title"></td> | 1210 <td tal:condition="request/show/title" |
| 1263 <td tal:condition="request/show/status" tal:content="contex/status"></td> | 1211 tal:content="contex/title"></td> |
| 1264 <td tal:condition="request/show/fixer" tal:content="contex/fixer"></td> | 1212 <td tal:condition="request/show/status" |
| 1213 tal:content="contex/status"></td> | |
| 1214 <td tal:condition="request/show/fixer" | |
| 1215 tal:content="contex/fixer"></td> | |
| 1265 </tr> | 1216 </tr> |
| 1266 | 1217 |
| 1267 Sorting | 1218 Sorting |
| 1268 """"""" | 1219 """"""" |
| 1269 | 1220 |
| 1270 String and Date values are sorted in the natural way. | 1221 String and Date values are sorted in the natural way. Link properties |
| 1271 Link properties are sorted according to the value of the | 1222 are sorted according to the value of the "order" property on the linked |
| 1272 "order" property on the linked items if it is present; or | 1223 items if it is present; or otherwise on the key string of the linked |
| 1273 otherwise on the key string of the linked items; or | 1224 items; or finally on the item ids. Multilink properties are sorted |
| 1274 finally on the item ids. Multilink properties are | 1225 according to how many links are present. |
| 1275 sorted according to how many links are present. | |
| 1276 | 1226 |
| 1277 Issue Views | 1227 Issue Views |
| 1278 ~~~~~~~~~~~ | 1228 ~~~~~~~~~~~ |
| 1279 | 1229 |
| 1280 An issue view contains an editor section and a spool section. | 1230 An issue view contains an editor section and a spool section. At the top |
| 1281 At the top of an issue view, links to superseding and superseded | 1231 of an issue view, links to superseding and superseded issues are always |
| 1282 issues are always displayed. | 1232 displayed. |
| 1283 | 1233 |
| 1284 Issue View Specifiers | 1234 Issue View Specifiers |
| 1285 """"""""""""""""""""" | 1235 """"""""""""""""""""" |
| 1286 | 1236 |
| 1287 An issue view specifier is simply the issue's designator:: | 1237 An issue view specifier is simply the issue's designator:: |
| 1290 | 1240 |
| 1291 | 1241 |
| 1292 Editor Section | 1242 Editor Section |
| 1293 """""""""""""" | 1243 """""""""""""" |
| 1294 | 1244 |
| 1295 The editor section is generated from a template | 1245 The editor section is generated from a template containing |
| 1296 containing <display> tags to insert | 1246 ``tal:content="context/<property>/<widget>"`` directives to insert the |
| 1297 the appropriate widgets for editing properties. | 1247 appropriate widgets for editing properties. |
| 1298 | 1248 |
| 1299 Here's an example of a basic editor template:: | 1249 Here's an example of a basic editor template:: |
| 1300 | 1250 |
| 1301 <table> | 1251 <table> |
| 1302 <tr> | 1252 <tr> |
| 1315 <textarea name=":note" rows=5 cols=60></textarea> | 1265 <textarea name=":note" rows=5 cols=60></textarea> |
| 1316 </td> | 1266 </td> |
| 1317 </tr> | 1267 </tr> |
| 1318 </table> | 1268 </table> |
| 1319 | 1269 |
| 1320 As shown in the example, the editor template can also include a ":note" field, | 1270 As shown in the example, the editor template can also include a ":note" |
| 1321 which is a text area for entering a note to go along with a change. | 1271 field, which is a text area for entering a note to go along with a |
| 1322 | 1272 change. |
| 1323 When a change is submitted, the system automatically | 1273 |
| 1324 generates a message describing the changed properties. | 1274 When a change is submitted, the system automatically generates a message |
| 1325 The message displays all of the property values on the | 1275 describing the changed properties. The message displays all of the |
| 1326 issue and indicates which ones have changed. | 1276 property values on the issue and indicates which ones have changed. An |
| 1327 An example of such a message might be this:: | 1277 example of such a message might be this:: |
| 1328 | 1278 |
| 1329 title: Polly Parrot is dead | 1279 title: Polly Parrot is dead |
| 1330 priority: critical | 1280 priority: critical |
| 1331 status: unread -> in-progress | 1281 status: unread -> in-progress |
| 1332 fixer: (none) | 1282 fixer: (none) |
| 1333 keywords: parrot,plumage,perch,nailed,dead | 1283 keywords: parrot,plumage,perch,nailed,dead |
| 1334 | 1284 |
| 1335 If a note is given in the ":note" field, the note is | 1285 If a note is given in the ":note" field, the note is appended to the |
| 1336 appended to the description. The message is then added | 1286 description. The message is then added to the issue's message spool |
| 1337 to the issue's message spool (thus triggering the standard | 1287 (thus triggering the standard detector to react by sending out this |
| 1338 detector to react by sending out this message to the nosy list). | 1288 message to the nosy list). |
| 1339 | 1289 |
| 1340 Spool Section | 1290 Spool Section |
| 1341 """"""""""""" | 1291 """"""""""""" |
| 1342 | 1292 |
| 1343 The spool section lists messages in the issue's "messages" | 1293 The spool section lists messages in the issue's "messages" property. |
| 1344 property. The index of messages displays the "date", "author", | 1294 The index of messages displays the "date", "author", and "summary" |
| 1345 and "summary" properties on the message items, and selecting a | 1295 properties on the message items, and selecting a message takes you to |
| 1346 message takes you to its content. | 1296 its content. |
| 1347 | 1297 |
| 1348 Access Control | 1298 Access Control |
| 1349 -------------- | 1299 -------------- |
| 1350 | 1300 |
| 1351 At each point that requires an action to be performed, the security mechanisms | 1301 At each point that requires an action to be performed, the security |
| 1352 are asked if the current user has permission. This permission is defined as a | 1302 mechanisms are asked if the current user has permission. This permission |
| 1353 Permission. | 1303 is defined as a Permission. |
| 1354 | 1304 |
| 1355 Individual assignment of Permission to user is unwieldy. The concept of a | 1305 Individual assignment of Permission to user is unwieldy. The concept of |
| 1356 Role, which encompasses several Permissions and may be assigned to many Users, | 1306 a Role, which encompasses several Permissions and may be assigned to |
| 1357 is quite well developed in many projects. Roundup will take this path, and | 1307 many Users, is quite well developed in many projects. Roundup will take |
| 1358 allow the multiple assignment of Roles to Users, and multiple Permissions to | 1308 this path, and allow the multiple assignment of Roles to Users, and |
| 1359 Roles. These definitions are not persistent - they're defined when the | 1309 multiple Permissions to Roles. These definitions are not persistent - |
| 1360 application initialises. | 1310 they're defined when the application initialises. |
| 1361 | 1311 |
| 1362 There will be two levels of Permission. The Class level permissions define | 1312 There will be two levels of Permission. The Class level permissions |
| 1363 logical permissions associated with all items of a particular class (or all | 1313 define logical permissions associated with all items of a particular |
| 1364 classes). The Item level permissions define logical permissions associated | 1314 class (or all classes). The Item level permissions define logical |
| 1365 with specific items by way of their user-linked properties. | 1315 permissions associated with specific items by way of their user-linked |
| 1316 properties. | |
| 1366 | 1317 |
| 1367 | 1318 |
| 1368 Access Control Interface Specification | 1319 Access Control Interface Specification |
| 1369 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 1320 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1370 | 1321 |
| 1374 ''' Defines a Permission with the attributes | 1325 ''' Defines a Permission with the attributes |
| 1375 - name | 1326 - name |
| 1376 - description | 1327 - description |
| 1377 - klass (optional) | 1328 - klass (optional) |
| 1378 | 1329 |
| 1379 The klass may be unset, indicating that this permission is not | 1330 The klass may be unset, indicating that this permission is |
| 1380 locked to a particular hyperdb class. There may be multiple | 1331 not locked to a particular hyperdb class. There may be |
| 1381 Permissions for the same name for different classes. | 1332 multiple Permissions for the same name for different |
| 1333 classes. | |
| 1382 ''' | 1334 ''' |
| 1383 | 1335 |
| 1384 class Role: | 1336 class Role: |
| 1385 ''' Defines a Role with the attributes | 1337 ''' Defines a Role with the attributes |
| 1386 - name | 1338 - name |
| 1388 - permissions | 1340 - permissions |
| 1389 ''' | 1341 ''' |
| 1390 | 1342 |
| 1391 class Security: | 1343 class Security: |
| 1392 def __init__(self, db): | 1344 def __init__(self, db): |
| 1393 ''' Initialise the permission and role stores, and add in the | 1345 ''' Initialise the permission and role stores, and add in |
| 1394 base roles (for admin user). | 1346 the base roles (for admin user). |
| 1395 ''' | 1347 ''' |
| 1396 | 1348 |
| 1397 def getPermission(self, permission, classname=None): | 1349 def getPermission(self, permission, classname=None): |
| 1398 ''' Find the Permission matching the name and for the class, if the | 1350 ''' Find the Permission matching the name and for the class, |
| 1399 classname is specified. | 1351 if the classname is specified. |
| 1400 | 1352 |
| 1401 Raise ValueError if there is no exact match. | 1353 Raise ValueError if there is no exact match. |
| 1402 ''' | 1354 ''' |
| 1403 | 1355 |
| 1404 def hasPermission(self, permission, userid, classname=None): | 1356 def hasPermission(self, permission, userid, classname=None): |
| 1405 ''' Look through all the Roles, and hence Permissions, and see if | 1357 ''' Look through all the Roles, and hence Permissions, and |
| 1406 "permission" is there for the specified classname. | 1358 see if "permission" is there for the specified |
| 1359 classname. | |
| 1407 ''' | 1360 ''' |
| 1408 | 1361 |
| 1409 def hasItemPermission(self, classname, itemid, **propspec): | 1362 def hasItemPermission(self, classname, itemid, **propspec): |
| 1410 ''' Check the named properties of the given item to see if the | 1363 ''' Check the named properties of the given item to see if |
| 1411 userid appears in them. If it does, then the user is granted | 1364 the userid appears in them. If it does, then the user is |
| 1412 this permission check. | 1365 granted this permission check. |
| 1413 | 1366 |
| 1414 'propspec' consists of a set of properties and values that | 1367 'propspec' consists of a set of properties and values |
| 1415 must be present on the given item for access to be granted. | 1368 that must be present on the given item for access to be |
| 1416 | 1369 granted. |
| 1417 If a property is a Link, the value must match the property | 1370 |
| 1418 value. If a property is a Multilink, the value must appear | 1371 If a property is a Link, the value must match the |
| 1419 in the Multilink list. | 1372 property value. If a property is a Multilink, the value |
| 1373 must appear in the Multilink list. | |
| 1420 ''' | 1374 ''' |
| 1421 | 1375 |
| 1422 def addPermission(self, **propspec): | 1376 def addPermission(self, **propspec): |
| 1423 ''' Create a new Permission with the properties defined in | 1377 ''' Create a new Permission with the properties defined in |
| 1424 'propspec' | 1378 'propspec' |
| 1425 ''' | 1379 ''' |
| 1426 | 1380 |
| 1427 def addRole(self, **propspec): | 1381 def addRole(self, **propspec): |
| 1428 ''' Create a new Role with the properties defined in 'propspec' | 1382 ''' Create a new Role with the properties defined in |
| 1383 'propspec' | |
| 1429 ''' | 1384 ''' |
| 1430 | 1385 |
| 1431 def addPermissionToRole(self, rolename, permission): | 1386 def addPermissionToRole(self, rolename, permission): |
| 1432 ''' Add the permission to the role's permission list. | 1387 ''' Add the permission to the role's permission list. |
| 1433 | 1388 |
| 1438 permissions like so (this example is ``cgi/client.py``):: | 1393 permissions like so (this example is ``cgi/client.py``):: |
| 1439 | 1394 |
| 1440 def initialiseSecurity(security): | 1395 def initialiseSecurity(security): |
| 1441 ''' Create some Permissions and Roles on the security object | 1396 ''' Create some Permissions and Roles on the security object |
| 1442 | 1397 |
| 1443 This function is directly invoked by security.Security.__init__() | 1398 This function is directly invoked by |
| 1444 as a part of the Security object instantiation. | 1399 security.Security.__init__() as a part of the Security |
| 1400 object instantiation. | |
| 1445 ''' | 1401 ''' |
| 1446 p = security.addPermission(name="Web Registration", | 1402 p = security.addPermission(name="Web Registration", |
| 1447 description="Anonymous users may register through the web") | 1403 description="Anonymous users may register through the web") |
| 1448 security.addToRole('Anonymous', p) | 1404 security.addToRole('Anonymous', p) |
| 1449 | 1405 |
| 1450 Detectors may also define roles in their init() function:: | 1406 Detectors may also define roles in their init() function:: |
| 1451 | 1407 |
| 1452 def init(db): | 1408 def init(db): |
| 1453 # register an auditor that checks that a user has the "May Resolve" | 1409 # register an auditor that checks that a user has the "May |
| 1454 # Permission before allowing them to set an issue status to "resolved" | 1410 # Resolve" Permission before allowing them to set an issue |
| 1411 # status to "resolved" | |
| 1455 db.issue.audit('set', checkresolvedok) | 1412 db.issue.audit('set', checkresolvedok) |
| 1456 p = db.security.addPermission(name="May Resolve", klass="issue") | 1413 p = db.security.addPermission(name="May Resolve", klass="issue") |
| 1457 security.addToRole('Manager', p) | 1414 security.addToRole('Manager', p) |
| 1458 | 1415 |
| 1459 The tracker dbinit module then has in ``open()``:: | 1416 The tracker dbinit module then has in ``open()``:: |
| 1485 # all ok | 1442 # all ok |
| 1486 | 1443 |
| 1487 if db.security.hasItemPermission('issue', itemid, assignedto=userid): | 1444 if db.security.hasItemPermission('issue', itemid, assignedto=userid): |
| 1488 # all ok | 1445 # all ok |
| 1489 | 1446 |
| 1490 Code in the core will make use of these methods, as should code in auditors in | 1447 Code in the core will make use of these methods, as should code in |
| 1491 custom templates. The HTML templating may access the access controls through | 1448 auditors in custom templates. The HTML templating may access the access |
| 1492 the *user* attribute of the *request* variable. It exposes a ``hasPermission()`` | 1449 controls through the *user* attribute of the *request* variable. It |
| 1493 method:: | 1450 exposes a ``hasPermission()`` method:: |
| 1494 | 1451 |
| 1495 tal:condition="python:request.user.hasPermission('Edit', 'issue')" | 1452 tal:condition="python:request.user.hasPermission('Edit', 'issue')" |
| 1496 | 1453 |
| 1497 or, if the *context* is *issue*, then the following is the same:: | 1454 or, if the *context* is *issue*, then the following is the same:: |
| 1498 | 1455 |
| 1500 | 1457 |
| 1501 | 1458 |
| 1502 Authentication of Users | 1459 Authentication of Users |
| 1503 ~~~~~~~~~~~~~~~~~~~~~~~ | 1460 ~~~~~~~~~~~~~~~~~~~~~~~ |
| 1504 | 1461 |
| 1505 Users must be authenticated correctly for the above controls to work. This is | 1462 Users must be authenticated correctly for the above controls to work. |
| 1506 not done in the current mail gateway at all. Use of digital signing of | 1463 This is not done in the current mail gateway at all. Use of digital |
| 1507 messages could alleviate this problem. | 1464 signing of messages could alleviate this problem. |
| 1508 | 1465 |
| 1509 The exact mechanism of registering the digital signature should be flexible, | 1466 The exact mechanism of registering the digital signature should be |
| 1510 with perhaps a level of trust. Users who supply their signature through their | 1467 flexible, with perhaps a level of trust. Users who supply their |
| 1511 first message into the tracker should be at a lower level of trust to those | 1468 signature through their first message into the tracker should be at a |
| 1512 who supply their signature to an admin for submission to their user details. | 1469 lower level of trust to those who supply their signature to an admin for |
| 1470 submission to their user details. | |
| 1513 | 1471 |
| 1514 | 1472 |
| 1515 Anonymous Users | 1473 Anonymous Users |
| 1516 ~~~~~~~~~~~~~~~ | 1474 ~~~~~~~~~~~~~~~ |
| 1517 | 1475 |
| 1521 | 1479 |
| 1522 | 1480 |
| 1523 Use Cases | 1481 Use Cases |
| 1524 ~~~~~~~~~ | 1482 ~~~~~~~~~ |
| 1525 | 1483 |
| 1526 public - end users can submit bugs, request new features, request support | 1484 public - end users can submit bugs, request new features, request |
| 1527 The Users would be given the default "User" Role which gives "View" and | 1485 support |
| 1528 "Edit" Permission to the "issue" class. | 1486 The Users would be given the default "User" Role which gives "View" |
| 1529 developer - developers can fix bugs, implement new features, provide support | 1487 and "Edit" Permission to the "issue" class. |
| 1530 A new Role "Developer" is created with the Permission "Fixer" which is | 1488 developer - developers can fix bugs, implement new features, provide |
| 1531 checked for in custom auditors that see whether the issue is being | 1489 support |
| 1532 resolved with a particular resolution ("fixed", "implemented", | 1490 A new Role "Developer" is created with the Permission "Fixer" which |
| 1491 is checked for in custom auditors that see whether the issue is | |
| 1492 being resolved with a particular resolution ("fixed", "implemented", | |
| 1533 "supported") and allows that resolution only if the permission is | 1493 "supported") and allows that resolution only if the permission is |
| 1534 available. | 1494 available. |
| 1535 manager - approvers/managers can approve new features and signoff bug fixes | 1495 manager - approvers/managers can approve new features and signoff bug |
| 1536 A new Role "Manager" is created with the Permission "Signoff" which is | 1496 fixes |
| 1537 checked for in custom auditors that see whether the issue status is being | 1497 A new Role "Manager" is created with the Permission "Signoff" which |
| 1538 changed similar to the developer example. | 1498 is checked for in custom auditors that see whether the issue status |
| 1539 admin - administrators can add users and set user's roles | 1499 is being changed similar to the developer example. admin - |
| 1540 The existing Role "Admin" has the Permissions "Edit" for all classes | 1500 administrators can add users and set user's roles The existing Role |
| 1541 (including "user") and "Web Roles" which allow the desired actions. | 1501 "Admin" has the Permissions "Edit" for all classes (including |
| 1542 system - automated request handlers running various report/escalation scripts | 1502 "user") and "Web Roles" which allow the desired actions. |
| 1543 A combination of existing and new Roles, Permissions and auditors could | 1503 system - automated request handlers running various report/escalation |
| 1544 be used here. | 1504 scripts |
| 1505 A combination of existing and new Roles, Permissions and auditors | |
| 1506 could be used here. | |
| 1545 privacy - issues that are only visible to some users | 1507 privacy - issues that are only visible to some users |
| 1546 A new property is added to the issue which marks the user or group of | 1508 A new property is added to the issue which marks the user or group |
| 1547 users who are allowed to view and edit the issue. An auditor will check | 1509 of users who are allowed to view and edit the issue. An auditor will |
| 1548 for edit access, and the template user object can check for view access. | 1510 check for edit access, and the template user object can check for |
| 1511 view access. | |
| 1549 | 1512 |
| 1550 | 1513 |
| 1551 Deployment Scenarios | 1514 Deployment Scenarios |
| 1552 -------------------- | 1515 -------------------- |
| 1553 | 1516 |
| 1554 The design described above should be general enough | 1517 The design described above should be general enough to permit the use of |
| 1555 to permit the use of Roundup for bug tracking, managing | 1518 Roundup for bug tracking, managing projects, managing patches, or |
| 1556 projects, managing patches, or holding discussions. By | 1519 holding discussions. By using items of multiple types, one could deploy |
| 1557 using items of multiple types, one could deploy a system | 1520 a system that maintains requirement specifications, catalogs bugs, and |
| 1558 that maintains requirement specifications, catalogs bugs, | 1521 manages submitted patches, where patches could be linked to the bugs and |
| 1559 and manages submitted patches, where patches could be | 1522 requirements they address. |
| 1560 linked to the bugs and requirements they address. | |
| 1561 | 1523 |
| 1562 | 1524 |
| 1563 Acknowledgements | 1525 Acknowledgements |
| 1564 ---------------- | 1526 ---------------- |
| 1565 | 1527 |
| 1566 My thanks are due to Christy Heyl for | 1528 My thanks are due to Christy Heyl for reviewing and contributing |
| 1567 reviewing and contributing suggestions to this paper | 1529 suggestions to this paper and motivating me to get it done, and to Jesse |
| 1568 and motivating me to get it done, and to | 1530 Vincent, Mark Miller, Christopher Simons, Jeff Dunmall, Wayne Gramlich, |
| 1569 Jesse Vincent, Mark Miller, Christopher Simons, | 1531 and Dean Tribble for their assistance with the first-round submission. |
| 1570 Jeff Dunmall, Wayne Gramlich, and Dean Tribble for | |
| 1571 their assistance with the first-round submission. | |
| 1572 | 1532 |
| 1573 Changes to this document | 1533 Changes to this document |
| 1574 ------------------------ | 1534 ------------------------ |
| 1575 | 1535 |
| 1576 - Added Boolean and Number types | 1536 - Added Boolean and Number types |
| 1577 - Added section Hyperdatabase Implementations | 1537 - Added section Hyperdatabase Implementations |
| 1578 - "Item" has been renamed to "Issue" to account for the more specific nature | 1538 - "Item" has been renamed to "Issue" to account for the more specific |
| 1579 of the Class. | 1539 nature of the Class. |
| 1580 - New Templating | 1540 - New Templating |
| 1581 - Access Controls | 1541 - Access Controls |
| 1582 | 1542 |
| 1583 ------------------ | 1543 ------------------ |
| 1584 | 1544 |
