Code

more doc
[roundup.git] / doc / design.txt
1 ========================================================
2 Roundup - An Issue-Tracking System for Knowledge Workers
3 ========================================================
5 :Authors: Ka-Ping Yee (original__), Richard Jones (implementation)
7 __ spec.html
9 .. contents::
11 Introduction
12 ---------------
14 This document presents a description of the components
15 of the Roundup system and specifies their interfaces and
16 behaviour in sufficient detail to guide an implementation.
17 For the philosophy and rationale behind the Roundup design,
18 see the first-round Software Carpentry submission for Roundup.
19 This document fleshes out that design as well as specifying
20 interfaces so that the components can be developed separately.
23 The Layer Cake
24 -----------------
26 Lots of software design documents come with a picture of
27 a cake.  Everybody seems to like them.  I also like cakes
28 (i think they are tasty).  So i, too, shall include
29 a picture of a cake here::
31      _________________________________________________________________________
32     |  E-mail Client   |   Web Browser   |   Detector Scripts   |    Shell    |
33     |------------------+-----------------+----------------------+-------------|
34     |   E-mail User    |    Web User     |      Detector        |   Command   | 
35     |-------------------------------------------------------------------------|
36     |                         Roundup Database Layer                          |
37     |-------------------------------------------------------------------------|
38     |                          Hyperdatabase Layer                            |
39     |-------------------------------------------------------------------------|
40     |                             Storage Layer                               |
41      -------------------------------------------------------------------------
43 The colourful parts of the cake are part of our system;
44 the faint grey parts of the cake are external components.
46 I will now proceed to forgo all table manners and
47 eat from the bottom of the cake to the top.  You may want
48 to stand back a bit so you don't get covered in crumbs.
51 Hyperdatabase
52 -------------
54 The lowest-level component to be implemented is the hyperdatabase.
55 The hyperdatabase is intended to be
56 a flexible data store that can hold configurable data in
57 records which we call items.
59 The hyperdatabase is implemented on top of the storage layer,
60 an external module for storing its data.  The storage layer could
61 be a third-party RDBMS; for a "batteries-included" distribution,
62 implementing the hyperdatabase on the standard bsddb
63 module is suggested.
65 Dates and Date Arithmetic
66 ~~~~~~~~~~~~~~~~~~~~~~~~~
68 Before we get into the hyperdatabase itself, we need a
69 way of handling dates.  The hyperdatabase module provides
70 Timestamp objects for
71 representing date-and-time stamps and Interval objects for
72 representing date-and-time intervals.
74 As strings, date-and-time stamps are specified with
75 the date in international standard format
76 (``yyyy-mm-dd``)
77 joined to the time (``hh:mm:ss``)
78 by a period "``.``".  Dates in
79 this form can be easily compared and are fairly readable
80 when printed.  An example of a valid stamp is
81 "``2000-06-24.13:03:59``".
82 We'll call this the "full date format".  When Timestamp objects are
83 printed as strings, they appear in the full date format with
84 the time always given in GMT.  The full date format is always
85 exactly 19 characters long.
87 For user input, some partial forms are also permitted:
88 the whole time or just the seconds may be omitted; and the whole date
89 may be omitted or just the year may be omitted.  If the time is given,
90 the time is interpreted in the user's local time zone.
91 The Date constructor takes care of these conversions.
92 In the following examples, suppose that ``yyyy`` is the current year,
93 ``mm`` is the current month, and ``dd`` is the current
94 day of the month; and suppose that the user is on Eastern Standard Time.
96 -   "2000-04-17" means <Date 2000-04-17.00:00:00>
97 -   "01-25" means <Date yyyy-01-25.00:00:00>
98 -   "2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
99 -   "08-13.22:13" means <Date yyyy-08-14.03:13:00>
100 -   "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
101 -   "14:25" means
102 -   <Date yyyy-mm-dd.19:25:00>
103 -   "8:47:11" means
104 -   <Date yyyy-mm-dd.13:47:11>
105 -   the special date "." means "right now"
108 Date intervals are specified using the suffixes
109 "y", "m", and "d".  The suffix "w" (for "week") means 7 days.
110 Time intervals are specified in hh:mm:ss format (the seconds
111 may be omitted, but the hours and minutes may not).
113 -   "3y" means three years
114 -   "2y 1m" means two years and one month
115 -   "1m 25d" means one month and 25 days
116 -   "2w 3d" means two weeks and three days
117 -   "1d 2:50" means one day, two hours, and 50 minutes
118 -   "14:00" means 14 hours
119 -   "0:04:33" means four minutes and 33 seconds
122 The Date class should understand simple date expressions of the form 
123 *stamp* ``+`` *interval* and *stamp* ``-`` *interval*.
124 When adding or subtracting intervals involving months or years, the
125 components are handled separately.  For example, when evaluating
126 "``2000-06-25 + 1m 10d``", we first add one month to
127 get 2000-07-25, then add 10 days to get
128 2000-08-04 (rather than trying to decide whether
129 1m 10d means 38 or 40 or 41 days).
131 Here is an outline of the Date and Interval classes::
133     class Date:
134         def __init__(self, spec, offset):
135             """Construct a date given a specification and a time zone offset.
137             'spec' is a full date or a partial form, with an optional
138             added or subtracted interval.  'offset' is the local time
139             zone offset from GMT in hours.
140             """
142         def __add__(self, interval):
143             """Add an interval to this date to produce another date."""
145         def __sub__(self, interval):
146             """Subtract an interval from this date to produce another date."""
148         def __cmp__(self, other):
149             """Compare this date to another date."""
151         def __str__(self):
152             """Return this date as a string in the yyyy-mm-dd.hh:mm:ss format."""
154         def local(self, offset):
155             """Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone."""
157     class Interval:
158         def __init__(self, spec):
159             """Construct an interval given a specification."""
161         def __cmp__(self, other):
162             """Compare this interval to another interval."""
163             
164         def __str__(self):
165             """Return this interval as a string."""
169 Here are some examples of how these classes would behave in practice.
170 For the following examples, assume that we are on Eastern Standard
171 Time and the current local time is 19:34:02 on 25 June 2000::
173     >>> Date(".")
174     <Date 2000-06-26.00:34:02>
175     >>> _.local(-5)
176     "2000-06-25.19:34:02"
177     >>> Date(". + 2d")
178     <Date 2000-06-28.00:34:02>
179     >>> Date("1997-04-17", -5)
180     <Date 1997-04-17.00:00:00>
181     >>> Date("01-25", -5)
182     <Date 2000-01-25.00:00:00>
183     >>> Date("08-13.22:13", -5)
184     <Date 2000-08-14.03:13:00>
185     >>> Date("14:25", -5)
186     <Date 2000-06-25.19:25:00>
187     >>> Interval("  3w  1  d  2:00")
188     <Interval 22d 2:00>
189     >>> Date(". + 2d") - Interval("3w")
190     <Date 2000-06-07.00:34:02>
192 Items and Classes
193 ~~~~~~~~~~~~~~~~~
195 Items contain data in properties.  To Python, these
196 properties are presented as the key-value pairs of a dictionary.
197 Each item belongs to a class which defines the names
198 and types of its properties.  The database permits the creation
199 and modification of classes as well as items.
201 Identifiers and Designators
202 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
204 Each item has a numeric identifier which is unique among
205 items in its class.  The items are numbered sequentially
206 within each class in order of creation, starting from 1.
207 The designator
208 for an item is a way to identify an item in the database, and
209 consists of the name of the item's class concatenated with
210 the item's numeric identifier.
212 For example, if "spam" and "eggs" are classes, the first
213 item created in class "spam" has id 1 and designator "spam1".
214 The first item created in class "eggs" also has id 1 but has
215 the distinct designator "eggs1".  Item designators are
216 conventionally enclosed in square brackets when mentioned
217 in plain text.  This permits a casual mention of, say,
218 "[patch37]" in an e-mail message to be turned into an active
219 hyperlink.
221 Property Names and Types
222 ~~~~~~~~~~~~~~~~~~~~~~~~
224 Property names must begin with a letter.
226 A property may be one of five basic types:
228 - String properties are for storing arbitrary-length strings.
230 - Boolean properties are for storing true/false, or yes/no values.
232 - Number properties are for storing numeric values.
234 - Date properties store date-and-time stamps.
235   Their values are Timestamp objects.
237 - A Link property refers to a single other item
238   selected from a specified class.  The class is part of the property;
239   the value is an integer, the id of the chosen item.
241 - A Multilink property refers to possibly many items
242   in a specified class.  The value is a list of integers.
244 *None* is also a permitted value for any of these property
245 types.  An attempt to store None into a Multilink property stores an empty list.
247 A property that is not specified will return as None from a *get*
248 operation.
250 Hyperdb Interface Specification
251 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253 TODO: replace the Interface Specifications with links to the pydoc
255 The hyperdb module provides property objects to designate
256 the different kinds of properties.  These objects are used when
257 specifying what properties belong in classes::
259     class String:
260         def __init__(self, indexme='no'):
261             """An object designating a String property."""
263     class Boolean:
264         def __init__(self):
265             """An object designating a Boolean property."""
267     class Number:
268         def __init__(self):
269             """An object designating a Number property."""
271     class Date:
272         def __init__(self):
273             """An object designating a Date property."""
275     class Link:
276         def __init__(self, classname, do_journal='yes'):
277             """An object designating a Link property that links to
278             items in a specified class.
280             If the do_journal argument is not 'yes' then changes to
281             the property are not journalled in the linked item.
282             """
284     class Multilink:
285         def __init__(self, classname, do_journal='yes'):
286             """An object designating a Multilink property that links
287             to items in a specified class.
289             If the do_journal argument is not 'yes' then changes to
290             the property are not journalled in the linked item(s).
291             """
294 Here is the interface provided by the hyperdatabase::
296     class Database:
297         """A database for storing records containing flexible data types."""
299         def __init__(self, config, journaltag=None):
300             """Open a hyperdatabase given a specifier to some storage.
302             The 'storagelocator' is obtained from config.DATABASE.
303             The meaning of 'storagelocator' depends on the particular
304             implementation of the hyperdatabase.  It could be a file name,
305             a directory path, a socket descriptor for a connection to a
306             database over the network, etc.
308             The 'journaltag' is a token that will be attached to the journal
309             entries for any edits done on the database.  If 'journaltag' is
310             None, the database is opened in read-only mode: the Class.create(),
311             Class.set(), Class.retire(), and Class.restore() methods are 
312             disabled.
313             """
315         def __getattr__(self, classname):
316             """A convenient way of calling self.getclass(classname)."""
318         def getclasses(self):
319             """Return a list of the names of all existing classes."""
321         def getclass(self, classname):
322             """Get the Class object representing a particular class.
324             If 'classname' is not a valid class name, a KeyError is raised.
325             """
327     class Class:
328         """The handle to a particular class of items in a hyperdatabase."""
330         def __init__(self, db, classname, **properties):
331             """Create a new class with a given name and property specification.
333             'classname' must not collide with the name of an existing class,
334             or a ValueError is raised.  The keyword arguments in 'properties'
335             must map names to property objects, or a TypeError is raised.
336             """
338         # Editing items:
340         def create(self, **propvalues):
341             """Create a new item of this class and return its id.
343             The keyword arguments in 'propvalues' map property names to values.
344             The values of arguments must be acceptable for the types of their
345             corresponding properties or a TypeError is raised.  If this class
346             has a key property, it must be present and its value must not
347             collide with other key strings or a ValueError is raised.  Any other
348             properties on this class that are missing from the 'propvalues'
349             dictionary are set to None.  If an id in a link or multilink
350             property does not refer to a valid item, an IndexError is raised.
351             """
353         def get(self, itemid, propname):
354             """Get the value of a property on an existing item of this class.
356             'itemid' must be the id of an existing item of this class or an
357             IndexError is raised.  'propname' must be the name of a property
358             of this class or a KeyError is raised.
359             """
361         def set(self, itemid, **propvalues):
362             """Modify a property on an existing item of this class.
363             
364             'itemid' must be the id of an existing item of this class or an
365             IndexError is raised.  Each key in 'propvalues' must be the name
366             of a property of this class or a KeyError is raised.  All values
367             in 'propvalues' must be acceptable types for their corresponding
368             properties or a TypeError is raised.  If the value of the key
369             property is set, it must not collide with other key strings or a
370             ValueError is raised.  If the value of a Link or Multilink
371             property contains an invalid item id, a ValueError is raised.
372             """
374         def retire(self, itemid):
375             """Retire an item.
376             
377             The properties on the item remain available from the get() method,
378             and the item's id is never reused.  Retired items are not returned
379             by the find(), list(), or lookup() methods, and other items may
380             reuse the values of their key properties.
381             """
383         def restore(self, nodeid):
384         '''Restpre a retired node.
386         Make node available for all operations like it was before retirement.
387         '''
389         def history(self, itemid):
390             """Retrieve the journal of edits on a particular item.
392             'itemid' must be the id of an existing item of this class or an
393             IndexError is raised.
395             The returned list contains tuples of the form
397                 (date, tag, action, params)
399             'date' is a Timestamp object specifying the time of the change and
400             'tag' is the journaltag specified when the database was opened.
401             'action' may be:
403                 'create' or 'set' -- 'params' is a dictionary of property values
404                 'link' or 'unlink' -- 'params' is (classname, itemid, propname)
405                 'retire' -- 'params' is None
406             """
408         # Locating items:
410         def setkey(self, propname):
411             """Select a String property of this class to be the key property.
413             'propname' must be the name of a String property of this class or
414             None, or a TypeError is raised.  The values of the key property on
415             all existing items must be unique or a ValueError is raised.
416             """
418         def getkey(self):
419             """Return the name of the key property for this class or None."""
421         def lookup(self, keyvalue):
422             """Locate a particular item by its key property and return its id.
424             If this class has no key property, a TypeError is raised.  If the
425             'keyvalue' matches one of the values for the key property among
426             the items in this class, the matching item's id is returned;
427             otherwise a KeyError is raised.
428             """
430         def find(self, propname, itemid):
431             """Get the ids of items in this class which link to the given items.
433             'propspec' consists of keyword args propname={itemid:1,}   
434             'propname' must be the name of a property in this class, or a
435             KeyError is raised.  That property must be a Link or Multilink
436             property, or a TypeError is raised.
438             Any item in this class whose 'propname' property links to any of the
439             itemids will be returned. Used by the full text indexing, which
440             knows that "foo" occurs in msg1, msg3 and file7, so we have hits
441             on these issues:
443                 db.issue.find(messages={'1':1,'3':1}, files={'7':1})
444             """
446         def filter(self, search_matches, filterspec, sort, group):
447             ''' Return a list of the ids of the active items in this class that
448                 match the 'filter' spec, sorted by the group spec and then the
449                 sort spec.
450             '''
452         def list(self):
453             """Return a list of the ids of the active items in this class."""
455         def count(self):
456             """Get the number of items in this class.
458             If the returned integer is 'numitems', the ids of all the items
459             in this class run from 1 to numitems, and numitems+1 will be the
460             id of the next item to be created in this class.
461             """
463         # Manipulating properties:
465         def getprops(self):
466             """Return a dictionary mapping property names to property objects."""
468         def addprop(self, **properties):
469             """Add properties to this class.
471             The keyword arguments in 'properties' must map names to property
472             objects, or a TypeError is raised.  None of the keys in 'properties'
473             may collide with the names of existing properties, or a ValueError
474             is raised before any properties have been added.
475             """
477         def getitem(self, itemid, cache=1):
478             ''' Return a Item convenience wrapper for the item.
480             'itemid' must be the id of an existing item of this class or an
481             IndexError is raised.
483             'cache' indicates whether the transaction cache should be queried
484             for the item. If the item has been modified and you need to
485             determine what its values prior to modification are, you need to
486             set cache=0.
487             '''
489     class Item:
490         ''' A convenience wrapper for the given item. It provides a mapping
491             interface to a single item's properties
492         '''
494 Hyperdatabase Implementations
495 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
497 Hyperdatabase implementations exist to create the interface described in the
498 `hyperdb interface specification`_
499 over an existing storage mechanism. Examples are relational databases,
500 \*dbm key-value databases, and so on.
502 Several implementations are provided - they belong in the roundup.backends
503 package.
506 Application Example
507 ~~~~~~~~~~~~~~~~~~~
509 Here is an example of how the hyperdatabase module would work in practice::
511     >>> import hyperdb
512     >>> db = hyperdb.Database("foo.db", "ping")
513     >>> db
514     <hyperdb.Database "foo.db" opened by "ping">
515     >>> hyperdb.Class(db, "status", name=hyperdb.String())
516     <hyperdb.Class "status">
517     >>> _.setkey("name")
518     >>> db.status.create(name="unread")
519     1
520     >>> db.status.create(name="in-progress")
521     2
522     >>> db.status.create(name="testing")
523     3
524     >>> db.status.create(name="resolved")
525     4
526     >>> db.status.count()
527     4
528     >>> db.status.list()
529     [1, 2, 3, 4]
530     >>> db.status.lookup("in-progress")
531     2
532     >>> db.status.retire(3)
533     >>> db.status.list()
534     [1, 2, 4]
535     >>> hyperdb.Class(db, "issue", title=hyperdb.String(), status=hyperdb.Link("status"))
536     <hyperdb.Class "issue">
537     >>> db.issue.create(title="spam", status=1)
538     1
539     >>> db.issue.create(title="eggs", status=2)
540     2
541     >>> db.issue.create(title="ham", status=4)
542     3
543     >>> db.issue.create(title="arguments", status=2)
544     4
545     >>> db.issue.create(title="abuse", status=1)
546     5
547     >>> hyperdb.Class(db, "user", username=hyperdb.Key(), password=hyperdb.String())
548     <hyperdb.Class "user">
549     >>> db.issue.addprop(fixer=hyperdb.Link("user"))
550     >>> db.issue.getprops()
551     {"title": <hyperdb.String>, "status": <hyperdb.Link to "status">,
552      "user": <hyperdb.Link to "user">}
553     >>> db.issue.set(5, status=2)
554     >>> db.issue.get(5, "status")
555     2
556     >>> db.status.get(2, "name")
557     "in-progress"
558     >>> db.issue.get(5, "title")
559     "abuse"
560     >>> db.issue.find("status", db.status.lookup("in-progress"))
561     [2, 4, 5]
562     >>> db.issue.history(5)
563     [(<Date 2000-06-28.19:09:43>, "ping", "create", {"title": "abuse", "status": 1}),
564      (<Date 2000-06-28.19:11:04>, "ping", "set", {"status": 2})]
565     >>> db.status.history(1)
566     [(<Date 2000-06-28.19:09:43>, "ping", "link", ("issue", 5, "status")),
567      (<Date 2000-06-28.19:11:04>, "ping", "unlink", ("issue", 5, "status"))]
568     >>> db.status.history(2)
569     [(<Date 2000-06-28.19:11:04>, "ping", "link", ("issue", 5, "status"))]
572 For the purposes of journalling, when a Multilink property is
573 set to a new list of items, the hyperdatabase compares the old
574 list to the new list.
575 The journal records "unlink" events for all the items that appear
576 in the old list but not the new list,
577 and "link" events for
578 all the items that appear in the new list but not in the old list.
581 Roundup Database
582 ----------------
584 The Roundup database layer is implemented on top of the
585 hyperdatabase and mediates calls to the database.
586 Some of the classes in the Roundup database are considered
587 issue classes.
588 The Roundup database layer adds detectors and user items,
589 and on issues it provides mail spools, nosy lists, and superseders.
591 Reserved Classes
592 ~~~~~~~~~~~~~~~~
594 Internal to this layer we reserve three special classes
595 of items that are not issues.
597 Users
598 """""
600 Users are stored in the hyperdatabase as items of
601 class "user".  The "user" class has the definition::
603     hyperdb.Class(db, "user", username=hyperdb.String(),
604                               password=hyperdb.String(),
605                               address=hyperdb.String())
606     db.user.setkey("username")
608 Messages
609 """"""""
611 E-mail messages are represented by hyperdatabase items of class "msg".
612 The actual text content of the messages is stored in separate files.
613 (There's no advantage to be gained by stuffing them into the
614 hyperdatabase, and if messages are stored in ordinary text files,
615 they can be grepped from the command line.)  The text of a message is
616 saved in a file named after the message item designator (e.g. "msg23")
617 for the sake of the command interface (see below).  Attachments are
618 stored separately and associated with "file" items.
619 The "msg" class has the definition::
621     hyperdb.Class(db, "msg", author=hyperdb.Link("user"),
622                              recipients=hyperdb.Multilink("user"),
623                              date=hyperdb.Date(),
624                              summary=hyperdb.String(),
625                              files=hyperdb.Multilink("file"))
627 The "author" property indicates the author of the message
628 (a "user" item must exist in the hyperdatabase for any messages
629 that are stored in the system).
630 The "summary" property contains a summary of the message for display
631 in a message index.
633 Files
634 """""
636 Submitted files are represented by hyperdatabase
637 items of class "file".  Like e-mail messages, the file content
638 is stored in files outside the database,
639 named after the file item designator (e.g. "file17").
640 The "file" class has the definition::
642     hyperdb.Class(db, "file", user=hyperdb.Link("user"),
643                               name=hyperdb.String(),
644                               type=hyperdb.String())
646 The "user" property indicates the user who submitted the
647 file, the "name" property holds the original name of the file,
648 and the "type" property holds the MIME type of the file as received.
650 Issue Classes
651 ~~~~~~~~~~~~~
653 All issues have the following standard properties:
655 =========== ==========================
656 Property    Definition
657 =========== ==========================
658 title       hyperdb.String()
659 messages    hyperdb.Multilink("msg")
660 files       hyperdb.Multilink("file")
661 nosy        hyperdb.Multilink("user")
662 superseder  hyperdb.Multilink("issue")
663 =========== ==========================
665 Also, two Date properties named "creation" and "activity" are
666 fabricated by the Roundup database layer.  By "fabricated" we
667 mean that no such properties are actually stored in the
668 hyperdatabase, but when properties on issues are requested, the
669 "creation" and "activity" properties are made available.
670 The value of the "creation" property is the date when an issue was
671 created, and the value of the "activity" property is the
672 date when any property on the issue was last edited (equivalently,
673 these are the dates on the first and last records in the issue's journal).
675 Roundupdb Interface Specification
676 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
678 The interface to a Roundup database delegates most method
679 calls to the hyperdatabase, except for the following
680 changes and additional methods::
682     class Database:
683         def getuid(self):
684             """Return the id of the "user" item associated with the user
685             that owns this connection to the hyperdatabase."""
687     class Class:
688         # Overridden methods:
690         def create(self, **propvalues):
691         def set(self, **propvalues):
692         def retire(self, itemid):
693             """These operations trigger detectors and can be vetoed.  Attempts
694             to modify the "creation" or "activity" properties cause a KeyError.
695             """
697         # New methods:
699         def audit(self, event, detector):
700         def react(self, event, detector):
701             """Register a detector (see below for more details)."""
703     class IssueClass(Class):
704         # Overridden methods:
706         def __init__(self, db, classname, **properties):
707             """The newly-created class automatically includes the "messages",
708             "files", "nosy", and "superseder" properties.  If the 'properties'
709             dictionary attempts to specify any of these properties or a
710             "creation" or "activity" property, a ValueError is raised."""
712         def get(self, itemid, propname):
713         def getprops(self):
714             """In addition to the actual properties on the item, these
715             methods provide the "creation" and "activity" properties."""
717         # New methods:
719         def addmessage(self, itemid, summary, text):
720             """Add a message to an issue's mail spool.
722             A new "msg" item is constructed using the current date, the
723             user that owns the database connection as the author, and
724             the specified summary text.  The "files" and "recipients"
725             fields are left empty.  The given text is saved as the body
726             of the message and the item is appended to the "messages"
727             field of the specified issue.
728             """
730         def sendmessage(self, itemid, msgid):
731             """Send a message to the members of an issue's nosy list.
733             The message is sent only to users on the nosy list who are not
734             already on the "recipients" list for the message.  These users
735             are then added to the message's "recipients" list.
736             """
739 Default Schema
740 ~~~~~~~~~~~~~~
742 The default schema included with Roundup turns it into a
743 typical software bug tracker.  The database is set up like this::
745     pri = Class(db, "priority", name=hyperdb.String(), order=hyperdb.String())
746     pri.setkey("name")
747     pri.create(name="critical", order="1")
748     pri.create(name="urgent", order="2")
749     pri.create(name="bug", order="3")
750     pri.create(name="feature", order="4")
751     pri.create(name="wish", order="5")
753     stat = Class(db, "status", name=hyperdb.String(), order=hyperdb.String())
754     stat.setkey("name")
755     stat.create(name="unread", order="1")
756     stat.create(name="deferred", order="2")
757     stat.create(name="chatting", order="3")
758     stat.create(name="need-eg", order="4")
759     stat.create(name="in-progress", order="5")
760     stat.create(name="testing", order="6")
761     stat.create(name="done-cbb", order="7")
762     stat.create(name="resolved", order="8")
764     Class(db, "keyword", name=hyperdb.String())
766     Class(db, "issue", fixer=hyperdb.Multilink("user"),
767                        topic=hyperdb.Multilink("keyword"),
768                        priority=hyperdb.Link("priority"),
769                        status=hyperdb.Link("status"))
771 (The "order" property hasn't been explained yet.  It
772 gets used by the Web user interface for sorting.)
774 The above isn't as pretty-looking as the schema specification
775 in the first-stage submission, but it could be made just as easy
776 with the addition of a convenience function like Choice
777 for setting up the "priority" and "status" classes::
779     def Choice(name, *options):
780         cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String())
781         for i in range(len(options)):
782             cl.create(name=option[i], order=i)
783         return hyperdb.Link(name)
786 Detector Interface
787 ------------------
789 Detectors are Python functions that are triggered on certain
790 kinds of events.  The definitions of the
791 functions live in Python modules placed in a directory set aside
792 for this purpose.  Importing the Roundup database module also
793 imports all the modules in this directory, and the ``init()``
794 function of each module is called when a database is opened to
795 provide it a chance to register its detectors.
797 There are two kinds of detectors:
799 1. an auditor is triggered just before modifying an item
800 2. a reactor is triggered just after an item has been modified
802 When the Roundup database is about to perform a
803 ``create()``, ``set()``, ``retire()``, or ``restore``
804 operation, it first calls any *auditors* that
805 have been registered for that operation on that class.
806 Any auditor may raise a *Reject* exception
807 to abort the operation.
809 If none of the auditors raises an exception, the database
810 proceeds to carry out the operation.  After it's done, it
811 then calls all of the *reactors* that have been registered
812 for the operation.
814 Detector Interface Specification
815 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
817 The ``audit()`` and ``react()`` methods
818 register detectors on a given class of items::
820     class Class:
821         def audit(self, event, detector):
822             """Register an auditor on this class.
824             'event' should be one of "create", "set", "retire", or "restore".
825             'detector' should be a function accepting four arguments.
826             """
828         def react(self, event, detector):
829             """Register a reactor on this class.
831             'event' should be one of "create", "set", "retire", or "restore".
832             'detector' should be a function accepting four arguments.
833             """
835 Auditors are called with the arguments::
837     audit(db, cl, itemid, newdata)
839 where ``db`` is the database, ``cl`` is an
840 instance of Class or IssueClass within the database, and ``newdata``
841 is a dictionary mapping property names to values.
843 For a ``create()``
844 operation, the ``itemid`` argument is None and newdata
845 contains all of the initial property values with which the item
846 is about to be created.
848 For a ``set()`` operation, newdata
849 contains only the names and values of properties that are about
850 to be changed.
852 For a ``retire()`` or ``restore()`` operation, newdata is None.
854 Reactors are called with the arguments::
856     react(db, cl, itemid, olddata)
858 where ``db`` is the database, ``cl`` is an
859 instance of Class or IssueClass within the database, and ``olddata``
860 is a dictionary mapping property names to values.
862 For a ``create()``
863 operation, the ``itemid`` argument is the id of the
864 newly-created item and ``olddata`` is None.
866 For a ``set()`` operation, ``olddata``
867 contains the names and previous values of properties that were changed.
869 For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
870 the retired or restored item and ``olddata`` is None.
872 Detector Example
873 ~~~~~~~~~~~~~~~~
875 Here is an example of detectors written for a hypothetical
876 project-management application, where users can signal approval
877 of a project by adding themselves to an "approvals" list, and
878 a project proceeds when it has three approvals::
880     # Permit users only to add themselves to the "approvals" list.
882     def check_approvals(db, cl, id, newdata):
883         if newdata.has_key("approvals"):
884             if cl.get(id, "status") == db.status.lookup("approved"):
885                 raise Reject, "You can't modify the approvals list " \
886                     "for a project that has already been approved."
887             old = cl.get(id, "approvals")
888             new = newdata["approvals"]
889             for uid in old:
890                 if uid not in new and uid != db.getuid():
891                     raise Reject, "You can't remove other users from the "
892                         "approvals list; you can only remove yourself."
893             for uid in new:
894                 if uid not in old and uid != db.getuid():
895                     raise Reject, "You can't add other users to the approvals "
896                         "list; you can only add yourself."
898     # When three people have approved a project, change its
899     # status from "pending" to "approved".
901     def approve_project(db, cl, id, olddata):
902         if olddata.has_key("approvals") and len(cl.get(id, "approvals")) == 3:
903             if cl.get(id, "status") == db.status.lookup("pending"):
904                 cl.set(id, status=db.status.lookup("approved"))
906     def init(db):
907         db.project.audit("set", check_approval)
908         db.project.react("set", approve_project)
910 Here is another example of a detector that can allow or prevent
911 the creation of new items.  In this scenario, patches for a software
912 project are submitted by sending in e-mail with an attached file,
913 and we want to ensure that there are text/plain attachments on
914 the message.  The maintainer of the package can then apply the
915 patch by setting its status to "applied"::
917     # Only accept attempts to create new patches that come with patch files.
919     def check_new_patch(db, cl, id, newdata):
920         if not newdata["files"]:
921             raise Reject, "You can't submit a new patch without " \
922                           "attaching a patch file."
923         for fileid in newdata["files"]:
924             if db.file.get(fileid, "type") != "text/plain":
925                 raise Reject, "Submitted patch files must be text/plain."
927     # When the status is changed from "approved" to "applied", apply the patch.
929     def apply_patch(db, cl, id, olddata):
930         if cl.get(id, "status") == db.status.lookup("applied") and \
931             olddata["status"] == db.status.lookup("approved"):
932             # ...apply the patch...
934     def init(db):
935         db.patch.audit("create", check_new_patch)
936         db.patch.react("set", apply_patch)
939 Command Interface
940 -----------------
942 The command interface is a very simple and minimal interface,
943 intended only for quick searches and checks from the shell prompt.
944 (Anything more interesting can simply be written in Python using
945 the Roundup database module.)
947 Command Interface Specification
948 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
950 A single command, roundup, provides basic access to
951 the hyperdatabase from the command line::
953     roundup-admin help
954     roundup-admin get [-list] designator[, designator,...] propname
955     roundup-admin set designator[, designator,...] propname=value ...
956     roundup-admin find [-list] classname propname=value ...
958 See ``roundup-admin help commands`` for a complete list of commands.
960 Property values are represented as strings in command arguments
961 and in the printed results:
963 - Strings are, well, strings.
965 - Numbers are displayed the same as strings.
967 - Booleans are displayed as 'Yes' or 'No'.
969 - Date values are printed in the full date format in the local
970   time zone, and accepted in the full format or any of the partial
971   formats explained above.
973 - Link values are printed as item designators.  When given as
974   an argument, item designators and key strings are both accepted.
976 - Multilink values are printed as lists of item designators
977   joined by commas.  When given as an argument, item designators
978   and key strings are both accepted; an empty string, a single item,
979   or a list of items joined by commas is accepted.
981 When multiple items are specified to the
982 roundup get or roundup set
983 commands, the specified properties are retrieved or set
984 on all the listed items.
986 When multiple results are returned by the roundup get
987 or roundup find commands, they are printed one per
988 line (default) or joined by commas (with the -list) option.
990 Usage Example
991 ~~~~~~~~~~~~~
993 To find all messages regarding in-progress issues that
994 contain the word "spam", for example, you could execute the
995 following command from the directory where the database
996 dumps its files::
998     shell% for issue in `roundup find issue status=in-progress`; do
999     > grep -l spam `roundup get $issue messages`
1000     > done
1001     msg23
1002     msg49
1003     msg50
1004     msg61
1005     shell%
1007 Or, using the -list option, this can be written as a single command::
1009     shell% grep -l spam `roundup get \
1010         \`roundup find -list issue status=in-progress\` messages`
1011     msg23
1012     msg49
1013     msg50
1014     msg61
1015     shell%
1016     
1018 E-mail User Interface
1019 ---------------------
1021 The Roundup system must be assigned an e-mail address
1022 at which to receive mail.  Messages should be piped to
1023 the Roundup mail-handling script by the mail delivery
1024 system (e.g. using an alias beginning with "|" for sendmail).
1026 Message Processing
1027 ~~~~~~~~~~~~~~~~~~
1029 Incoming messages are examined for multiple parts.
1030 In a multipart/mixed message or part, each subpart is
1031 extracted and examined.  In a multipart/alternative
1032 message or part, we look for a text/plain subpart and
1033 ignore the other parts.  The text/plain subparts are
1034 assembled to form the textual body of the message, to
1035 be stored in the file associated with a "msg" class item.
1036 Any parts of other types are each stored in separate
1037 files and given "file" class items that are linked to
1038 the "msg" item.
1040 The "summary" property on message items is taken from
1041 the first non-quoting section in the message body.
1042 The message body is divided into sections by blank lines.
1043 Sections where the second and all subsequent lines begin
1044 with a ">" or "|" character are considered "quoting
1045 sections".  The first line of the first non-quoting 
1046 section becomes the summary of the message.
1048 All of the addresses in the To: and Cc: headers of the
1049 incoming message are looked up among the user items, and
1050 the corresponding users are placed in the "recipients"
1051 property on the new "msg" item.  The address in the From:
1052 header similarly determines the "author" property of the
1053 new "msg" item.
1054 The default handling for
1055 addresses that don't have corresponding users is to create
1056 new users with no passwords and a username equal to the
1057 address.  (The web interface does not permit logins for
1058 users with no passwords.)  If we prefer to reject mail from
1059 outside sources, we can simply register an auditor on the
1060 "user" class that prevents the creation of user items with
1061 no passwords.
1063 The subject line of the incoming message is examined to
1064 determine whether the message is an attempt to create a new
1065 issue or to discuss an existing issue.  A designator enclosed
1066 in square brackets is sought as the first thing on the
1067 subject line (after skipping any "Fwd:" or "Re:" prefixes).
1069 If an issue designator (class name and id number) is found
1070 there, the newly created "msg" item is added to the "messages"
1071 property for that issue, and any new "file" items are added to
1072 the "files" property for the issue.
1074 If just an issue class name is found there, we attempt to
1075 create a new issue of that class with its "messages" property
1076 initialized to contain the new "msg" item and its "files"
1077 property initialized to contain any new "file" items.
1079 Both cases may trigger detectors (in the first case we
1080 are calling the set() method to add the message to the
1081 issue's spool; in the second case we are calling the
1082 create() method to create a new item).  If an auditor
1083 raises an exception, the original message is bounced back to
1084 the sender with the explanatory message given in the exception.
1086 Nosy Lists
1087 ~~~~~~~~~~
1089 A standard detector is provided that watches for additions
1090 to the "messages" property.  When a new message is added, the
1091 detector sends it to all the users on the "nosy" list for the
1092 issue that are not already on the "recipients" list of the
1093 message.  Those users are then appended to the "recipients"
1094 property on the message, so multiple copies of a message
1095 are never sent to the same user.  The journal recorded by
1096 the hyperdatabase on the "recipients" property then provides
1097 a log of when the message was sent to whom.
1099 Setting Properties
1100 ~~~~~~~~~~~~~~~~~~
1102 The e-mail interface also provides a simple way to set
1103 properties on issues.  At the end of the subject line,
1104 ``propname=value`` pairs can be
1105 specified in square brackets, using the same conventions
1106 as for the roundup ``set`` shell command.
1109 Web User Interface
1110 ------------------
1112 The web interface is provided by a CGI script that can be
1113 run under any web server.  A simple web server can easily be
1114 built on the standard CGIHTTPServer module, and
1115 should also be included in the distribution for quick
1116 out-of-the-box deployment.
1118 The user interface is constructed from a number of template
1119 files containing mostly HTML.  Among the HTML tags in templates
1120 are interspersed some nonstandard tags, which we use as
1121 placeholders to be replaced by properties and their values.
1123 Views and View Specifiers
1124 ~~~~~~~~~~~~~~~~~~~~~~~~~
1126 There are two main kinds of views: *index* views and *issue* views.
1127 An index view displays a list of issues of a particular class,
1128 optionally sorted and filtered as requested.  An issue view
1129 presents the properties of a particular issue for editing
1130 and displays the message spool for the issue.
1132 A view specifier is a string that specifies
1133 all the options needed to construct a particular view.
1134 It goes after the URL to the Roundup CGI script or the
1135 web server to form the complete URL to a view.  When the
1136 result of selecting a link or submitting a form takes
1137 the user to a new view, the Web browser should be redirected
1138 to a canonical location containing a complete view specifier
1139 so that the view can be bookmarked.
1141 Displaying Properties
1142 ~~~~~~~~~~~~~~~~~~~~~
1144 Properties appear in the user interface in three contexts:
1145 in indices, in editors, and as search filters.  For each type of
1146 property, there are several display possibilities.  For example,
1147 in an index view, a string property may just be printed as
1148 a plain string, but in an editor view, that property should
1149 be displayed in an editable field.
1151 The display of a property is handled by functions in
1152 the ``cgi.templating`` module.
1154 Displayer functions are triggered by ``tal:content`` or ``tal:replace``
1155 tag attributes in templates.  The value of the attribute
1156 provides an expression for calling the displayer function.
1157 For example, the occurrence of::
1159     tal:content="context/status/plain"
1161 in a template triggers a call to::
1162     
1163     context['status'].plain()
1165 where the context would be an item of the "issue" class.  The displayer
1166 functions can accept extra arguments to further specify
1167 details about the widgets that should be generated.
1169 Some of the standard displayer functions include:
1171 ========= ====================================================================
1172 Function  Description
1173 ========= ====================================================================
1174 plain     display a String property directly;
1175           display a Date property in a specified time zone with an option
1176           to omit the time from the date stamp; for a Link or Multilink
1177           property, display the key strings of the linked items (or the
1178           ids if the linked class has no key property)
1179 field     display a property like the plain displayer above, but in a text
1180           field to be edited
1181 menu      for a Link property, display a menu of the available choices
1182 ========= ====================================================================
1184 See the `customisation`_ documentation for the complete list.
1187 Index Views
1188 ~~~~~~~~~~~
1190 An index view contains two sections: a filter section
1191 and an index section.
1192 The filter section provides some widgets for selecting
1193 which issues appear in the index.  The index section is
1194 a table of issues.
1196 Index View Specifiers
1197 """""""""""""""""""""
1199 An index view specifier looks like this (whitespace
1200 has been added for clarity)::
1202     /issue?status=unread,in-progress,resolved&
1203         topic=security,ui&
1204         :group=priority&
1205         :sort=-activity&
1206         :filters=status,topic&
1207         :columns=title,status,fixer
1210 The index view is determined by two parts of the
1211 specifier: the layout part and the filter part.
1212 The layout part consists of the query parameters that
1213 begin with colons, and it determines the way that the
1214 properties of selected items are displayed.
1215 The filter part consists of all the other query parameters,
1216 and it determines the criteria by which items 
1217 are selected for display.
1219 The filter part is interactively manipulated with
1220 the form widgets displayed in the filter section.  The
1221 layout part is interactively manipulated by clicking
1222 on the column headings in the table.
1224 The filter part selects the union of the
1225 sets of issues with values matching any specified Link
1226 properties and the intersection of the sets
1227 of issues with values matching any specified Multilink
1228 properties.
1230 The example specifies an index of "issue" items.
1231 Only issues with a "status" of either
1232 "unread" or "in-progres" or "resolved" are displayed,
1233 and only issues with "topic" values including both
1234 "security" and "ui" are displayed.  The issues
1235 are grouped by priority, arranged in ascending order;
1236 and within groups, sorted by activity, arranged in
1237 descending order.  The filter section shows filters
1238 for the "status" and "topic" properties, and the
1239 table includes columns for the "title", "status", and
1240 "fixer" properties.
1242 Associated with each issue class is a default
1243 layout specifier.  The layout specifier in the above
1244 example is the default layout to be provided with
1245 the default bug-tracker schema described above in
1246 section 4.4.
1248 Index Section
1249 """""""""""""
1251 The template for an index section describes one row of
1252 the index table.
1253 Fragments enclosed in ``<property>...</property>``
1254 tags are included or omitted depending on whether the
1255 view specifier requests a column for a particular property.
1256 The table cells should contain <display> tags
1257 to display the values of the issue's properties.
1259 Here's a simple example of an index template::
1261     <tr>
1262       <td tal:condition="request/show/title" tal:content="contex/title"></td>
1263       <td tal:condition="request/show/status" tal:content="contex/status"></td>
1264       <td tal:condition="request/show/fixer" tal:content="contex/fixer"></td>
1265     </tr>
1267 Sorting
1268 """""""
1270 String and Date values are sorted in the natural way.
1271 Link properties are sorted according to the value of the
1272 "order" property on the linked items if it is present; or
1273 otherwise on the key string of the linked items; or
1274 finally on the item ids.  Multilink properties are
1275 sorted according to how many links are present.
1277 Issue Views
1278 ~~~~~~~~~~~
1280 An issue view contains an editor section and a spool section.
1281 At the top of an issue view, links to superseding and superseded
1282 issues are always displayed.
1284 Issue View Specifiers
1285 """""""""""""""""""""
1287 An issue view specifier is simply the issue's designator::
1289     /patch23
1292 Editor Section
1293 """"""""""""""
1295 The editor section is generated from a template
1296 containing <display> tags to insert
1297 the appropriate widgets for editing properties.
1299 Here's an example of a basic editor template::
1301     <table>
1302     <tr>
1303         <td colspan=2 tal:content="python:context.title.field(size='60')"></td>
1304     </tr>
1305     <tr>
1306         <td tal:content="context/fixer/field"></td>
1307         <td tal:content="context/status/menu"></td>
1308     </tr>
1309     <tr>
1310         <td tal:content="context/nosy/field"></td>
1311         <td tal:content="context/priority/menu"></td>
1312     </tr>
1313     <tr>
1314         <td colspan=2>
1315           <textarea name=":note" rows=5 cols=60></textarea>
1316         </td>
1317     </tr>
1318     </table>
1320 As shown in the example, the editor template can also include a ":note" field,
1321 which is a text area for entering a note to go along with a change.
1323 When a change is submitted, the system automatically
1324 generates a message describing the changed properties.
1325 The message displays all of the property values on the
1326 issue and indicates which ones have changed.
1327 An example of such a message might be this::
1329     title: Polly Parrot is dead
1330     priority: critical
1331     status: unread -> in-progress
1332     fixer: (none)
1333     keywords: parrot,plumage,perch,nailed,dead
1335 If a note is given in the ":note" field, the note is
1336 appended to the description.  The message is then added
1337 to the issue's message spool (thus triggering the standard
1338 detector to react by sending out this message to the nosy list).
1340 Spool Section
1341 """""""""""""
1343 The spool section lists messages in the issue's "messages"
1344 property.  The index of messages displays the "date", "author",
1345 and "summary" properties on the message items, and selecting a
1346 message takes you to its content.
1348 Access Control
1349 --------------
1351 At each point that requires an action to be performed, the security mechanisms
1352 are asked if the current user has permission. This permission is defined as a
1353 Permission.
1355 Individual assignment of Permission to user is unwieldy. The concept of a
1356 Role, which encompasses several Permissions and may be assigned to many Users,
1357 is quite well developed in many projects. Roundup will take this path, and
1358 allow the multiple assignment of Roles to Users, and multiple Permissions to
1359 Roles. These definitions are not persistent - they're defined when the
1360 application initialises.
1362 There will be two levels of Permission. The Class level permissions define
1363 logical permissions associated with all items of a particular class (or all
1364 classes). The Item level permissions define logical permissions associated
1365 with specific items by way of their user-linked properties.
1368 Access Control Interface Specification
1369 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1371 The security module defines::
1373     class Permission:
1374         ''' Defines a Permission with the attributes
1375             - name
1376             - description
1377             - klass (optional)
1379             The klass may be unset, indicating that this permission is not
1380             locked to a particular hyperdb class. There may be multiple
1381             Permissions for the same name for different classes.
1382         '''
1384     class Role:
1385         ''' Defines a Role with the attributes
1386             - name
1387             - description
1388             - permissions
1389         '''
1391     class Security:
1392         def __init__(self, db):
1393             ''' Initialise the permission and role stores, and add in the
1394                 base roles (for admin user).
1395             '''
1397         def getPermission(self, permission, classname=None):
1398             ''' Find the Permission matching the name and for the class, if the
1399                 classname is specified.
1401                 Raise ValueError if there is no exact match.
1402             '''
1404         def hasPermission(self, permission, userid, classname=None):
1405             ''' Look through all the Roles, and hence Permissions, and see if
1406                 "permission" is there for the specified classname.
1407             '''
1409         def hasItemPermission(self, classname, itemid, **propspec):
1410             ''' Check the named properties of the given item to see if the
1411                 userid appears in them. If it does, then the user is granted
1412                 this permission check.
1414                 'propspec' consists of a set of properties and values that
1415                 must be present on the given item for access to be granted.
1417                 If a property is a Link, the value must match the property
1418                 value. If a property is a Multilink, the value must appear
1419                 in the Multilink list.
1420             '''
1422         def addPermission(self, **propspec):
1423             ''' Create a new Permission with the properties defined in
1424                 'propspec'
1425             '''
1427         def addRole(self, **propspec):
1428             ''' Create a new Role with the properties defined in 'propspec'
1429             '''
1431         def addPermissionToRole(self, rolename, permission):
1432             ''' Add the permission to the role's permission list.
1434                 'rolename' is the name of the role to add permission to.
1435             '''
1437 Modules such as ``cgi/client.py`` and ``mailgw.py`` define their own
1438 permissions like so (this example is ``cgi/client.py``)::
1440     def initialiseSecurity(security):
1441         ''' Create some Permissions and Roles on the security object
1443             This function is directly invoked by security.Security.__init__()
1444             as a part of the Security object instantiation.
1445         '''
1446         p = security.addPermission(name="Web Registration",
1447             description="Anonymous users may register through the web")
1448         security.addToRole('Anonymous', p)
1450 Detectors may also define roles in their init() function::
1452     def init(db):
1453         # register an auditor that checks that a user has the "May Resolve"
1454         # Permission before allowing them to set an issue status to "resolved"
1455         db.issue.audit('set', checkresolvedok)
1456         p = db.security.addPermission(name="May Resolve", klass="issue")
1457         security.addToRole('Manager', p)
1459 The tracker dbinit module then has in ``open()``::
1461     # open the database - it must be modified to init the Security class
1462     # from security.py as db.security
1463     db = Database(config, name)
1465     # add some extra permissions and associate them with roles
1466     ei = db.security.addPermission(name="Edit", klass="issue",
1467                     description="User is allowed to edit issues")
1468     db.security.addPermissionToRole('User', ei)
1469     ai = db.security.addPermission(name="View", klass="issue",
1470                     description="User is allowed to access issues")
1471     db.security.addPermissionToRole('User', ai)
1473 In the dbinit ``init()``::
1475     # create the two default users
1476     user.create(username="admin", password=Password(adminpw),
1477                 address=config.ADMIN_EMAIL, roles='Admin')
1478     user.create(username="anonymous", roles='Anonymous')
1480 Then in the code that matters, calls to ``hasPermission`` and
1481 ``hasItemPermission`` are made to determine if the user has permission
1482 to perform some action::
1484     if db.security.hasPermission('issue', 'Edit', userid):
1485         # all ok
1487     if db.security.hasItemPermission('issue', itemid, assignedto=userid):
1488         # all ok
1490 Code in the core will make use of these methods, as should code in auditors in
1491 custom templates. The HTML templating may access the access controls through
1492 the *user* attribute of the *request* variable. It exposes a ``hasPermission()``
1493 method::
1495   tal:condition="python:request.user.hasPermission('Edit', 'issue')"
1497 or, if the *context* is *issue*, then the following is the same::
1499   tal:condition="python:request.user.hasPermission('Edit')"
1502 Authentication of Users
1503 ~~~~~~~~~~~~~~~~~~~~~~~
1505 Users must be authenticated correctly for the above controls to work. This is
1506 not done in the current mail gateway at all. Use of digital signing of
1507 messages could alleviate this problem.
1509 The exact mechanism of registering the digital signature should be flexible,
1510 with perhaps a level of trust. Users who supply their signature through their
1511 first message into the tracker should be at a lower level of trust to those
1512 who supply their signature to an admin for submission to their user details.
1515 Anonymous Users
1516 ~~~~~~~~~~~~~~~
1518 The "anonymous" user must always exist, and defines the access permissions for
1519 anonymous users. Unknown users accessing Roundup through the web or email
1520 interfaces will be logged in as the "anonymous" user.
1523 Use Cases
1524 ~~~~~~~~~
1526 public - end users can submit bugs, request new features, request support
1527     The Users would be given the default "User" Role which gives "View" and
1528     "Edit" Permission to the "issue" class.
1529 developer - developers can fix bugs, implement new features, provide support
1530     A new Role "Developer" is created with the Permission "Fixer" which is
1531     checked for in custom auditors that see whether the issue is being
1532     resolved with a particular resolution ("fixed", "implemented",
1533     "supported") and allows that resolution only if the permission is
1534     available.
1535 manager - approvers/managers can approve new features and signoff bug fixes
1536     A new Role "Manager" is created with the Permission "Signoff" which is
1537     checked for in custom auditors that see whether the issue status is being
1538     changed similar to the developer example.
1539 admin - administrators can add users and set user's roles
1540     The existing Role "Admin" has the Permissions "Edit" for all classes
1541     (including "user") and "Web Roles" which allow the desired actions.
1542 system - automated request handlers running various report/escalation scripts
1543     A combination of existing and new Roles, Permissions and auditors could
1544     be used here.
1545 privacy - issues that are only visible to some users
1546     A new property is added to the issue which marks the user or group of
1547     users who are allowed to view and edit the issue. An auditor will check
1548     for edit access, and the template user object can check for view access.
1551 Deployment Scenarios
1552 --------------------
1554 The design described above should be general enough
1555 to permit the use of Roundup for bug tracking, managing
1556 projects, managing patches, or holding discussions.  By
1557 using items of multiple types, one could deploy a system
1558 that maintains requirement specifications, catalogs bugs,
1559 and manages submitted patches, where patches could be
1560 linked to the bugs and requirements they address.
1563 Acknowledgements
1564 ----------------
1566 My thanks are due to Christy Heyl for 
1567 reviewing and contributing suggestions to this paper
1568 and motivating me to get it done, and to
1569 Jesse Vincent, Mark Miller, Christopher Simons,
1570 Jeff Dunmall, Wayne Gramlich, and Dean Tribble for
1571 their assistance with the first-round submission.
1573 Changes to this document
1574 ------------------------
1576 - Added Boolean and Number types
1577 - Added section Hyperdatabase Implementations
1578 - "Item" has been renamed to "Issue" to account for the more specific nature
1579   of the Class.
1580 - New Templating
1581 - Access Controls
1583 ------------------
1585 Back to `Table of Contents`_
1587 .. _`Table of Contents`: index.html
1588 .. _customisation: customizing.html