Code

e39d857861003805dab02f87bb95e7170a8229aa
[roundup.git] / doc / spec.html
1 <html>
2 <head>
3 <title>Software Carpentry Track: Roundup</title>
4 </head>
5 <body bgcolor=white>
7 <table width="100%">
8 <tr>
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>
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>
26 </tr>
27 </table>
29 <hr><p>
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>
37 <h2>Contents</h2>
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>
102 <p><hr>
103 <h2>1. Introduction</h2>
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.
113 <p><hr>
114 <h2>2. The Layer Cake</h2>
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.
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>
184 <p>The colourful parts of the cake are part of our system;
185 the faint grey parts of the cake are external components.
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.
191 <p><hr>
192 <h2>3. Hyperdatabase</h2>
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>.
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.
205 <h3>3.1. Dates and Date Arithmetic</h3>
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.
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.
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.
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>
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).
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>
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).
272 <p>Here is an outline of the Date and Interval classes.
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.
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         """
284     def <strong>__add__</strong>(self, interval):
285         """Add an interval to this date to produce another date."""
287     def <strong>__sub__</strong>(self, interval):
288         """Subtract an interval from this date to produce another date."""
290     def <strong>__cmp__</strong>(self, other):
291         """Compare this date to another date."""
293     def <strong>__str__</strong>(self):
294         """Return this date as a string in the yyyy-mm-dd.hh:mm:ss format."""
296     def <strong>local</strong>(self, offset):
297         """Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone."""
299 class <strong>Interval</strong>:
300     def <strong>__init__</strong>(self, spec):
301         """Construct an interval given a specification."""
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>
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.
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>
336 <h3>3.2. Nodes and Classes</h3>
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.
344 <h3>3.3. Identifiers and Designators</h3>
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.
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.
363 <h3>3.4. Property Names and Types</h3>
365 <p>Property names must begin with a letter.
367 <p>A property may be one of five <em>basic types</em>:
369 <ul>
370 <li><em>String</em> properties are for storing arbitrary-length
371 strings.
373 <li><em>Date</em> properties store date-and-time stamps.
374 Their values are Timestamp objects.
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.
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>
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.
389 <h3>3.5. Interface Specification</h3>
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.
395 <blockquote><pre><small
396 >class <strong>String</strong>:
397     def <strong>__init__</strong>(self):
398         """An object designating a String property."""
400 class <strong>Date</strong>:
401     def <strong>__init__</strong>(self):
402         """An object designating a Date property."""
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."""
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>
415 <p>Here is the interface provided by the hyperdatabase.
417 <blockquote><pre><small
418 >class <strong>Database</strong>:
419     """A database for storing records containing flexible data types."""
421     def <strong>__init__</strong>(self, storagelocator, journaltag):
422         """Open a hyperdatabase given a specifier to some storage.
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.
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         """
435     def <strong>__getattr__</strong>(self, classname):
436         """A convenient way of calling self.getclass(classname)."""
438     def <strong>getclasses</strong>(self):
439         """Return a list of the names of all existing classes."""
441     def <strong>getclass</strong>(self, classname):
442         """Get the Class object representing a particular class.
444         If 'classname' is not a valid class name, a KeyError is raised.
445         """
447 class <strong>Class</strong>:
448     """The handle to a particular class of nodes in a hyperdatabase."""
450     def <strong>__init__</strong>(self, db, classname, **properties):
451         """Create a new class with a given name and property specification.
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         """
458     # Editing nodes:
460     def <strong>create</strong>(self, **propvalues):
461         """Create a new node of this class and return its id.
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         """
473     def <strong>get</strong>(self, nodeid, propname):
474         """Get the value of a property on an existing node of this class.
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         """
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         """
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         """
503     def <strong>history</strong>(self, nodeid):
504         """Retrieve the journal of edits on a particular node.
506         'nodeid' must be the id of an existing node of this class or an
507         IndexError is raised.
509         The returned list contains tuples of the form
511             (date, tag, action, params)
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:
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         """
522     # Locating nodes:
524     def <strong>setkey</strong>(self, propname):
525         """Select a String property of this class to be the key property.
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         """
532     def <strong>getkey</strong>(self):
533         """Return the name of the key property for this class or None."""
535     def <strong>lookup</strong>(self, keyvalue):
536         """Locate a particular node by its key property and return its id.
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         """
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         """
554     def <strong>list</strong>(self):
555         """Return a list of the ids of the active nodes in this class."""
557     def <strong>count</strong>(self):
558         """Get the number of nodes in this class.
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         """
565     # Manipulating properties:
567     def <strong>getprops</strong>(self):
568         """Return a dictionary mapping property names to property objects."""
570     def <strong>addprop</strong>(self, **properties):
571         """Add properties to this class.
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>
579 <h3>3.6. Application Example</h3>
581 <p>Here is an example of how the hyperdatabase module would work in practice.
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>
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.
657 <p><hr>
658 <h2>4. Roundup Database</h2>
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.
667 <h3>4.1. Reserved Classes</h3>
669 <p>Internal to this layer we reserve three special classes
670 of nodes that are not items.
672 <h4>4.1.1. Users</h4>
674 <p>Users are stored in the hyperdatabase as nodes of
675 class "user".  The "user" class has the definition:
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>
683 <h4>4.1.2. Messages</h4>
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:
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>
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.
709 <h4>4.1.3. Files</h4>
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:
717 <blockquote><pre><small
718 >hyperdb.Class(db, "file", user=hyperdb.Link("user"),
719                           name=hyperdb.String(),
720                           type=hyperdb.String())</small></pre></blockquote>
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.
726 <h3>4.2. Item Classes</h3>
728 <p>All items have the following standard properties:
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>
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).
747 <h3>4.3. Interface Specification</h3>
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.
753 <blockquote><pre><small
754 >class <strong>Database</strong>:
755     # Overridden methods:
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."""
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."""
765     # New methods:
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."""
771 class <strong>Class</strong>:
772     # Overridden methods:
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         """
781     # New methods:
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)."""
787 class <strong>ItemClass</strong>(Class):
788     # Overridden methods:
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."""
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."""
801     # New methods:
803     def <strong>addmessage</strong>(self, nodeid, summary, text):
804         """Add a message to an item's mail spool.
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         """
814     def <strong>sendmessage</strong>(self, nodeid, msgid):
815         """Send a message to the members of an item's nosy list.
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>
823 <h3>4.4. Default Schema</h3>
825 <p>The default schema included with Roundup turns it into a
826 typical software bug tracker.  The database is set up like this:
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")
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")
848 Class(db, "keyword", name=hyperdb.String())
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>
856 <p>(The "order" property hasn't been explained yet.  It
857 gets used by the Web user interface for sorting.)
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:
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>
872 <p><hr>
873 <h2>5. Detector Interface</h2>
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.
883 <p>There are two kinds of detectors:
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>
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.
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.
902 <h3>5.1. Interface Specification</h3>
904 <p>The <tt>audit()</tt> and <tt>react()</tt> methods
905 register detectors on a given class of nodes.
907 <blockquote><pre><small
908 >class Class:
909     def <strong>audit</strong>(self, event, detector):
910         """Register an auditor on this class.
912         'event' should be one of "create", "set", or "retire".
913         'detector' should be a function accepting four arguments.
914         """
916     def <strong>react</strong>(self, event, detector):
917         """Register a reactor on this class.
919         'event' should be one of "create", "set", or "retire".
920         'detector' should be a function accepting four arguments.
921         """
922 </small></pre></blockquote>
924 <p>Auditors are called with the arguments:
926 <blockquote><pre><small
927 >audit(db, cl, nodeid, newdata)</small></pre></blockquote>
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.
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.
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.
942 For a <tt>retire()</tt> operation, <tt>newdata</tt> is <tt>None</tt>.
944 <p>Reactors are called with the arguments:
946 <blockquote><pre><small
947 >react(db, cl, nodeid, olddata)</small></pre></blockquote>
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.
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.
957 For a <tt>set()</tt> operation, <tt>olddata</tt>
958 contains the names and previous values of properties that were changed.
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>.
963 <h3>5.2. Detector Example</h3>
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.
970 <blockquote><pre><small
971 ># Permit users only to add themselves to the "approvals" list.
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."
989 # When three people have approved a project, change its
990 # status from "pending" to "approved".
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"))
997 def init(db):
998     db.project.audit("set", check_approval)
999     db.project.react("set", approve_project)</small
1000 ></pre></blockquote>    
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".
1009 <blockquote><pre><small
1010 ># Only accept attempts to create new patches that come with patch files.
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."
1020 # When the status is changed from "approved" to "applied", apply the patch.
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...
1027 def init(db):
1028     db.patch.audit("create", check_new_patch)
1029     db.patch.react("set", apply_patch)</small
1030 ></pre></blockquote>
1032 <p><hr>
1033 <h2>6. Command Interface</h2>
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.)
1040 <h3>6.1. Interface Specification</h3>
1042 <p>A single command, <tt>roundup</tt>, provides basic access to
1043 the hyperdatabase from the command line.
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>
1056 <p>Property values are represented as strings in command arguments
1057 and in the printed results:
1059 <ul>
1060 <li>Strings are, well, strings.
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.
1066 <li>Link values are printed as node designators.  When given as
1067 an argument, node designators and key strings are both accepted.
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>
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.
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.
1084 <h3>6.2. Usage Example</h3>
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:
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>
1101 <p>Or, using the <tt>-list</tt> option, this can be written as a single command:
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>
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).
1120 <h3>7.1. Message Processing</h3>
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.
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.
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.
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).
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.
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.
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.
1179 <h3>7.2. Nosy Lists</h3>
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.
1191 <h3>7.3. Setting Properties</h3>
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.
1199 <p><hr>
1200 <h2>8. Web User Interface</h2>
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.
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.
1213 <h3>8.1. Views and View Specifiers</h3>
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.
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.
1230 <h3>8.2. Displaying Properties</h3>
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.
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.
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
1250 <blockquote><pre><small
1251 >    &lt;display call="plain('status', max=30)"&gt;
1252 </small></pre></blockquote>
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>
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.
1265 <p>Some of the standard displayer functions include:
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)
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
1278 <li><strong>menu</strong>: for a Link property, display
1279 a menu of the available choices
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
1285 <li><strong>count</strong>: for a Multilink property, display
1286 a count of the number of links in the list
1288 <li><strong>reldate</strong>: display a Date property in terms
1289 of an interval relative to the current date (e.g. "+ 3w", "- 2d").
1291 <li><strong>download</strong>: show a Link("file") or Multilink("file")
1292 property using links that allow you to download files
1294 <li><strong>checklist</strong>: for a Link or Multilink property,
1295 display checkboxes for the available choices to permit filtering
1296 </ul>
1298 <h3>8.3. Index Views</h3>
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.
1306 <h4>8.3.1. Index View Specifiers</h4>
1308 <p>An index view specifier looks like this (whitespace
1309 has been added for clarity):
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>
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.
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.
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.
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.
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.
1358 <h4>8.3.2. Filter Section</h4>
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.
1366 <p>Here's a simple example of a filter template.
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>
1381 <h4>8.3.3. Index Section</h4>
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.
1391 <p>Here's a simple example of an index template.
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>
1406 <h4>8.3.4. Sorting</h4>
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.
1415 <h3>8.4. Item Views</h3>
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.
1421 <h4>8.4.1. Item View Specifiers</h4>
1423 <p>An item view specifier is simply the item's designator:
1425 <blockquote><pre><small
1426 >/patch23
1427 </small></pre></blockquote>
1429 <h4>8.4.2. Editor Section</h4>
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.
1435 <p>Here's an example of a basic editor template.
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>
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.
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:
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>
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).
1491 <h4>8.4.3. Spool Section</h4>
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.
1498 <p><hr>
1499 <h2>9. Deployment Scenarios</h2>
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.
1509 <p><hr>
1510 <h2>10. Acknowledgements</h2>
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>
1522 <p>
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>
1541 <p><hr>
1542 <center>Last modified 2001/04/06 11:50:59.9063 US/Mountain</center>
1543 </BODY>
1544 </HTML>