comparison doc/spec.html @ 71:5147b4c51fd5

Added the Roundup spec to the new documentation directory.
author Richard Jones <richard@users.sourceforge.net>
date Wed, 25 Jul 2001 01:23:07 +0000
parents
children 944bb5255eaf
comparison
equal deleted inserted replaced
70:d95a03480fd1 71:5147b4c51fd5
1 <html>
2 <head>
3 <title>Software Carpentry Track: Roundup</title>
4 </head>
5 <body bgcolor=white>
6
7 <table width="100%">
8 <tr>
9
10 <td align="left">
11 <a href="http://www.software-carpentry.com"><img src="images/logo-software-carpentry-standard.gif" alt="[Software Carpentry logo]" border="0"></a>
12 </td>
13
14 <td align="right">
15 <table>
16 <tr><td>
17 <a href="http://www.acl.lanl.gov"><img src="images//logo-acl-medium.gif" alt="[ACL Logo]" border="0"></a>
18 </td></tr>
19 <tr><td><hr></td></tr>
20 <tr><td>
21 <a href="http://www.codesourcery.com"><img src="images/logo-codesourcery-medium.gif" alt="[CodeSourcery Logo]" border="0"></a>
22 </td></tr>
23 </table>
24 </td>
25
26 </tr>
27 </table>
28
29 <hr><p>
30
31 <h1 align=center>Roundup</h1>
32 <h3 align=center>An Issue-Tracking System for Knowledge Workers</h3>
33 <h4 align=center><a href="http://www.lfw.org/ping/">Ka-Ping Yee</a><br>
34 <a href="mailto:ping@lfw.org">ping@lfw.org</a></h4>
35 <h3 align=center>Implementation Guide</h3>
36
37 <h2>Contents</h2>
38
39 <ol>
40 <li>Introduction
41 <li>The Layer Cake
42 <li>Hyperdatabase
43 <ol>
44 <li>Dates and Date Arithmetic
45 <li>Nodes and Classes
46 <li>Identifiers and Designators
47 <li>Property Names and Types
48 <li>Interface Specification
49 <li>Application Example
50 </ol>
51 <li>Roundup Database
52 <ol>
53 <li>Reserved Classes
54 <ol>
55 <li>Users
56 <li>Messages
57 <li>Files
58 </ol>
59 <li>Item Classes
60 <li>Interface Specification
61 <li>Default Schema
62 </ol>
63 <li>Detector Interface
64 <ol>
65 <li>Interface Specification
66 <li>Detector Example
67 </ol>
68 <li>Command Interface
69 <ol>
70 <li>Interface Specification
71 <li>Usage Example
72 </ol>
73 <li>E-mail User Interface
74 <ol>
75 <li>Message Processing
76 <li>Nosy Lists
77 <li>Setting Properties
78 <li>Workflow Example
79 </ol>
80 <li>Web User Interface
81 <ol>
82 <li>Views and View Specifiers
83 <li>Displaying Properties
84 <li>Index Views
85 <ol>
86 <li>Index View Specifiers
87 <li>Filter Section
88 <li>Index Section
89 <li>Sorting
90 </ol>
91 <li>Item Views
92 <ol>
93 <li>Item View Specifiers
94 <li>Editor Section
95 <li>Spool Section
96 </ol>
97 </ol>
98 <li>Deployment Scenarios
99 <li>Acknowledgements
100 </ol>
101
102 <p><hr>
103 <h2>1. Introduction</h2>
104
105 <p>This document presents a description of the components
106 of the Roundup system and specifies their interfaces and
107 behaviour in sufficient detail to guide an implementation.
108 For the philosophy and rationale behind the Roundup design,
109 see the first-round Software Carpentry submission for Roundup.
110 This document fleshes out that design as well as specifying
111 interfaces so that the components can be developed separately.
112
113 <p><hr>
114 <h2>2. The Layer Cake</h2>
115
116 <p>Lots of software design documents come with a picture of
117 a cake. Everybody seems to like them. I also like cakes
118 (i think they are tasty). So i, too, shall include
119 a picture of a cake here.
120
121 <p align=center><table cellspacing=0 cellpadding=10 border=0 align=center>
122 <tr>
123 <td bgcolor="#e8e8e8" align=center>
124 <p><font face="helvetica, arial"><small>
125 E-mail Client
126 </small></font>
127 </td>
128 <td bgcolor="#e0e0e0" align="center">
129 <p><font face="helvetica, arial"><small>
130 Web Browser
131 </small></font>
132 </td>
133 <td bgcolor="#e8e8e8" align=center>
134 <p><font face="helvetica, arial"><small>
135 Detector Scripts
136 </small></font>
137 </td>
138 <td bgcolor="#e0e0e0" align="center">
139 <p><font face="helvetica, arial"><small>
140 Shell
141 </small></font>
142 </td>
143 <tr>
144 <td bgcolor="#d0d0f0" align=center>
145 <p><font face="helvetica, arial"><small>
146 E-mail User Interface
147 </small></font>
148 </td>
149 <td bgcolor="#f0d0d0" align=center>
150 <p><font face="helvetica, arial"><small>
151 Web User Interface
152 </small></font>
153 </td>
154 <td bgcolor="#d0f0d0" align=center>
155 <p><font face="helvetica, arial"><small>
156 Detector Interface
157 </small></font>
158 </td>
159 <td bgcolor="#f0d0f0" align=center>
160 <p><font face="helvetica, arial"><small>
161 Command Interface
162 </small></font>
163 </td>
164 <tr>
165 <td bgcolor="#f0f0d0" colspan=4 align=center>
166 <p><font face="helvetica, arial"><small>
167 Roundup Database Layer
168 </small></font>
169 </td>
170 <tr>
171 <td bgcolor="#d0f0f0" colspan=4 align=center>
172 <p><font face="helvetica, arial"><small>
173 Hyperdatabase Layer
174 </small></font>
175 </td>
176 <tr>
177 <td bgcolor="#e8e8e8" colspan=4 align=center>
178 <p><font face="helvetica, arial"><small>
179 Storage Layer
180 </small></font>
181 </td>
182 </table>
183
184 <p>The colourful parts of the cake are part of our system;
185 the faint grey parts of the cake are external components.
186
187 <p>I will now proceed to forgo all table manners and
188 eat from the bottom of the cake to the top. You may want
189 to stand back a bit so you don't get covered in crumbs.
190
191 <p><hr>
192 <h2>3. Hyperdatabase</h2>
193
194 <p>The lowest-level component to be implemented is the hyperdatabase.
195 The hyperdatabase is intended to be
196 a flexible data store that can hold configurable data in
197 records which we call <em>nodes</em>.
198
199 <p>The hyperdatabase is implemented on top of the storage layer,
200 an external module for storing its data. The storage layer could
201 be a third-party RDBMS; for a "batteries-included" distribution,
202 implementing the hyperdatabase on the standard <tt>bsddb</tt>
203 module is suggested.
204
205 <h3>3.1. Dates and Date Arithmetic</h3>
206
207 <p>Before we get into the hyperdatabase itself, we need a
208 way of handling dates. The hyperdatabase module provides
209 Timestamp objects for
210 representing date-and-time stamps and Interval objects for
211 representing date-and-time intervals.
212
213 <p>As strings, date-and-time stamps are specified with
214 the date in international standard format
215 (<em>yyyy</em>-<em>mm</em>-<em>dd</em>)
216 joined to the time (<em>hh</em>:<em>mm</em>:<em>ss</em>)
217 by a period ("."). Dates in
218 this form can be easily compared and are fairly readable
219 when printed. An example of a valid stamp is
220 "<strong>2000-06-24.13:03:59</strong>".
221 We'll call this the "full date format". When Timestamp objects are
222 printed as strings, they appear in the full date format with
223 the time always given in GMT. The full date format is always
224 exactly 19 characters long.
225
226 <p>For user input, some partial forms are also permitted:
227 the whole time or just the seconds may be omitted; and the whole date
228 may be omitted or just the year may be omitted. If the time is given,
229 the time is interpreted in the user's local time zone.
230 The <tt>Date</tt> constructor takes care of these conversions.
231 In the following examples, suppose that <em>yyyy</em> is the current year,
232 <em>mm</em> is the current month, and <em>dd</em> is the current
233 day of the month; and suppose that the user is on Eastern Standard Time.
234
235 <ul>
236 <li>"<strong>2000-04-17</strong>" means &lt;Date 2000-04-17.00:00:00&gt;
237 <li>"<strong>01-25</strong>" means &lt;Date <em>yyyy</em>-01-25.00:00:00&gt;
238 <li>"<strong>2000-04-17.03:45</strong>" means &lt;Date 2000-04-17.08:45:00&gt;
239 <li>"<strong>08-13.22:13</strong>" means &lt;Date <em>yyyy</em>-08-14.03:13:00&gt;
240 <li>"<strong>11-07.09:32:43</strong>" means &lt;Date <em>yyyy</em>-11-07.14:32:43&gt;
241 <li>"<strong>14:25</strong>" means
242 &lt;Date <em>yyyy</em>-<em>mm</em>-<em>dd</em>.19:25:00&gt;
243 <li>"<strong>8:47:11</strong>" means
244 &lt;Date <em>yyyy</em>-<em>mm</em>-<em>dd</em>.13:47:11&gt;
245 <li>the special date "<strong>.</strong>" means "right now"
246 </ul>
247
248 <p>Date intervals are specified using the suffixes
249 "y", "m", and "d". The suffix "w" (for "week") means 7 days.
250 Time intervals are specified in hh:mm:ss format (the seconds
251 may be omitted, but the hours and minutes may not).
252
253 <ul>
254 <li>"<strong>3y</strong>" means three years
255 <li>"<strong>2y 1m</strong>" means two years and one month
256 <li>"<strong>1m 25d</strong>" means one month and 25 days
257 <li>"<strong>2w 3d</strong>" means two weeks and three days
258 <li>"<strong>1d 2:50</strong>" means one day, two hours, and 50 minutes
259 <li>"<strong>14:00</strong>" means 14 hours
260 <li>"<strong>0:04:33</strong>" means four minutes and 33 seconds
261 </ul>
262
263 <p>The Date class should understand simple date expressions of the form
264 <em>stamp</em> + <em>interval</em> and <em>stamp</em> - <em>interval</em>.
265 When adding or subtracting intervals involving months or years, the
266 components are handled separately. For example, when evaluating
267 "<strong>2000-06-25 + 1m 10d</strong>", we first add one month to
268 get <strong>2000-07-25</strong>, then add 10 days to get
269 <strong>2000-08-04</strong> (rather than trying to decide whether
270 <strong>1m 10d</strong> means 38 or 40 or 41 days).
271
272 <p>Here is an outline of the Date and Interval classes.
273
274 <blockquote>
275 <pre><small>class <strong>Date</strong>:
276 def <strong>__init__</strong>(self, spec, offset):
277 """Construct a date given a specification and a time zone offset.
278
279 'spec' is a full date or a partial form, with an optional
280 added or subtracted interval. 'offset' is the local time
281 zone offset from GMT in hours.
282 """
283
284 def <strong>__add__</strong>(self, interval):
285 """Add an interval to this date to produce another date."""
286
287 def <strong>__sub__</strong>(self, interval):
288 """Subtract an interval from this date to produce another date."""
289
290 def <strong>__cmp__</strong>(self, other):
291 """Compare this date to another date."""
292
293 def <strong>__str__</strong>(self):
294 """Return this date as a string in the yyyy-mm-dd.hh:mm:ss format."""
295
296 def <strong>local</strong>(self, offset):
297 """Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone."""
298
299 class <strong>Interval</strong>:
300 def <strong>__init__</strong>(self, spec):
301 """Construct an interval given a specification."""
302
303 def <strong>__cmp__</strong>(self, other):
304 """Compare this interval to another interval."""
305
306 def <strong>__str__</strong>(self):
307 """Return this interval as a string."""
308 </small></pre>
309 </blockquote>
310
311 <p>Here are some examples of how these classes would behave in practice.
312 For the following examples, assume that we are on Eastern Standard
313 Time and the current local time is 19:34:02 on 25 June 2000.
314
315 <blockquote><pre><small
316 >&gt;&gt;&gt; <span class="input">Date(".")</span>
317 <span class="output">&lt;Date 2000-06-26.00:34:02&gt;</span>
318 &gt;&gt;&gt; <span class="input">_.local(-5)</span>
319 <span class="output">"2000-06-25.19:34:02"</span>
320 &gt;&gt;&gt; <span class="input">Date(". + 2d")</span>
321 <span class="output">&lt;Date 2000-06-28.00:34:02&gt;</span>
322 &gt;&gt;&gt; <span class="input">Date("1997-04-17", -5)</span>
323 <span class="output">&lt;Date 1997-04-17.00:00:00&gt;</span>
324 &gt;&gt;&gt; <span class="input">Date("01-25", -5)</span>
325 <span class="output">&lt;Date 2000-01-25.00:00:00&gt;</span>
326 &gt;&gt;&gt; <span class="input">Date("08-13.22:13", -5)</span>
327 <span class="output">&lt;Date 2000-08-14.03:13:00&gt;</span>
328 &gt;&gt;&gt; <span class="input">Date("14:25", -5)</span>
329 <span class="output">&lt;Date 2000-06-25.19:25:00&gt;</span>
330 &gt;&gt;&gt; <span class="input">Interval(" 3w 1 d 2:00")</span>
331 <span class="output">&lt;Interval 22d 2:00&gt;</span>
332 &gt;&gt;&gt; <span class="input">Date(". + 2d") - Interval("3w")</span>
333 <span class="output">&lt;Date 2000-06-07.00:34:02&gt;</span
334 ></small></pre></blockquote>
335
336 <h3>3.2. Nodes and Classes</h3>
337
338 <p>Nodes contain data in <em>properties</em>. To Python, these
339 properties are presented as the key-value pairs of a dictionary.
340 Each node belongs to a <em>class</em> which defines the names
341 and types of its properties. The database permits the creation
342 and modification of classes as well as nodes.
343
344 <h3>3.3. Identifiers and Designators</h3>
345
346 <p>Each node has a numeric identifier which is unique among
347 nodes in its class. The nodes are numbered sequentially
348 within each class in order of creation, starting from 1.
349 The <em>designator</em>
350 for a node is a way to identify a node in the database, and
351 consists of the name of the node's class concatenated with
352 the node's numeric identifier.
353
354 <p>For example, if "spam" and "eggs" are classes, the first
355 node created in class "spam" has id 1 and designator "spam1".
356 The first node created in class "eggs" also has id 1 but has
357 the distinct designator "eggs1". Node designators are
358 conventionally enclosed in square brackets when mentioned
359 in plain text. This permits a casual mention of, say,
360 "[patch37]" in an e-mail message to be turned into an active
361 hyperlink.
362
363 <h3>3.4. Property Names and Types</h3>
364
365 <p>Property names must begin with a letter.
366
367 <p>A property may be one of five <em>basic types</em>:
368
369 <ul>
370 <li><em>String</em> properties are for storing arbitrary-length
371 strings.
372
373 <li><em>Date</em> properties store date-and-time stamps.
374 Their values are Timestamp objects.
375
376 <li>A <em>Link</em> property refers to a single other node
377 selected from a specified class. The class is part of the property;
378 the value is an integer, the id of the chosen node.
379
380 <li>A <em>Multilink</em> property refers to possibly many nodes
381 in a specified class. The value is a list of integers.
382 </ul>
383
384 <p><tt>None</tt> is also a permitted value for any of these property
385 types. An attempt to store <tt>None</tt> into a String property
386 stores the empty string; an attempt to store <tt>None</tt>
387 into a Multilink property stores an empty list.
388
389 <h3>3.5. Interface Specification</h3>
390
391 <p>The hyperdb module provides property objects to designate
392 the different kinds of properties. These objects are used when
393 specifying what properties belong in classes.
394
395 <blockquote><pre><small
396 >class <strong>String</strong>:
397 def <strong>__init__</strong>(self):
398 """An object designating a String property."""
399
400 class <strong>Date</strong>:
401 def <strong>__init__</strong>(self):
402 """An object designating a Date property."""
403
404 class <strong>Link</strong>:
405 def <strong>__init__</strong>(self, classname):
406 """An object designating a Link property that links to
407 nodes in a specified class."""
408
409 class <strong>Multilink</strong>:
410 def <strong>__init__</strong>(self, classname):
411 """An object designating a Multilink property that links
412 to nodes in a specified class."""
413 </small></pre></blockquote>
414
415 <p>Here is the interface provided by the hyperdatabase.
416
417 <blockquote><pre><small
418 >class <strong>Database</strong>:
419 """A database for storing records containing flexible data types."""
420
421 def <strong>__init__</strong>(self, storagelocator, journaltag):
422 """Open a hyperdatabase given a specifier to some storage.
423
424 The meaning of 'storagelocator' depends on the particular
425 implementation of the hyperdatabase. It could be a file name,
426 a directory path, a socket descriptor for a connection to a
427 database over the network, etc.
428
429 The 'journaltag' is a token that will be attached to the journal
430 entries for any edits done on the database. If 'journaltag' is
431 None, the database is opened in read-only mode: the Class.create(),
432 Class.set(), and Class.retire() methods are disabled.
433 """
434
435 def <strong>__getattr__</strong>(self, classname):
436 """A convenient way of calling self.getclass(classname)."""
437
438 def <strong>getclasses</strong>(self):
439 """Return a list of the names of all existing classes."""
440
441 def <strong>getclass</strong>(self, classname):
442 """Get the Class object representing a particular class.
443
444 If 'classname' is not a valid class name, a KeyError is raised.
445 """
446
447 class <strong>Class</strong>:
448 """The handle to a particular class of nodes in a hyperdatabase."""
449
450 def <strong>__init__</strong>(self, db, classname, **properties):
451 """Create a new class with a given name and property specification.
452
453 'classname' must not collide with the name of an existing class,
454 or a ValueError is raised. The keyword arguments in 'properties'
455 must map names to property objects, or a TypeError is raised.
456 """
457
458 # Editing nodes:
459
460 def <strong>create</strong>(self, **propvalues):
461 """Create a new node of this class and return its id.
462
463 The keyword arguments in 'propvalues' map property names to values.
464 The values of arguments must be acceptable for the types of their
465 corresponding properties or a TypeError is raised. If this class
466 has a key property, it must be present and its value must not
467 collide with other key strings or a ValueError is raised. Any other
468 properties on this class that are missing from the 'propvalues'
469 dictionary are set to None. If an id in a link or multilink
470 property does not refer to a valid node, an IndexError is raised.
471 """
472
473 def <strong>get</strong>(self, nodeid, propname):
474 """Get the value of a property on an existing node of this class.
475
476 'nodeid' must be the id of an existing node of this class or an
477 IndexError is raised. 'propname' must be the name of a property
478 of this class or a KeyError is raised.
479 """
480
481 def <strong>set</strong>(self, nodeid, **propvalues):
482 """Modify a property on an existing node of this class.
483
484 'nodeid' must be the id of an existing node of this class or an
485 IndexError is raised. Each key in 'propvalues' must be the name
486 of a property of this class or a KeyError is raised. All values
487 in 'propvalues' must be acceptable types for their corresponding
488 properties or a TypeError is raised. If the value of the key
489 property is set, it must not collide with other key strings or a
490 ValueError is raised. If the value of a Link or Multilink
491 property contains an invalid node id, a ValueError is raised.
492 """
493
494 def <strong>retire</strong>(self, nodeid):
495 """Retire a node.
496
497 The properties on the node remain available from the get() method,
498 and the node's id is never reused. Retired nodes are not returned
499 by the find(), list(), or lookup() methods, and other nodes may
500 reuse the values of their key properties.
501 """
502
503 def <strong>history</strong>(self, nodeid):
504 """Retrieve the journal of edits on a particular node.
505
506 'nodeid' must be the id of an existing node of this class or an
507 IndexError is raised.
508
509 The returned list contains tuples of the form
510
511 (date, tag, action, params)
512
513 'date' is a Timestamp object specifying the time of the change and
514 'tag' is the journaltag specified when the database was opened.
515 'action' may be:
516
517 'create' or 'set' -- 'params' is a dictionary of property values
518 'link' or 'unlink' -- 'params' is (classname, nodeid, propname)
519 'retire' -- 'params' is None
520 """
521
522 # Locating nodes:
523
524 def <strong>setkey</strong>(self, propname):
525 """Select a String property of this class to be the key property.
526
527 'propname' must be the name of a String property of this class or
528 None, or a TypeError is raised. The values of the key property on
529 all existing nodes must be unique or a ValueError is raised.
530 """
531
532 def <strong>getkey</strong>(self):
533 """Return the name of the key property for this class or None."""
534
535 def <strong>lookup</strong>(self, keyvalue):
536 """Locate a particular node by its key property and return its id.
537
538 If this class has no key property, a TypeError is raised. If the
539 'keyvalue' matches one of the values for the key property among
540 the nodes in this class, the matching node's id is returned;
541 otherwise a KeyError is raised.
542 """
543
544 def <strong>find</strong>(self, propname, nodeid):
545 """Get the ids of nodes in this class which link to a given node.
546
547 'propname' must be the name of a property in this class, or a
548 KeyError is raised. That property must be a Link or Multilink
549 property, or a TypeError is raised. 'nodeid' must be the id of
550 an existing node in the class linked to by the given property,
551 or an IndexError is raised.
552 """
553
554 def <strong>list</strong>(self):
555 """Return a list of the ids of the active nodes in this class."""
556
557 def <strong>count</strong>(self):
558 """Get the number of nodes in this class.
559
560 If the returned integer is 'numnodes', the ids of all the nodes
561 in this class run from 1 to numnodes, and numnodes+1 will be the
562 id of the next node to be created in this class.
563 """
564
565 # Manipulating properties:
566
567 def <strong>getprops</strong>(self):
568 """Return a dictionary mapping property names to property objects."""
569
570 def <strong>addprop</strong>(self, **properties):
571 """Add properties to this class.
572
573 The keyword arguments in 'properties' must map names to property
574 objects, or a TypeError is raised. None of the keys in 'properties'
575 may collide with the names of existing properties, or a ValueError
576 is raised before any properties have been added.
577 """</small></pre></blockquote>
578
579 <h3>3.6. Application Example</h3>
580
581 <p>Here is an example of how the hyperdatabase module would work in practice.
582
583 <blockquote><pre><small
584 >&gt;&gt;&gt; <span class="input">import hyperdb</span>
585 &gt;&gt;&gt; <span class="input">db = hyperdb.Database("foo.db", "ping")</span>
586 &gt;&gt;&gt; <span class="input">db</span>
587 <span class="output">&lt;hyperdb.Database "foo.db" opened by "ping"&gt;</span>
588 &gt;&gt;&gt; <span class="input">hyperdb.Class(db, "status", name=hyperdb.String())</span>
589 <span class="output">&lt;hyperdb.Class "status"&gt;</span>
590 &gt;&gt;&gt; <span class="input">_.setkey("name")</span>
591 &gt;&gt;&gt; <span class="input">db.status.create(name="unread")</span>
592 <span class="output">1</span>
593 &gt;&gt;&gt; <span class="input">db.status.create(name="in-progress")</span>
594 <span class="output">2</span>
595 &gt;&gt;&gt; <span class="input">db.status.create(name="testing")</span>
596 <span class="output">3</span>
597 &gt;&gt;&gt; <span class="input">db.status.create(name="resolved")</span>
598 <span class="output">4</span>
599 &gt;&gt;&gt; <span class="input">db.status.count()</span>
600 <span class="output">4</span>
601 &gt;&gt;&gt; <span class="input">db.status.list()</span>
602 <span class="output">[1, 2, 3, 4]</span>
603 &gt;&gt;&gt; <span class="input">db.status.lookup("in-progress")</span>
604 <span class="output">2</span>
605 &gt;&gt;&gt; <span class="input">db.status.retire(3)</span>
606 &gt;&gt;&gt; <span class="input">db.status.list()</span>
607 <span class="output">[1, 2, 4]</span>
608 &gt;&gt;&gt; <span class="input">hyperdb.Class(db, "issue", title=hyperdb.String(), status=hyperdb.Link("status"))</span>
609 <span class="output">&lt;hyperdb.Class "issue"&gt;</span>
610 &gt;&gt;&gt; <span class="input">db.issue.create(title="spam", status=1)</span>
611 <span class="output">1</span>
612 &gt;&gt;&gt; <span class="input">db.issue.create(title="eggs", status=2)</span>
613 <span class="output">2</span>
614 &gt;&gt;&gt; <span class="input">db.issue.create(title="ham", status=4)</span>
615 <span class="output">3</span>
616 &gt;&gt;&gt; <span class="input">db.issue.create(title="arguments", status=2)</span>
617 <span class="output">4</span>
618 &gt;&gt;&gt; <span class="input">db.issue.create(title="abuse", status=1)</span>
619 <span class="output">5</span>
620 &gt;&gt;&gt; <span class="input">hyperdb.Class(db, "user", username=hyperdb.Key(), password=hyperdb.String())</span>
621 <span class="output">&lt;hyperdb.Class "user"&gt;</span>
622 &gt;&gt;&gt; <span class="input">db.issue.addprop(fixer=hyperdb.Link("user"))</span>
623 &gt;&gt;&gt; <span class="input">db.issue.getprops()</span>
624 <span class="output"
625 >{"title": &lt;hyperdb.String&gt;, "status": &lt;hyperdb.Link to "status"&gt;,
626 "user": &lt;hyperdb.Link to "user"&gt;}</span>
627 &gt;&gt;&gt; <span class="input">db.issue.set(5, status=2)</span>
628 &gt;&gt;&gt; <span class="input">db.issue.get(5, "status")</span>
629 <span class="output">2</span>
630 &gt;&gt;&gt; <span class="input">db.status.get(2, "name")</span>
631 <span class="output">"in-progress"</span>
632 &gt;&gt;&gt; <span class="input">db.issue.get(5, "title")</span>
633 <span class="output">"abuse"</span>
634 &gt;&gt;&gt; <span class="input">db.issue.find("status", db.status.lookup("in-progress"))</span>
635 <span class="output">[2, 4, 5]</span>
636 &gt;&gt;&gt; <span class="input">db.issue.history(5)</span>
637 <span class="output"
638 >[(&lt;Date 2000-06-28.19:09:43&gt;, "ping", "create", {"title": "abuse", "status": 1}),
639 (&lt;Date 2000-06-28.19:11:04&gt;, "ping", "set", {"status": 2})]</span>
640 &gt;&gt;&gt; <span class="input">db.status.history(1)</span>
641 <span class="output"
642 >[(&lt;Date 2000-06-28.19:09:43&gt;, "ping", "link", ("issue", 5, "status")),
643 (&lt;Date 2000-06-28.19:11:04&gt;, "ping", "unlink", ("issue", 5, "status"))]</span>
644 &gt;&gt;&gt; <span class="input">db.status.history(2)</span>
645 <span class="output"
646 >[(&lt;Date 2000-06-28.19:11:04&gt;, "ping", "link", ("issue", 5, "status"))]</span>
647 </small></pre></blockquote>
648
649 <p>For the purposes of journalling, when a Multilink property is
650 set to a new list of nodes, the hyperdatabase compares the old
651 list to the new list.
652 The journal records "unlink" events for all the nodes that appear
653 in the old list but not the new list,
654 and "link" events for
655 all the nodes that appear in the new list but not in the old list.
656
657 <p><hr>
658 <h2>4. Roundup Database</h2>
659
660 <p>The Roundup database layer is implemented on top of the
661 hyperdatabase and mediates calls to the database.
662 Some of the classes in the Roundup database are considered
663 <em>item classes</em>.
664 The Roundup database layer adds detectors and user nodes,
665 and on items it provides mail spools, nosy lists, and superseders.
666
667 <h3>4.1. Reserved Classes</h3>
668
669 <p>Internal to this layer we reserve three special classes
670 of nodes that are not items.
671
672 <h4>4.1.1. Users</h4>
673
674 <p>Users are stored in the hyperdatabase as nodes of
675 class "user". The "user" class has the definition:
676
677 <blockquote><pre><small
678 >hyperdb.Class(db, "user", username=hyperdb.String(),
679 password=hyperdb.String(),
680 address=hyperdb.String())
681 db.user.setkey("username")</small></pre></blockquote>
682
683 <h4>4.1.2. Messages</h4>
684
685 <p>E-mail messages are represented by hyperdatabase nodes of class "msg".
686 The actual text content of the messages is stored in separate files.
687 (There's no advantage to be gained by stuffing them into the
688 hyperdatabase, and if messages are stored in ordinary text files,
689 they can be grepped from the command line.) The text of a message is
690 saved in a file named after the message node designator (e.g. "msg23")
691 for the sake of the command interface (see below). Attachments are
692 stored separately and associated with "file" nodes.
693 The "msg" class has the definition:
694
695 <blockquote><pre><small
696 >hyperdb.Class(db, "msg", author=hyperdb.Link("user"),
697 recipients=hyperdb.Multilink("user"),
698 date=hyperdb.Date(),
699 summary=hyperdb.String(),
700 files=hyperdb.Multilink("file"))</small
701 ></pre></blockquote>
702
703 <p>The "author" property indicates the author of the message
704 (a "user" node must exist in the hyperdatabase for any messages
705 that are stored in the system).
706 The "summary" property contains a summary of the message for display
707 in a message index.
708
709 <h4>4.1.3. Files</h4>
710
711 <p>Submitted files are represented by hyperdatabase
712 nodes of class "file". Like e-mail messages, the file content
713 is stored in files outside the database,
714 named after the file node designator (e.g. "file17").
715 The "file" class has the definition:
716
717 <blockquote><pre><small
718 >hyperdb.Class(db, "file", user=hyperdb.Link("user"),
719 name=hyperdb.String(),
720 type=hyperdb.String())</small></pre></blockquote>
721
722 <p>The "user" property indicates the user who submitted the
723 file, the "name" property holds the original name of the file,
724 and the "type" property holds the MIME type of the file as received.
725
726 <h3>4.2. Item Classes</h3>
727
728 <p>All items have the following standard properties:
729
730 <blockquote><pre><small
731 >title=hyperdb.String()
732 messages=hyperdb.Multilink("msg")
733 files=hyperdb.Multilink("file")
734 nosy=hyperdb.Multilink("user")
735 superseder=hyperdb.Multilink("item")</small></pre></blockquote>
736
737 <p>Also, two Date properties named "creation" and "activity" are
738 fabricated by the Roundup database layer. By "fabricated" we
739 mean that no such properties are actually stored in the
740 hyperdatabase, but when properties on items are requested, the
741 "creation" and "activity" properties are made available.
742 The value of the "creation" property is the date when an item was
743 created, and the value of the "activity" property is the
744 date when any property on the item was last edited (equivalently,
745 these are the dates on the first and last records in the item's journal).
746
747 <h3>4.3. Interface Specification</h3>
748
749 <p>The interface to a Roundup database delegates most method
750 calls to the hyperdatabase, except for the following
751 changes and additional methods.
752
753 <blockquote><pre><small
754 >class <strong>Database</strong>:
755 # Overridden methods:
756
757 def <strong>__init__</strong>(self, storagelocator, journaltag):
758 """When the Roundup database is opened by a particular user,
759 the 'journaltag' is the id of the user's "user" node."""
760
761 def <strong>getclass</strong>(self, classname):
762 """This method now returns an instance of either Class or
763 ItemClass depending on whether an item class is specified."""
764
765 # New methods:
766
767 def <strong>getuid</strong>(self):
768 """Return the id of the "user" node associated with the user
769 that owns this connection to the hyperdatabase."""
770
771 class <strong>Class</strong>:
772 # Overridden methods:
773
774 def <strong>create</strong>(self, **propvalues):
775 def <strong>set</strong>(self, **propvalues):
776 def <strong>retire</strong>(self, nodeid):
777 """These operations trigger detectors and can be vetoed. Attempts
778 to modify the "creation" or "activity" properties cause a KeyError.
779 """
780
781 # New methods:
782
783 def <strong>audit</strong>(self, event, detector):
784 def <strong>react</strong>(self, event, detector):
785 """Register a detector (see below for more details)."""
786
787 class <strong>ItemClass</strong>(Class):
788 # Overridden methods:
789
790 def <strong>__init__</strong>(self, db, classname, **properties):
791 """The newly-created class automatically includes the "messages",
792 "files", "nosy", and "superseder" properties. If the 'properties'
793 dictionary attempts to specify any of these properties or a
794 "creation" or "activity" property, a ValueError is raised."""
795
796 def <strong>get</strong>(self, nodeid, propname):
797 def <strong>getprops</strong>(self):
798 """In addition to the actual properties on the node, these
799 methods provide the "creation" and "activity" properties."""
800
801 # New methods:
802
803 def <strong>addmessage</strong>(self, nodeid, summary, text):
804 """Add a message to an item's mail spool.
805
806 A new "msg" node is constructed using the current date, the
807 user that owns the database connection as the author, and
808 the specified summary text. The "files" and "recipients"
809 fields are left empty. The given text is saved as the body
810 of the message and the node is appended to the "messages"
811 field of the specified item.
812 """
813
814 def <strong>sendmessage</strong>(self, nodeid, msgid):
815 """Send a message to the members of an item's nosy list.
816
817 The message is sent only to users on the nosy list who are not
818 already on the "recipients" list for the message. These users
819 are then added to the message's "recipients" list.
820 """
821 </small></pre></blockquote>
822
823 <h3>4.4. Default Schema</h3>
824
825 <p>The default schema included with Roundup turns it into a
826 typical software bug tracker. The database is set up like this:
827
828 <blockquote><pre><small
829 >pri = Class(db, "priority", name=hyperdb.String(), order=hyperdb.String())
830 pri.setkey("name")
831 pri.create(name="critical", order="1")
832 pri.create(name="urgent", order="2")
833 pri.create(name="bug", order="3")
834 pri.create(name="feature", order="4")
835 pri.create(name="wish", order="5")
836
837 stat = Class(db, "status", name=hyperdb.String(), order=hyperdb.String())
838 stat.setkey("name")
839 stat.create(name="unread", order="1")
840 stat.create(name="deferred", order="2")
841 stat.create(name="chatting", order="3")
842 stat.create(name="need-eg", order="4")
843 stat.create(name="in-progress", order="5")
844 stat.create(name="testing", order="6")
845 stat.create(name="done-cbb", order="7")
846 stat.create(name="resolved", order="8")
847
848 Class(db, "keyword", name=hyperdb.String())
849
850 Class(db, "issue", fixer=hyperdb.Multilink("user"),
851 topic=hyperdb.Multilink("keyword"),
852 priority=hyperdb.Link("priority"),
853 status=hyperdb.Link("status"))
854 </small></pre></blockquote>
855
856 <p>(The "order" property hasn't been explained yet. It
857 gets used by the Web user interface for sorting.)
858
859 <p>The above isn't as pretty-looking as the schema specification
860 in the first-stage submission, but it could be made just as easy
861 with the addition of a convenience function like <tt>Choice</tt>
862 for setting up the "priority" and "status" classes:
863
864 <blockquote><pre><small
865 >def Choice(name, *options):
866 cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String())
867 for i in range(len(options)):
868 cl.create(name=option[i], order=i)
869 return hyperdb.Link(name)
870 </small></pre></blockquote>
871
872 <p><hr>
873 <h2>5. Detector Interface</h2>
874
875 <p>Detectors are Python functions that are triggered on certain
876 kinds of events. The definitions of the
877 functions live in Python modules placed in a directory set aside
878 for this purpose. Importing the Roundup database module also
879 imports all the modules in this directory, and the <tt>init()</tt>
880 function of each module is called when a database is opened to
881 provide it a chance to register its detectors.
882
883 <p>There are two kinds of detectors:
884
885 <ul>
886 <li>an <em>auditor</em> is triggered just before modifying an node
887 <li>a <em>reactor</em> is triggered just after an node has been modified
888 </ul>
889
890 <p>When the Roundup database is about to perform a
891 <tt>create()</tt>, <tt>set()</tt>, or <tt>retire()</tt>
892 operation, it first calls any auditors that
893 have been registered for that operation on that class.
894 Any auditor may raise a <tt>Reject</tt> exception
895 to abort the operation.
896
897 <p>If none of the auditors raises an exception, the database
898 proceeds to carry out the operation. After it's done, it
899 then calls all of the reactors that have been registered
900 for the operation.
901
902 <h3>5.1. Interface Specification</h3>
903
904 <p>The <tt>audit()</tt> and <tt>react()</tt> methods
905 register detectors on a given class of nodes.
906
907 <blockquote><pre><small
908 >class Class:
909 def <strong>audit</strong>(self, event, detector):
910 """Register an auditor on this class.
911
912 'event' should be one of "create", "set", or "retire".
913 'detector' should be a function accepting four arguments.
914 """
915
916 def <strong>react</strong>(self, event, detector):
917 """Register a reactor on this class.
918
919 'event' should be one of "create", "set", or "retire".
920 'detector' should be a function accepting four arguments.
921 """
922 </small></pre></blockquote>
923
924 <p>Auditors are called with the arguments:
925
926 <blockquote><pre><small
927 >audit(db, cl, nodeid, newdata)</small></pre></blockquote>
928
929 where <tt>db</tt> is the database, <tt>cl</tt> is an
930 instance of Class or ItemClass within the database, and <tt>newdata</tt>
931 is a dictionary mapping property names to values.
932
933 For a <tt>create()</tt>
934 operation, the <tt>nodeid</tt> argument is <tt>None</tt> and <tt>newdata</tt>
935 contains all of the initial property values with which the node
936 is about to be created.
937
938 For a <tt>set()</tt> operation, <tt>newdata</tt>
939 contains only the names and values of properties that are about
940 to be changed.
941
942 For a <tt>retire()</tt> operation, <tt>newdata</tt> is <tt>None</tt>.
943
944 <p>Reactors are called with the arguments:
945
946 <blockquote><pre><small
947 >react(db, cl, nodeid, olddata)</small></pre></blockquote>
948
949 where <tt>db</tt> is the database, <tt>cl</tt> is an
950 instance of Class or ItemClass within the database, and <tt>olddata</tt>
951 is a dictionary mapping property names to values.
952
953 For a <tt>create()</tt>
954 operation, the <tt>nodeid</tt> argument is the id of the
955 newly-created node and <tt>olddata</tt> is None.
956
957 For a <tt>set()</tt> operation, <tt>olddata</tt>
958 contains the names and previous values of properties that were changed.
959
960 For a <tt>retire()</tt> operation, <tt>nodeid</tt> is the
961 id of the retired node and <tt>olddata</tt> is <tt>None</tt>.
962
963 <h3>5.2. Detector Example</h3>
964
965 <p>Here is an example of detectors written for a hypothetical
966 project-management application, where users can signal approval
967 of a project by adding themselves to an "approvals" list, and
968 a project proceeds when it has three approvals.
969
970 <blockquote><pre><small
971 ># Permit users only to add themselves to the "approvals" list.
972
973 def check_approvals(db, cl, id, newdata):
974 if newdata.has_key("approvals"):
975 if cl.get(id, "status") == db.status.lookup("approved"):
976 raise Reject, "You can't modify the approvals list " \
977 "for a project that has already been approved."
978 old = cl.get(id, "approvals")
979 new = newdata["approvals"]
980 for uid in old:
981 if uid not in new and uid != db.getuid():
982 raise Reject, "You can't remove other users from the "
983 "approvals list; you can only remove yourself."
984 for uid in new:
985 if uid not in old and uid != db.getuid():
986 raise Reject, "You can't add other users to the approvals "
987 "list; you can only add yourself."
988
989 # When three people have approved a project, change its
990 # status from "pending" to "approved".
991
992 def approve_project(db, cl, id, olddata):
993 if olddata.has_key("approvals") and len(cl.get(id, "approvals")) == 3:
994 if cl.get(id, "status") == db.status.lookup("pending"):
995 cl.set(id, status=db.status.lookup("approved"))
996
997 def init(db):
998 db.project.audit("set", check_approval)
999 db.project.react("set", approve_project)</small
1000 ></pre></blockquote>
1001
1002 <p>Here is another example of a detector that can allow or prevent
1003 the creation of new nodes. In this scenario, patches for a software
1004 project are submitted by sending in e-mail with an attached file,
1005 and we want to ensure that there are <tt>text/plain</tt> attachments on
1006 the message. The maintainer of the package can then apply the
1007 patch by setting its status to "applied".
1008
1009 <blockquote><pre><small
1010 ># Only accept attempts to create new patches that come with patch files.
1011
1012 def check_new_patch(db, cl, id, newdata):
1013 if not newdata["files"]:
1014 raise Reject, "You can't submit a new patch without " \
1015 "attaching a patch file."
1016 for fileid in newdata["files"]:
1017 if db.file.get(fileid, "type") != "text/plain":
1018 raise Reject, "Submitted patch files must be text/plain."
1019
1020 # When the status is changed from "approved" to "applied", apply the patch.
1021
1022 def apply_patch(db, cl, id, olddata):
1023 if cl.get(id, "status") == db.status.lookup("applied") and \
1024 olddata["status"] == db.status.lookup("approved"):
1025 # ...apply the patch...
1026
1027 def init(db):
1028 db.patch.audit("create", check_new_patch)
1029 db.patch.react("set", apply_patch)</small
1030 ></pre></blockquote>
1031
1032 <p><hr>
1033 <h2>6. Command Interface</h2>
1034
1035 <p>The command interface is a very simple and minimal interface,
1036 intended only for quick searches and checks from the shell prompt.
1037 (Anything more interesting can simply be written in Python using
1038 the Roundup database module.)
1039
1040 <h3>6.1. Interface Specification</h3>
1041
1042 <p>A single command, <tt>roundup</tt>, provides basic access to
1043 the hyperdatabase from the command line.
1044
1045 <ul>
1046 <li><tt>roundup&nbsp;get&nbsp;</tt>[<tt>-list</tt>]<tt>&nbsp;</tt
1047 ><em>designator</em>[<tt>,</tt
1048 ><em>designator</em><tt>,</tt>...]<tt>&nbsp;</tt><em>propname</em>
1049 <li><tt>roundup&nbsp;set&nbsp;</tt><em>designator</em>[<tt>,</tt
1050 ><em>designator</em><tt>,</tt>...]<tt>&nbsp;</tt><em>propname</em
1051 ><tt>=</tt><em>value</em> ...
1052 <li><tt>roundup&nbsp;find&nbsp;</tt>[<tt>-list</tt>]<tt>&nbsp;</tt
1053 ><em>classname</em><tt>&nbsp;</tt><em>propname</em>=<em>value</em> ...
1054 </ul>
1055
1056 <p>Property values are represented as strings in command arguments
1057 and in the printed results:
1058
1059 <ul>
1060 <li>Strings are, well, strings.
1061
1062 <li>Date values are printed in the full date format in the local
1063 time zone, and accepted in the full format or any of the partial
1064 formats explained above.
1065
1066 <li>Link values are printed as node designators. When given as
1067 an argument, node designators and key strings are both accepted.
1068
1069 <li>Multilink values are printed as lists of node designators
1070 joined by commas. When given as an argument, node designators
1071 and key strings are both accepted; an empty string, a single node,
1072 or a list of nodes joined by commas is accepted.
1073 </ul>
1074
1075 <p>When multiple nodes are specified to the
1076 <tt>roundup&nbsp;get</tt> or <tt>roundup&nbsp;set</tt>
1077 commands, the specified properties are retrieved or set
1078 on all the listed nodes.
1079
1080 <p>When multiple results are returned by the <tt>roundup&nbsp;get</tt>
1081 or <tt>roundup&nbsp;find</tt> commands, they are printed one per
1082 line (default) or joined by commas (with the <tt>-list</tt>) option.
1083
1084 <h3>6.2. Usage Example</h3>
1085
1086 <p>To find all messages regarding in-progress issues that
1087 contain the word "spam", for example, you could execute the
1088 following command from the directory where the database
1089 dumps its files:
1090
1091 <blockquote><pre><small
1092 >shell% <span class="input">for issue in `roundup find issue status=in-progress`; do</span>
1093 &gt; <span class="input">grep -l spam `roundup get $issue messages`</span>
1094 &gt; <span class="input">done</span>
1095 <span class="output">msg23
1096 msg49
1097 msg50
1098 msg61</span>
1099 shell%</small></pre></blockquote>
1100
1101 <p>Or, using the <tt>-list</tt> option, this can be written as a single command:
1102
1103 <blockquote><pre><small
1104 >shell% <span class="input">grep -l spam `roundup get \
1105 \`roundup find -list issue status=in-progress\` messages`</span>
1106 <span class="output">msg23
1107 msg49
1108 msg50
1109 msg61</span>
1110 shell%</small></pre></blockquote>
1111
1112 <p><hr>
1113 <h2>7. E-mail User Interface</h2>
1114
1115 <p>The Roundup system must be assigned an e-mail address
1116 at which to receive mail. Messages should be piped to
1117 the Roundup mail-handling script by the mail delivery
1118 system (e.g. using an alias beginning with "|" for sendmail).
1119
1120 <h3>7.1. Message Processing</h3>
1121
1122 <p>Incoming messages are examined for multiple parts.
1123 In a <tt>multipart/mixed</tt> message or part, each subpart is
1124 extracted and examined. In a <tt>multipart/alternative</tt>
1125 message or part, we look for a <tt>text/plain</tt> subpart and
1126 ignore the other parts. The <tt>text/plain</tt> subparts are
1127 assembled to form the textual body of the message, to
1128 be stored in the file associated with a "msg" class node.
1129 Any parts of other types are each stored in separate
1130 files and given "file" class nodes that are linked to
1131 the "msg" node.
1132
1133 <p>The "summary" property on message nodes is taken from
1134 the first non-quoting section in the message body.
1135 The message body is divided into sections by blank lines.
1136 Sections where the second and all subsequent lines begin
1137 with a "&gt;" or "|" character are considered "quoting
1138 sections". The first line of the first non-quoting
1139 section becomes the summary of the message.
1140
1141 <p>All of the addresses in the To: and Cc: headers of the
1142 incoming message are looked up among the user nodes, and
1143 the corresponding users are placed in the "recipients"
1144 property on the new "msg" node. The address in the From:
1145 header similarly determines the "author" property of the
1146 new "msg" node.
1147 The default handling for
1148 addresses that don't have corresponding users is to create
1149 new users with no passwords and a username equal to the
1150 address. (The web interface does not permit logins for
1151 users with no passwords.) If we prefer to reject mail from
1152 outside sources, we can simply register an auditor on the
1153 "user" class that prevents the creation of user nodes with
1154 no passwords.
1155
1156 <p>The subject line of the incoming message is examined to
1157 determine whether the message is an attempt to create a new
1158 item or to discuss an existing item. A designator enclosed
1159 in square brackets is sought as the first thing on the
1160 subject line (after skipping any "Fwd:" or "Re:" prefixes).
1161
1162 <p>If an item designator (class name and id number) is found
1163 there, the newly created "msg" node is added to the "messages"
1164 property for that item, and any new "file" nodes are added to
1165 the "files" property for the item.
1166
1167 <p>If just an item class name is found there, we attempt to
1168 create a new item of that class with its "messages" property
1169 initialized to contain the new "msg" node and its "files"
1170 property initialized to contain any new "file" nodes.
1171
1172 <p>Both cases may trigger detectors (in the first case we
1173 are calling the <tt>set()</tt> method to add the message to the
1174 item's spool; in the second case we are calling the
1175 <tt>create()</tt> method to create a new node). If an auditor
1176 raises an exception, the original message is bounced back to
1177 the sender with the explanatory message given in the exception.
1178
1179 <h3>7.2. Nosy Lists</h3>
1180
1181 <p>A standard detector is provided that watches for additions
1182 to the "messages" property. When a new message is added, the
1183 detector sends it to all the users on the "nosy" list for the
1184 item that are not already on the "recipients" list of the
1185 message. Those users are then appended to the "recipients"
1186 property on the message, so multiple copies of a message
1187 are never sent to the same user. The journal recorded by
1188 the hyperdatabase on the "recipients" property then provides
1189 a log of when the message was sent to whom.
1190
1191 <h3>7.3. Setting Properties</h3>
1192
1193 <p>The e-mail interface also provides a simple way to set
1194 properties on items. At the end of the subject line,
1195 <em>propname</em><tt>=</tt><em>value</em> pairs can be
1196 specified in square brackets, using the same conventions
1197 as for the <tt>roundup&nbsp;set</tt> shell command.
1198
1199 <p><hr>
1200 <h2>8. Web User Interface</h2>
1201
1202 <p>The web interface is provided by a CGI script that can be
1203 run under any web server. A simple web server can easily be
1204 built on the standard <tt>CGIHTTPServer</tt> module, and
1205 should also be included in the distribution for quick
1206 out-of-the-box deployment.
1207
1208 <p>The user interface is constructed from a number of template
1209 files containing mostly HTML. Among the HTML tags in templates
1210 are interspersed some nonstandard tags, which we use as
1211 placeholders to be replaced by properties and their values.
1212
1213 <h3>8.1. Views and View Specifiers</h3>
1214
1215 <p>There are two main kinds of views: index views and item views.
1216 An index view displays a list of items of a particular class,
1217 optionally sorted and filtered as requested. An item view
1218 presents the properties of a particular item for editing
1219 and displays the message spool for the item.
1220
1221 <p>A <em>view specifier</em> is a string that specifies
1222 all the options needed to construct a particular view.
1223 It goes after the URL to the Roundup CGI script or the
1224 web server to form the complete URL to a view. When the
1225 result of selecting a link or submitting a form takes
1226 the user to a new view, the Web browser should be redirected
1227 to a canonical location containing a complete view specifier
1228 so that the view can be bookmarked.
1229
1230 <h3>8.2. Displaying Properties</h3>
1231
1232 <p>Properties appear in the user interface in three contexts:
1233 in indices, in editors, and as filters. For each type of
1234 property, there are several display possibilities. For example,
1235 in an index view, a string property may just be printed as
1236 a plain string, but in an editor view, that property should
1237 be displayed in an editable field.
1238
1239 <p>The display of a property is handled by functions in
1240 a <tt>displayers</tt> module. Each function accepts at
1241 least three standard arguments -- the database, class name,
1242 and node id -- and returns a chunk of HTML.
1243
1244 <p>Displayer functions are triggered by <tt>&lt;display&gt;</tt>
1245 tags in templates. The <tt>call</tt> attribute of the tag
1246 provides a Python expression for calling the displayer
1247 function. The three standard arguments are inserted in
1248 front of the arguments given. For example, the occurrence of
1249
1250 <blockquote><pre><small
1251 > &lt;display call="plain('status', max=30)"&gt;
1252 </small></pre></blockquote>
1253
1254 in a template triggers a call to
1255
1256 <blockquote><pre><small
1257 > plain(db, "issue", 13, "status", max=30)
1258 </small></pre></blockquote>
1259
1260 when displaying item 13 in the "issue" class. The displayer
1261 functions can accept extra arguments to further specify
1262 details about the widgets that should be generated. By defining new
1263 displayer functions, the user interface can be highly customized.
1264
1265 <p>Some of the standard displayer functions include:
1266
1267 <ul>
1268 <li><strong>plain</strong>: display a String property directly;
1269 display a Date property in a specified time zone with an option
1270 to omit the time from the date stamp; for a Link or Multilink
1271 property, display the key strings of the linked nodes (or the
1272 ids if the linked class has no key property)
1273
1274 <li><strong>field</strong>: display a property like the
1275 <strong>plain</strong> displayer above, but in a text field
1276 to be edited
1277
1278 <li><strong>menu</strong>: for a Link property, display
1279 a menu of the available choices
1280
1281 <li><strong>link</strong>: for a Link or Multilink property,
1282 display the names of the linked nodes, hyperlinked to the
1283 item views on those nodes
1284
1285 <li><strong>count</strong>: for a Multilink property, display
1286 a count of the number of links in the list
1287
1288 <li><strong>reldate</strong>: display a Date property in terms
1289 of an interval relative to the current date (e.g. "+ 3w", "- 2d").
1290
1291 <li><strong>download</strong>: show a Link("file") or Multilink("file")
1292 property using links that allow you to download files
1293
1294 <li><strong>checklist</strong>: for a Link or Multilink property,
1295 display checkboxes for the available choices to permit filtering
1296 </ul>
1297
1298 <h3>8.3. Index Views</h3>
1299
1300 <p>An index view contains two sections: a filter section
1301 and an index section.
1302 The filter section provides some widgets for selecting
1303 which items appear in the index. The index section is
1304 a table of items.
1305
1306 <h4>8.3.1. Index View Specifiers</h4>
1307
1308 <p>An index view specifier looks like this (whitespace
1309 has been added for clarity):
1310
1311 <blockquote><pre><small
1312 >/issue?status=unread,in-progress,resolved&amp;
1313 topic=security,ui&amp;
1314 :group=+priority&amp;
1315 :sort=-activity&amp;
1316 :filters=status,topic&amp;
1317 :columns=title,status,fixer
1318 </small></pre></blockquote>
1319
1320 <p>The index view is determined by two parts of the
1321 specifier: the layout part and the filter part.
1322 The layout part consists of the query parameters that
1323 begin with colons, and it determines the way that the
1324 properties of selected nodes are displayed.
1325 The filter part consists of all the other query parameters,
1326 and it determines the criteria by which nodes
1327 are selected for display.
1328
1329 <p>The filter part is interactively manipulated with
1330 the form widgets displayed in the filter section. The
1331 layout part is interactively manipulated by clicking
1332 on the column headings in the table.
1333
1334 <p>The filter part selects the <em>union</em> of the
1335 sets of items with values matching any specified Link
1336 properties and the <em>intersection</em> of the sets
1337 of items with values matching any specified Multilink
1338 properties.
1339
1340 <p>The example specifies an index of "issue" nodes.
1341 Only items with a "status" of <em>either</em>
1342 "unread" or "in-progres" or "resolved" are displayed,
1343 and only items with "topic" values including <em>both</em>
1344 "security" <em>and</em> "ui" are displayed. The items
1345 are grouped by priority, arranged in ascending order;
1346 and within groups, sorted by activity, arranged in
1347 descending order. The filter section shows filters
1348 for the "status" and "topic" properties, and the
1349 table includes columns for the "title", "status", and
1350 "fixer" properties.
1351
1352 <p>Associated with each item class is a default
1353 layout specifier. The layout specifier in the above
1354 example is the default layout to be provided with
1355 the default bug-tracker schema described above in
1356 section 4.4.
1357
1358 <h4>8.3.2. Filter Section</h4>
1359
1360 <p>The template for a filter section provides the
1361 filtering widgets at the top of the index view.
1362 Fragments enclosed in <tt>&lt;property&gt;</tt>...<tt>&lt;/property&gt;</tt>
1363 tags are included or omitted depending on whether the
1364 view specifier requests a filter for a particular property.
1365
1366 <p>Here's a simple example of a filter template.
1367
1368 <blockquote><pre><small
1369 >&lt;property name=status&gt;
1370 &lt;display call="checklist('status')"&gt;
1371 &lt;/property&gt;
1372 &lt;br&gt;
1373 &lt;property name=priority&gt;
1374 &lt;display call="checklist('priority')"&gt;
1375 &lt;/property&gt;
1376 &lt;br&gt;
1377 &lt;property name=fixer&gt;
1378 &lt;display call="menu('fixer')"&gt;
1379 &lt;/property&gt;</small></pre></blockquote>
1380
1381 <h4>8.3.3. Index Section</h4>
1382
1383 <p>The template for an index section describes one row of
1384 the index table.
1385 Fragments enclosed in <tt>&lt;property&gt;</tt>...<tt>&lt;/property&gt;</tt>
1386 tags are included or omitted depending on whether the
1387 view specifier requests a column for a particular property.
1388 The table cells should contain <tt>&lt;display&gt;</tt> tags
1389 to display the values of the item's properties.
1390
1391 <p>Here's a simple example of an index template.
1392
1393 <blockquote><pre><small
1394 >&lt;tr&gt;
1395 &lt;property name=title&gt;
1396 &lt;td&gt;&lt;display call="plain('title', max=50)"&gt;&lt;/td&gt;
1397 &lt;/property&gt;
1398 &lt;property name=status&gt;
1399 &lt;td&gt;&lt;display call="plain('status')"&gt;&lt;/td&gt;
1400 &lt;/property&gt;
1401 &lt;property name=fixer&gt;
1402 &lt;td&gt;&lt;display call="plain('fixer')"&gt;&lt;/td&gt;
1403 &lt;/property&gt;
1404 &lt;/tr&gt;</small></pre></blockquote>
1405
1406 <h4>8.3.4. Sorting</h4>
1407
1408 <p>String and Date values are sorted in the natural way.
1409 Link properties are sorted according to the value of the
1410 "order" property on the linked nodes if it is present; or
1411 otherwise on the key string of the linked nodes; or
1412 finally on the node ids. Multilink properties are
1413 sorted according to how many links are present.
1414
1415 <h3>8.4. Item Views</h3>
1416
1417 <p>An item view contains an editor section and a spool section.
1418 At the top of an item view, links to superseding and superseded
1419 items are always displayed.
1420
1421 <h4>8.4.1. Item View Specifiers</h4>
1422
1423 <p>An item view specifier is simply the item's designator:
1424
1425 <blockquote><pre><small
1426 >/patch23
1427 </small></pre></blockquote>
1428
1429 <h4>8.4.2. Editor Section</h4>
1430
1431 <p>The editor section is generated from a template
1432 containing <tt>&lt;display&gt;</tt> tags to insert
1433 the appropriate widgets for editing properties.
1434
1435 <p>Here's an example of a basic editor template.
1436
1437 <blockquote><pre><small
1438 >&lt;table&gt;
1439 &lt;tr&gt;
1440 &lt;td colspan=2&gt;
1441 &lt;display call="field('title', size=60)"&gt;
1442 &lt;/td&gt;
1443 &lt;/tr&gt;
1444 &lt;tr&gt;
1445 &lt;td&gt;
1446 &lt;display call="field('fixer', size=30)"&gt;
1447 &lt;/td&gt;
1448 &lt;td&gt;
1449 &lt;display call="menu('status')&gt;
1450 &lt;/td&gt;
1451 &lt;/tr&gt;
1452 &lt;tr&gt;
1453 &lt;td&gt;
1454 &lt;display call="field('nosy', size=30)"&gt;
1455 &lt;/td&gt;
1456 &lt;td&gt;
1457 &lt;display call="menu('priority')&gt;
1458 &lt;/td&gt;
1459 &lt;/tr&gt;
1460 &lt;tr&gt;
1461 &lt;td colspan=2&gt;
1462 &lt;display call="note()"&gt;
1463 &lt;/td&gt;
1464 &lt;/tr&gt;
1465 &lt;/table&gt;
1466 </small></pre></blockquote>
1467
1468 <p>As shown in the example, the editor template can also
1469 request the display of a "note" field, which is a
1470 text area for entering a note to go along with a change.
1471
1472 <p>When a change is submitted, the system automatically
1473 generates a message describing the changed properties.
1474 The message displays all of the property values on the
1475 item and indicates which ones have changed.
1476 An example of such a message might be this:
1477
1478 <blockquote><pre><small
1479 >title: Polly Parrot is dead
1480 priority: critical
1481 status: unread -&gt; in-progress
1482 fixer: (none)
1483 keywords: parrot,plumage,perch,nailed,dead
1484 </small></pre></blockquote>
1485
1486 <p>If a note is given in the "note" field, the note is
1487 appended to the description. The message is then added
1488 to the item's message spool (thus triggering the standard
1489 detector to react by sending out this message to the nosy list).
1490
1491 <h4>8.4.3. Spool Section</h4>
1492
1493 <p>The spool section lists messages in the item's "messages"
1494 property. The index of messages displays the "date", "author",
1495 and "summary" properties on the message nodes, and selecting a
1496 message takes you to its content.
1497
1498 <p><hr>
1499 <h2>9. Deployment Scenarios</h2>
1500
1501 <p>The design described above should be general enough
1502 to permit the use of Roundup for bug tracking, managing
1503 projects, managing patches, or holding discussions. By
1504 using nodes of multiple types, one could deploy a system
1505 that maintains requirement specifications, catalogs bugs,
1506 and manages submitted patches, where patches could be
1507 linked to the bugs and requirements they address.
1508
1509 <p><hr>
1510 <h2>10. Acknowledgements</h2>
1511
1512 <p>My thanks are due to Christy Heyl for
1513 reviewing and contributing suggestions to this paper
1514 and motivating me to get it done, and to
1515 Jesse Vincent, Mark Miller, Christopher Simons,
1516 Jeff Dunmall, Wayne Gramlich, and Dean Tribble for
1517 their assistance with the first-round submission.
1518 </td>
1519 </tr>
1520 </table>
1521
1522 <p>
1523
1524 <center>
1525 <table>
1526 <tr>
1527 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/index.html"><b>[Home]</b></a>&nbsp;&nbsp;&nbsp;</td>
1528 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/faq.html"><b>[FAQ]</b></a>&nbsp;&nbsp;&nbsp;</td>
1529 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/license.html"><b>[License]</b></a>&nbsp;&nbsp;&nbsp;</td>
1530 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/contest-rules.html"><b>[Rules]</b></a>&nbsp;&nbsp;&nbsp;</td>
1531 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/sc_config/"><b>[Configure]</b></a>&nbsp;&nbsp;&nbsp;</td>
1532 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/sc_build/"><b>[Build]</b></a>&nbsp;&nbsp;&nbsp;</td>
1533 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/sc_test/"><b>[Test]</b></a>&nbsp;&nbsp;&nbsp;</td>
1534 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/sc_track/"><b>[Track]</b></a>&nbsp;&nbsp;&nbsp;</td>
1535 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/biblio.html"><b>[Resources]</b></a>&nbsp;&nbsp;&nbsp;</td>
1536 <td>&nbsp;&nbsp;&nbsp;<a href="http://www.software-carpentry.com/lists/"><b>[Archives]</b></a>&nbsp;&nbsp;&nbsp;</td>
1537 </tr>
1538 </table>
1539 </center>
1540
1541 <p><hr>
1542 <center>Last modified 2001/04/06 11:50:59.9063 US/Mountain</center>
1543 </BODY>
1544 </HTML>

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