6b00926da32edee9b56d627b1b74265a4a0057f1
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 of the Roundup
15 system and specifies their interfaces and behaviour in sufficient detail
16 to guide an implementation. For the philosophy and rationale behind the
17 Roundup design, see the first-round Software Carpentry submission for
18 Roundup. This document fleshes out that design as well as specifying
19 interfaces so that the components can be developed separately.
22 The Layer Cake
23 -----------------
25 Lots of software design documents come with a picture of a cake.
26 Everybody seems to like them. I also like cakes (i think they are
27 tasty). So I, too, shall include a picture of a cake here::
29 ________________________________________________________________
30 | E-mail Client | Web Browser | Detector Scripts | Shell |
31 |---------------+---------------+--------------------+-----------|
32 | E-mail User | Web User | Detector | Command |
33 |----------------------------------------------------------------|
34 | Roundup Database Layer |
35 |----------------------------------------------------------------|
36 | Hyperdatabase Layer |
37 |----------------------------------------------------------------|
38 | Storage Layer |
39 ----------------------------------------------------------------
41 The colourful parts of the cake are part of our system; the faint grey
42 parts of the cake are external components.
44 I will now proceed to forgo all table manners and eat from the bottom of
45 the cake to the top. You may want to stand back a bit so you don't get
46 covered in crumbs.
49 Hyperdatabase
50 -------------
52 The lowest-level component to be implemented is the hyperdatabase. The
53 hyperdatabase is intended to be a flexible data store that can hold
54 configurable data in records which we call items.
56 The hyperdatabase is implemented on top of the storage layer, an
57 external module for storing its data. The storage layer could be a
58 third-party RDBMS; for a "batteries-included" distribution, implementing
59 the hyperdatabase on the standard bsddb module is suggested.
61 Dates and Date Arithmetic
62 ~~~~~~~~~~~~~~~~~~~~~~~~~
64 Before we get into the hyperdatabase itself, we need a way of handling
65 dates. The hyperdatabase module provides Timestamp objects for
66 representing date-and-time stamps and Interval objects for representing
67 date-and-time intervals.
69 As strings, date-and-time stamps are specified with the date in
70 international standard format (``yyyy-mm-dd``) joined to the time
71 (``hh:mm:ss``) by a period "``.``". Dates in this form can be easily
72 compared and are fairly readable when printed. An example of a valid
73 stamp is "``2000-06-24.13:03:59``". We'll call this the "full date
74 format". When Timestamp objects are printed as strings, they appear in
75 the full date format with the time always given in GMT. The full date
76 format is always exactly 19 characters long.
78 For user input, some partial forms are also permitted: the whole time or
79 just the seconds may be omitted; and the whole date may be omitted or
80 just the year may be omitted. If the time is given, the time is
81 interpreted in the user's local time zone. The Date constructor takes
82 care of these conversions. In the following examples, suppose that
83 ``yyyy`` is the current year, ``mm`` is the current month, and ``dd`` is
84 the current day of the month; and suppose that the user is on Eastern
85 Standard Time.
87 - "2000-04-17" means <Date 2000-04-17.00:00:00>
88 - "01-25" means <Date yyyy-01-25.00:00:00>
89 - "2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
90 - "08-13.22:13" means <Date yyyy-08-14.03:13:00>
91 - "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
92 - "14:25" means
93 - <Date yyyy-mm-dd.19:25:00>
94 - "8:47:11" means
95 - <Date yyyy-mm-dd.13:47:11>
96 - the special date "." means "right now"
99 Date intervals are specified using the suffixes "y", "m", and "d". The
100 suffix "w" (for "week") means 7 days. Time intervals are specified in
101 hh:mm:ss format (the seconds may be omitted, but the hours and minutes
102 may not).
104 - "3y" means three years
105 - "2y 1m" means two years and one month
106 - "1m 25d" means one month and 25 days
107 - "2w 3d" means two weeks and three days
108 - "1d 2:50" means one day, two hours, and 50 minutes
109 - "14:00" means 14 hours
110 - "0:04:33" means four minutes and 33 seconds
113 The Date class should understand simple date expressions of the form
114 *stamp* ``+`` *interval* and *stamp* ``-`` *interval*. When adding or
115 subtracting intervals involving months or years, the components are
116 handled separately. For example, when evaluating "``2000-06-25 + 1m
117 10d``", we first add one month to get 2000-07-25, then add 10 days to
118 get 2000-08-04 (rather than trying to decide whether 1m 10d means 38 or
119 40 or 41 days).
121 Here is an outline of the Date and Interval classes::
123 class Date:
124 def __init__(self, spec, offset):
125 """Construct a date given a specification and a time zone
126 offset.
128 'spec' is a full date or a partial form, with an optional
129 added or subtracted interval. 'offset' is the local time
130 zone offset from GMT in hours.
131 """
133 def __add__(self, interval):
134 """Add an interval to this date to produce another date."""
136 def __sub__(self, interval):
137 """Subtract an interval from this date to produce another
138 date.
139 """
141 def __cmp__(self, other):
142 """Compare this date to another date."""
144 def __str__(self):
145 """Return this date as a string in the yyyy-mm-dd.hh:mm:ss
146 format.
147 """
149 def local(self, offset):
150 """Return this date as yyyy-mm-dd.hh:mm:ss in a local time
151 zone.
152 """
154 class Interval:
155 def __init__(self, spec):
156 """Construct an interval given a specification."""
158 def __cmp__(self, other):
159 """Compare this interval to another interval."""
161 def __str__(self):
162 """Return this interval as a string."""
166 Here are some examples of how these classes would behave in practice.
167 For the following examples, assume that we are on Eastern Standard Time
168 and the current local time is 19:34:02 on 25 June 2000::
170 >>> Date(".")
171 <Date 2000-06-26.00:34:02>
172 >>> _.local(-5)
173 "2000-06-25.19:34:02"
174 >>> Date(". + 2d")
175 <Date 2000-06-28.00:34:02>
176 >>> Date("1997-04-17", -5)
177 <Date 1997-04-17.00:00:00>
178 >>> Date("01-25", -5)
179 <Date 2000-01-25.00:00:00>
180 >>> Date("08-13.22:13", -5)
181 <Date 2000-08-14.03:13:00>
182 >>> Date("14:25", -5)
183 <Date 2000-06-25.19:25:00>
184 >>> Interval(" 3w 1 d 2:00")
185 <Interval 22d 2:00>
186 >>> Date(". + 2d") - Interval("3w")
187 <Date 2000-06-07.00:34:02>
190 Items and Classes
191 ~~~~~~~~~~~~~~~~~
193 Items contain data in properties. To Python, these properties are
194 presented as the key-value pairs of a dictionary. Each item belongs to a
195 class which defines the names and types of its properties. The database
196 permits the creation and modification of classes as well as items.
199 Identifiers and Designators
200 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
202 Each item has a numeric identifier which is unique among items in its
203 class. The items are numbered sequentially within each class in order
204 of creation, starting from 1. The designator for an item is a way to
205 identify an item in the database, and consists of the name of the item's
206 class concatenated with the item's numeric identifier.
208 For example, if "spam" and "eggs" are classes, the first item created in
209 class "spam" has id 1 and designator "spam1". The first item created in
210 class "eggs" also has id 1 but has the distinct designator "eggs1". Item
211 designators are conventionally enclosed in square brackets when
212 mentioned in plain text. This permits a casual mention of, say,
213 "[patch37]" in an e-mail message to be turned into an active hyperlink.
216 Property Names and Types
217 ~~~~~~~~~~~~~~~~~~~~~~~~
219 Property names must begin with a letter.
221 A property may be one of five basic types:
223 - String properties are for storing arbitrary-length strings.
225 - Boolean properties are for storing true/false, or yes/no values.
227 - Number properties are for storing numeric values.
229 - Date properties store date-and-time stamps. Their values are Timestamp
230 objects.
232 - A Link property refers to a single other item selected from a
233 specified class. The class is part of the property; the value is an
234 integer, the id of the chosen item.
236 - A Multilink property refers to possibly many items in a specified
237 class. The value is a list of integers.
239 *None* is also a permitted value for any of these property types. An
240 attempt to store None into a Multilink property stores an empty list.
242 A property that is not specified will return as None from a *get*
243 operation.
246 Hyperdb Interface Specification
247 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
249 TODO: replace the Interface Specifications with links to the pydoc
251 The hyperdb module provides property objects to designate the different
252 kinds of properties. These objects are used when specifying what
253 properties belong in classes::
255 class String:
256 def __init__(self, indexme='no'):
257 """An object designating a String property."""
259 class Boolean:
260 def __init__(self):
261 """An object designating a Boolean property."""
263 class Number:
264 def __init__(self):
265 """An object designating a Number property."""
267 class Date:
268 def __init__(self):
269 """An object designating a Date property."""
271 class Link:
272 def __init__(self, classname, do_journal='yes'):
273 """An object designating a Link property that links to
274 items in a specified class.
276 If the do_journal argument is not 'yes' then changes to
277 the property are not journalled in the linked item.
278 """
280 class Multilink:
281 def __init__(self, classname, do_journal='yes'):
282 """An object designating a Multilink property that links
283 to items in a specified class.
285 If the do_journal argument is not 'yes' then changes to
286 the property are not journalled in the linked item(s).
287 """
290 Here is the interface provided by the hyperdatabase::
292 class Database:
293 """A database for storing records containing flexible data
294 types.
295 """
297 def __init__(self, config, journaltag=None):
298 """Open a hyperdatabase given a specifier to some storage.
300 The 'storagelocator' is obtained from config.DATABASE. The
301 meaning of 'storagelocator' depends on the particular
302 implementation of the hyperdatabase. It could be a file
303 name, a directory path, a socket descriptor for a connection
304 to a database over the network, etc.
306 The 'journaltag' is a token that will be attached to the
307 journal entries for any edits done on the database. If
308 'journaltag' is None, the database is opened in read-only
309 mode: the Class.create(), Class.set(), Class.retire(), and
310 Class.restore() methods are disabled.
311 """
313 def __getattr__(self, classname):
314 """A convenient way of calling self.getclass(classname)."""
316 def getclasses(self):
317 """Return a list of the names of all existing classes."""
319 def getclass(self, classname):
320 """Get the Class object representing a particular class.
322 If 'classname' is not a valid class name, a KeyError is
323 raised.
324 """
326 class Class:
327 """The handle to a particular class of items in a hyperdatabase.
328 """
330 def __init__(self, db, classname, **properties):
331 """Create a new class with a given name and property
332 specification.
334 'classname' must not collide with the name of an existing
335 class, or a ValueError is raised. The keyword arguments in
336 'properties' must map names to property objects, or a
337 TypeError is raised.
339 A proxied reference to the database is available as the
340 'db' attribute on instances. For example, in
341 'IssueClass.send_message', the following is used to lookup
342 users, messages and files::
344 users = self.db.user
345 messages = self.db.msg
346 files = self.db.file
347 """
349 # Editing items:
351 def create(self, **propvalues):
352 """Create a new item of this class and return its id.
354 The keyword arguments in 'propvalues' map property names to
355 values. The values of arguments must be acceptable for the
356 types of their corresponding properties or a TypeError is
357 raised. If this class has a key property, it must be
358 present and its value must not collide with other key
359 strings or a ValueError is raised. Any other properties on
360 this class that are missing from the 'propvalues' dictionary
361 are set to None. If an id in a link or multilink property
362 does not refer to a valid item, an IndexError is raised.
363 """
365 def get(self, itemid, propname):
366 """Get the value of a property on an existing item of this
367 class.
369 'itemid' must be the id of an existing item of this class or
370 an IndexError is raised. 'propname' must be the name of a
371 property of this class or a KeyError is raised.
372 """
374 def set(self, itemid, **propvalues):
375 """Modify a property on an existing item of this class.
377 'itemid' must be the id of an existing item of this class or
378 an IndexError is raised. Each key in 'propvalues' must be
379 the name of a property of this class or a KeyError is
380 raised. All values in 'propvalues' must be acceptable types
381 for their corresponding properties or a TypeError is raised.
382 If the value of the key property is set, it must not collide
383 with other key strings or a ValueError is raised. If the
384 value of a Link or Multilink property contains an invalid
385 item id, a ValueError is raised.
386 """
388 def retire(self, itemid):
389 """Retire an item.
391 The properties on the item remain available from the get()
392 method, and the item's id is never reused. Retired items
393 are not returned by the find(), list(), or lookup() methods,
394 and other items may reuse the values of their key
395 properties.
396 """
398 def restore(self, nodeid):
399 '''Restore a retired node.
401 Make node available for all operations like it was before
402 retirement.
403 '''
405 def history(self, itemid):
406 """Retrieve the journal of edits on a particular item.
408 'itemid' must be the id of an existing item of this class or
409 an IndexError is raised.
411 The returned list contains tuples of the form
413 (date, tag, action, params)
415 'date' is a Timestamp object specifying the time of the
416 change and 'tag' is the journaltag specified when the
417 database was opened. 'action' may be:
419 'create' or 'set' -- 'params' is a dictionary of
420 property values
421 'link' or 'unlink' -- 'params' is (classname, itemid,
422 propname)
423 'retire' -- 'params' is None
424 """
426 # Locating items:
428 def setkey(self, propname):
429 """Select a String property of this class to be the key
430 property.
432 'propname' must be the name of a String property of this
433 class or None, or a TypeError is raised. The values of the
434 key property on all existing items must be unique or a
435 ValueError is raised.
436 """
438 def getkey(self):
439 """Return the name of the key property for this class or
440 None.
441 """
443 def lookup(self, keyvalue):
444 """Locate a particular item by its key property and return
445 its id.
447 If this class has no key property, a TypeError is raised.
448 If the 'keyvalue' matches one of the values for the key
449 property among the items in this class, the matching item's
450 id is returned; otherwise a KeyError is raised.
451 """
453 def find(self, propname, itemid):
454 """Get the ids of items in this class which link to the
455 given items.
457 'propspec' consists of keyword args propname={itemid:1,}
458 'propname' must be the name of a property in this class, or
459 a KeyError is raised. That property must be a Link or
460 Multilink property, or a TypeError is raised.
462 Any item in this class whose 'propname' property links to
463 any of the itemids will be returned. Used by the full text
464 indexing, which knows that "foo" occurs in msg1, msg3 and
465 file7, so we have hits on these issues:
467 db.issue.find(messages={'1':1,'3':1}, files={'7':1})
468 """
470 def filter(self, search_matches, filterspec, sort, group):
471 """ Return a list of the ids of the active items in this
472 class that match the 'filter' spec, sorted by the group spec
473 and then the sort spec.
474 """
476 def list(self):
477 """Return a list of the ids of the active items in this
478 class.
479 """
481 def count(self):
482 """Get the number of items in this class.
484 If the returned integer is 'numitems', the ids of all the
485 items in this class run from 1 to numitems, and numitems+1
486 will be the id of the next item to be created in this class.
487 """
489 # Manipulating properties:
491 def getprops(self):
492 """Return a dictionary mapping property names to property
493 objects.
494 """
496 def addprop(self, **properties):
497 """Add properties to this class.
499 The keyword arguments in 'properties' must map names to
500 property objects, or a TypeError is raised. None of the
501 keys in 'properties' may collide with the names of existing
502 properties, or a ValueError is raised before any properties
503 have been added.
504 """
506 def getitem(self, itemid, cache=1):
507 """ Return a Item convenience wrapper for the item.
509 'itemid' must be the id of an existing item of this class or
510 an IndexError is raised.
512 'cache' indicates whether the transaction cache should be
513 queried for the item. If the item has been modified and you
514 need to determine what its values prior to modification are,
515 you need to set cache=0.
516 """
518 class Item:
519 """ A convenience wrapper for the given item. It provides a
520 mapping interface to a single item's properties
521 """
523 Hyperdatabase Implementations
524 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
526 Hyperdatabase implementations exist to create the interface described in
527 the `hyperdb interface specification`_ over an existing storage
528 mechanism. Examples are relational databases, \*dbm key-value databases,
529 and so on.
531 Several implementations are provided - they belong in the
532 ``roundup.backends`` package.
535 Application Example
536 ~~~~~~~~~~~~~~~~~~~
538 Here is an example of how the hyperdatabase module would work in
539 practice::
541 >>> import hyperdb
542 >>> db = hyperdb.Database("foo.db", "ping")
543 >>> db
544 <hyperdb.Database "foo.db" opened by "ping">
545 >>> hyperdb.Class(db, "status", name=hyperdb.String())
546 <hyperdb.Class "status">
547 >>> _.setkey("name")
548 >>> db.status.create(name="unread")
549 1
550 >>> db.status.create(name="in-progress")
551 2
552 >>> db.status.create(name="testing")
553 3
554 >>> db.status.create(name="resolved")
555 4
556 >>> db.status.count()
557 4
558 >>> db.status.list()
559 [1, 2, 3, 4]
560 >>> db.status.lookup("in-progress")
561 2
562 >>> db.status.retire(3)
563 >>> db.status.list()
564 [1, 2, 4]
565 >>> hyperdb.Class(db, "issue", title=hyperdb.String(), status=hyperdb.Link("status"))
566 <hyperdb.Class "issue">
567 >>> db.issue.create(title="spam", status=1)
568 1
569 >>> db.issue.create(title="eggs", status=2)
570 2
571 >>> db.issue.create(title="ham", status=4)
572 3
573 >>> db.issue.create(title="arguments", status=2)
574 4
575 >>> db.issue.create(title="abuse", status=1)
576 5
577 >>> hyperdb.Class(db, "user", username=hyperdb.Key(),
578 ... password=hyperdb.String())
579 <hyperdb.Class "user">
580 >>> db.issue.addprop(fixer=hyperdb.Link("user"))
581 >>> db.issue.getprops()
582 {"title": <hyperdb.String>, "status": <hyperdb.Link to "status">,
583 "user": <hyperdb.Link to "user">}
584 >>> db.issue.set(5, status=2)
585 >>> db.issue.get(5, "status")
586 2
587 >>> db.status.get(2, "name")
588 "in-progress"
589 >>> db.issue.get(5, "title")
590 "abuse"
591 >>> db.issue.find("status", db.status.lookup("in-progress"))
592 [2, 4, 5]
593 >>> db.issue.history(5)
594 [(<Date 2000-06-28.19:09:43>, "ping", "create", {"title": "abuse",
595 "status": 1}),
596 (<Date 2000-06-28.19:11:04>, "ping", "set", {"status": 2})]
597 >>> db.status.history(1)
598 [(<Date 2000-06-28.19:09:43>, "ping", "link", ("issue", 5, "status")),
599 (<Date 2000-06-28.19:11:04>, "ping", "unlink", ("issue", 5, "status"))]
600 >>> db.status.history(2)
601 [(<Date 2000-06-28.19:11:04>, "ping", "link", ("issue", 5, "status"))]
604 For the purposes of journalling, when a Multilink property is set to a
605 new list of items, the hyperdatabase compares the old list to the new
606 list. The journal records "unlink" events for all the items that appear
607 in the old list but not the new list, and "link" events for all the
608 items that appear in the new list but not in the old list.
611 Roundup Database
612 ----------------
614 The Roundup database layer is implemented on top of the hyperdatabase
615 and mediates calls to the database. Some of the classes in the Roundup
616 database are considered issue classes. The Roundup database layer adds
617 detectors and user items, and on issues it provides mail spools, nosy
618 lists, and superseders.
621 Reserved Classes
622 ~~~~~~~~~~~~~~~~
624 Internal to this layer we reserve three special classes of items that
625 are not issues.
627 Users
628 """""
630 Users are stored in the hyperdatabase as items of class "user". The
631 "user" class has the definition::
633 hyperdb.Class(db, "user", username=hyperdb.String(),
634 password=hyperdb.String(),
635 address=hyperdb.String())
636 db.user.setkey("username")
638 Messages
639 """"""""
641 E-mail messages are represented by hyperdatabase items of class "msg".
642 The actual text content of the messages is stored in separate files.
643 (There's no advantage to be gained by stuffing them into the
644 hyperdatabase, and if messages are stored in ordinary text files, they
645 can be grepped from the command line.) The text of a message is saved
646 in a file named after the message item designator (e.g. "msg23") for the
647 sake of the command interface (see below). Attachments are stored
648 separately and associated with "file" items. The "msg" class has the
649 definition::
651 hyperdb.Class(db, "msg", author=hyperdb.Link("user"),
652 recipients=hyperdb.Multilink("user"),
653 date=hyperdb.Date(),
654 summary=hyperdb.String(),
655 files=hyperdb.Multilink("file"))
657 The "author" property indicates the author of the message (a "user" item
658 must exist in the hyperdatabase for any messages that are stored in the
659 system). The "summary" property contains a summary of the message for
660 display in a message index.
663 Files
664 """""
666 Submitted files are represented by hyperdatabase items of class "file".
667 Like e-mail messages, the file content is stored in files outside the
668 database, named after the file item designator (e.g. "file17"). The
669 "file" class has the definition::
671 hyperdb.Class(db, "file", user=hyperdb.Link("user"),
672 name=hyperdb.String(),
673 type=hyperdb.String())
675 The "user" property indicates the user who submitted the file, the
676 "name" property holds the original name of the file, and the "type"
677 property holds the MIME type of the file as received.
680 Issue Classes
681 ~~~~~~~~~~~~~
683 All issues have the following standard properties:
685 =========== ==========================
686 Property Definition
687 =========== ==========================
688 title hyperdb.String()
689 messages hyperdb.Multilink("msg")
690 files hyperdb.Multilink("file")
691 nosy hyperdb.Multilink("user")
692 superseder hyperdb.Multilink("issue")
693 =========== ==========================
695 Also, two Date properties named "creation" and "activity" are fabricated
696 by the Roundup database layer. By "fabricated" we mean that no such
697 properties are actually stored in the hyperdatabase, but when properties
698 on issues are requested, the "creation" and "activity" properties are
699 made available. The value of the "creation" property is the date when an
700 issue was created, and the value of the "activity" property is the date
701 when any property on the issue was last edited (equivalently, these are
702 the dates on the first and last records in the issue's journal).
705 Roundupdb Interface Specification
706 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
708 The interface to a Roundup database delegates most method calls to the
709 hyperdatabase, except for the following changes and additional methods::
711 class Database:
712 def getuid(self):
713 """Return the id of the "user" item associated with the user
714 that owns this connection to the hyperdatabase."""
716 class Class:
717 # Overridden methods:
719 def create(self, **propvalues):
720 def set(self, **propvalues):
721 def retire(self, itemid):
722 """These operations trigger detectors and can be vetoed.
723 Attempts to modify the "creation" or "activity" properties
724 cause a KeyError.
725 """
727 # New methods:
729 def audit(self, event, detector):
730 def react(self, event, detector):
731 """Register a detector (see below for more details)."""
733 class IssueClass(Class):
734 # Overridden methods:
736 def __init__(self, db, classname, **properties):
737 """The newly-created class automatically includes the
738 "messages", "files", "nosy", and "superseder" properties.
739 If the 'properties' dictionary attempts to specify any of
740 these properties or a "creation" or "activity" property, a
741 ValueError is raised."""
743 def get(self, itemid, propname):
744 def getprops(self):
745 """In addition to the actual properties on the item, these
746 methods provide the "creation" and "activity" properties."""
748 # New methods:
750 def addmessage(self, itemid, summary, text):
751 """Add a message to an issue's mail spool.
753 A new "msg" item is constructed using the current date, the
754 user that owns the database connection as the author, and
755 the specified summary text. The "files" and "recipients"
756 fields are left empty. The given text is saved as the body
757 of the message and the item is appended to the "messages"
758 field of the specified issue.
759 """
761 def nosymessage(self, itemid, msgid):
762 """Send a message to the members of an issue's nosy list.
764 The message is sent only to users on the nosy list who are
765 not already on the "recipients" list for the message. These
766 users are then added to the message's "recipients" list.
767 """
770 Default Schema
771 ~~~~~~~~~~~~~~
773 The default schema included with Roundup turns it into a typical
774 software bug tracker. The database is set up like this::
776 pri = Class(db, "priority", name=hyperdb.String(),
777 order=hyperdb.String())
778 pri.setkey("name")
779 pri.create(name="critical", order="1")
780 pri.create(name="urgent", order="2")
781 pri.create(name="bug", order="3")
782 pri.create(name="feature", order="4")
783 pri.create(name="wish", order="5")
785 stat = Class(db, "status", name=hyperdb.String(),
786 order=hyperdb.String())
787 stat.setkey("name")
788 stat.create(name="unread", order="1")
789 stat.create(name="deferred", order="2")
790 stat.create(name="chatting", order="3")
791 stat.create(name="need-eg", order="4")
792 stat.create(name="in-progress", order="5")
793 stat.create(name="testing", order="6")
794 stat.create(name="done-cbb", order="7")
795 stat.create(name="resolved", order="8")
797 Class(db, "keyword", name=hyperdb.String())
799 Class(db, "issue", fixer=hyperdb.Multilink("user"),
800 topic=hyperdb.Multilink("keyword"),
801 priority=hyperdb.Link("priority"),
802 status=hyperdb.Link("status"))
804 (The "order" property hasn't been explained yet. It gets used by the
805 Web user interface for sorting.)
807 The above isn't as pretty-looking as the schema specification in the
808 first-stage submission, but it could be made just as easy with the
809 addition of a convenience function like Choice for setting up the
810 "priority" and "status" classes::
812 def Choice(name, *options):
813 cl = Class(db, name, name=hyperdb.String(),
814 order=hyperdb.String())
815 for i in range(len(options)):
816 cl.create(name=option[i], order=i)
817 return hyperdb.Link(name)
820 Detector Interface
821 ------------------
823 Detectors are Python functions that are triggered on certain kinds of
824 events. The definitions of the functions live in Python modules placed
825 in a directory set aside for this purpose. Importing the Roundup
826 database module also imports all the modules in this directory, and the
827 ``init()`` function of each module is called when a database is opened
828 to provide it a chance to register its detectors.
830 There are two kinds of detectors:
832 1. an auditor is triggered just before modifying an item
833 2. a reactor is triggered just after an item has been modified
835 When the Roundup database is about to perform a ``create()``, ``set()``,
836 ``retire()``, or ``restore`` operation, it first calls any *auditors*
837 that have been registered for that operation on that class. Any auditor
838 may raise a *Reject* exception to abort the operation.
840 If none of the auditors raises an exception, the database proceeds to
841 carry out the operation. After it's done, it then calls all of the
842 *reactors* that have been registered for the operation.
845 Detector Interface Specification
846 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
848 The ``audit()`` and ``react()`` methods register detectors on a given
849 class of items::
851 class Class:
852 def audit(self, event, detector):
853 """Register an auditor on this class.
855 'event' should be one of "create", "set", "retire", or
856 "restore". 'detector' should be a function accepting four
857 arguments.
858 """
860 def react(self, event, detector):
861 """Register a reactor on this class.
863 'event' should be one of "create", "set", "retire", or
864 "restore". 'detector' should be a function accepting four
865 arguments.
866 """
868 Auditors are called with the arguments::
870 audit(db, cl, itemid, newdata)
872 where ``db`` is the database, ``cl`` is an instance of Class or
873 IssueClass within the database, and ``newdata`` is a dictionary mapping
874 property names to values.
876 For a ``create()`` operation, the ``itemid`` argument is None and
877 newdata contains all of the initial property values with which the item
878 is about to be created.
880 For a ``set()`` operation, newdata contains only the names and values of
881 properties that are about to be changed.
883 For a ``retire()`` or ``restore()`` operation, newdata is None.
885 Reactors are called with the arguments::
887 react(db, cl, itemid, olddata)
889 where ``db`` is the database, ``cl`` is an instance of Class or
890 IssueClass within the database, and ``olddata`` is a dictionary mapping
891 property names to values.
893 For a ``create()`` operation, the ``itemid`` argument is the id of the
894 newly-created item and ``olddata`` is None.
896 For a ``set()`` operation, ``olddata`` contains the names and previous
897 values of properties that were changed.
899 For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of
900 the retired or restored item and ``olddata`` is None.
903 Detector Example
904 ~~~~~~~~~~~~~~~~
906 Here is an example of detectors written for a hypothetical
907 project-management application, where users can signal approval of a
908 project by adding themselves to an "approvals" list, and a project
909 proceeds when it has three approvals::
911 # Permit users only to add themselves to the "approvals" list.
913 def check_approvals(db, cl, id, newdata):
914 if newdata.has_key("approvals"):
915 if cl.get(id, "status") == db.status.lookup("approved"):
916 raise Reject, "You can't modify the approvals list " \
917 "for a project that has already been approved."
918 old = cl.get(id, "approvals")
919 new = newdata["approvals"]
920 for uid in old:
921 if uid not in new and uid != db.getuid():
922 raise Reject, "You can't remove other users from " \
923 "the approvals list; you can only remove " \
924 "yourself."
925 for uid in new:
926 if uid not in old and uid != db.getuid():
927 raise Reject, "You can't add other users to the " \
928 "approvals list; you can only add yourself."
930 # When three people have approved a project, change its status from
931 # "pending" to "approved".
933 def approve_project(db, cl, id, olddata):
934 if (olddata.has_key("approvals") and
935 len(cl.get(id, "approvals")) == 3):
936 if cl.get(id, "status") == db.status.lookup("pending"):
937 cl.set(id, status=db.status.lookup("approved"))
939 def init(db):
940 db.project.audit("set", check_approval)
941 db.project.react("set", approve_project)
943 Here is another example of a detector that can allow or prevent the
944 creation of new items. In this scenario, patches for a software project
945 are submitted by sending in e-mail with an attached file, and we want to
946 ensure that there are text/plain attachments on the message. The
947 maintainer of the package can then apply the patch by setting its status
948 to "applied"::
950 # Only accept attempts to create new patches that come with patch
951 # files.
953 def check_new_patch(db, cl, id, newdata):
954 if not newdata["files"]:
955 raise Reject, "You can't submit a new patch without " \
956 "attaching a patch file."
957 for fileid in newdata["files"]:
958 if db.file.get(fileid, "type") != "text/plain":
959 raise Reject, "Submitted patch files must be " \
960 "text/plain."
962 # When the status is changed from "approved" to "applied", apply the
963 # patch.
965 def apply_patch(db, cl, id, olddata):
966 if (cl.get(id, "status") == db.status.lookup("applied") and
967 olddata["status"] == db.status.lookup("approved")):
968 # ...apply the patch...
970 def init(db):
971 db.patch.audit("create", check_new_patch)
972 db.patch.react("set", apply_patch)
975 Command Interface
976 -----------------
978 The command interface is a very simple and minimal interface, intended
979 only for quick searches and checks from the shell prompt. (Anything more
980 interesting can simply be written in Python using the Roundup database
981 module.)
984 Command Interface Specification
985 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
987 A single command, roundup, provides basic access to the hyperdatabase
988 from the command line::
990 roundup-admin help
991 roundup-admin get [-list] designator[, designator,...] propname
992 roundup-admin set designator[, designator,...] propname=value ...
993 roundup-admin find [-list] classname propname=value ...
995 See ``roundup-admin help commands`` for a complete list of commands.
997 Property values are represented as strings in command arguments and in
998 the printed results:
1000 - Strings are, well, strings.
1002 - Numbers are displayed the same as strings.
1004 - Booleans are displayed as 'Yes' or 'No'.
1006 - Date values are printed in the full date format in the local time
1007 zone, and accepted in the full format or any of the partial formats
1008 explained above.
1010 - Link values are printed as item designators. When given as an
1011 argument, item designators and key strings are both accepted.
1013 - Multilink values are printed as lists of item designators joined by
1014 commas. When given as an argument, item designators and key strings
1015 are both accepted; an empty string, a single item, or a list of items
1016 joined by commas is accepted.
1018 When multiple items are specified to the roundup get or roundup set
1019 commands, the specified properties are retrieved or set on all the
1020 listed items.
1022 When multiple results are returned by the roundup get or roundup find
1023 commands, they are printed one per line (default) or joined by commas
1024 (with the -list) option.
1027 Usage Example
1028 ~~~~~~~~~~~~~
1030 To find all messages regarding in-progress issues that contain the word
1031 "spam", for example, you could execute the following command from the
1032 directory where the database dumps its files::
1034 shell% for issue in `roundup find issue status=in-progress`; do
1035 > grep -l spam `roundup get $issue messages`
1036 > done
1037 msg23
1038 msg49
1039 msg50
1040 msg61
1041 shell%
1043 Or, using the -list option, this can be written as a single command::
1045 shell% grep -l spam `roundup get \
1046 \`roundup find -list issue status=in-progress\` messages`
1047 msg23
1048 msg49
1049 msg50
1050 msg61
1051 shell%
1054 E-mail User Interface
1055 ---------------------
1057 The Roundup system must be assigned an e-mail address at which to
1058 receive mail. Messages should be piped to the Roundup mail-handling
1059 script by the mail delivery system (e.g. using an alias beginning with
1060 "|" for sendmail).
1063 Message Processing
1064 ~~~~~~~~~~~~~~~~~~
1066 Incoming messages are examined for multiple parts. In a multipart/mixed
1067 message or part, each subpart is extracted and examined. In a
1068 multipart/alternative message or part, we look for a text/plain subpart
1069 and ignore the other parts. The text/plain subparts are assembled to
1070 form the textual body of the message, to be stored in the file
1071 associated with a "msg" class item. Any parts of other types are each
1072 stored in separate files and given "file" class items that are linked to
1073 the "msg" item.
1075 The "summary" property on message items is taken from the first
1076 non-quoting section in the message body. The message body is divided
1077 into sections by blank lines. Sections where the second and all
1078 subsequent lines begin with a ">" or "|" character are considered
1079 "quoting sections". The first line of the first non-quoting section
1080 becomes the summary of the message.
1082 All of the addresses in the To: and Cc: headers of the incoming message
1083 are looked up among the user items, and the corresponding users are
1084 placed in the "recipients" property on the new "msg" item. The address
1085 in the From: header similarly determines the "author" property of the
1086 new "msg" item. The default handling for addresses that don't have
1087 corresponding users is to create new users with no passwords and a
1088 username equal to the address. (The web interface does not permit
1089 logins for users with no passwords.) If we prefer to reject mail from
1090 outside sources, we can simply register an auditor on the "user" class
1091 that prevents the creation of user items with no passwords.
1093 The subject line of the incoming message is examined to determine
1094 whether the message is an attempt to create a new issue or to discuss an
1095 existing issue. A designator enclosed in square brackets is sought as
1096 the first thing on the subject line (after skipping any "Fwd:" or "Re:"
1097 prefixes).
1099 If an issue designator (class name and id number) is found there, the
1100 newly created "msg" item is added to the "messages" property for that
1101 issue, and any new "file" items are added to the "files" property for
1102 the issue.
1104 If just an issue class name is found there, we attempt to create a new
1105 issue of that class with its "messages" property initialized to contain
1106 the new "msg" item and its "files" property initialized to contain any
1107 new "file" items.
1109 Both cases may trigger detectors (in the first case we are calling the
1110 set() method to add the message to the issue's spool; in the second case
1111 we are calling the create() method to create a new item). If an auditor
1112 raises an exception, the original message is bounced back to the sender
1113 with the explanatory message given in the exception.
1116 Nosy Lists
1117 ~~~~~~~~~~
1119 A standard detector is provided that watches for additions to the
1120 "messages" property. When a new message is added, the detector sends it
1121 to all the users on the "nosy" list for the issue that are not already
1122 on the "recipients" list of the message. Those users are then appended
1123 to the "recipients" property on the message, so multiple copies of a
1124 message are never sent to the same user. The journal recorded by the
1125 hyperdatabase on the "recipients" property then provides a log of when
1126 the message was sent to whom.
1129 Setting Properties
1130 ~~~~~~~~~~~~~~~~~~
1132 The e-mail interface also provides a simple way to set properties on
1133 issues. At the end of the subject line, ``propname=value`` pairs can be
1134 specified in square brackets, using the same conventions as for the
1135 roundup ``set`` shell command.
1138 Web User Interface
1139 ------------------
1141 The web interface is provided by a CGI script that can be run under any
1142 web server. A simple web server can easily be built on the standard
1143 CGIHTTPServer module, and should also be included in the distribution
1144 for quick out-of-the-box deployment.
1146 The user interface is constructed from a number of template files
1147 containing mostly HTML. Among the HTML tags in templates are
1148 interspersed some nonstandard tags, which we use as placeholders to be
1149 replaced by properties and their values.
1152 Views and View Specifiers
1153 ~~~~~~~~~~~~~~~~~~~~~~~~~
1155 There are two main kinds of views: *index* views and *issue* views. An
1156 index view displays a list of issues of a particular class, optionally
1157 sorted and filtered as requested. An issue view presents the properties
1158 of a particular issue for editing and displays the message spool for the
1159 issue.
1161 A view specifier is a string that specifies all the options needed to
1162 construct a particular view. It goes after the URL to the Roundup CGI
1163 script or the web server to form the complete URL to a view. When the
1164 result of selecting a link or submitting a form takes the user to a new
1165 view, the Web browser should be redirected to a canonical location
1166 containing a complete view specifier so that the view can be bookmarked.
1169 Displaying Properties
1170 ~~~~~~~~~~~~~~~~~~~~~
1172 Properties appear in the user interface in three contexts: in indices,
1173 in editors, and as search filters. For each type of property, there are
1174 several display possibilities. For example, in an index view, a string
1175 property may just be printed as a plain string, but in an editor view,
1176 that property should be displayed in an editable field.
1178 The display of a property is handled by functions in the
1179 ``cgi.templating`` module.
1181 Displayer functions are triggered by ``tal:content`` or ``tal:replace``
1182 tag attributes in templates. The value of the attribute provides an
1183 expression for calling the displayer function. For example, the
1184 occurrence of::
1186 tal:content="context/status/plain"
1188 in a template triggers a call to::
1190 context['status'].plain()
1192 where the context would be an item of the "issue" class. The displayer
1193 functions can accept extra arguments to further specify details about
1194 the widgets that should be generated.
1196 Some of the standard displayer functions include:
1198 ========= ==============================================================
1199 Function Description
1200 ========= ==============================================================
1201 plain display a String property directly;
1202 display a Date property in a specified time zone with an
1203 option to omit the time from the date stamp; for a Link or
1204 Multilink property, display the key strings of the linked
1205 items (or the ids if the linked class has no key property)
1206 field display a property like the plain displayer above, but in a
1207 text field to be edited
1208 menu for a Link property, display a menu of the available choices
1209 ========= ==============================================================
1211 See the `customisation`_ documentation for the complete list.
1214 Index Views
1215 ~~~~~~~~~~~
1217 An index view contains two sections: a filter section and an index
1218 section. The filter section provides some widgets for selecting which
1219 issues appear in the index. The index section is a table of issues.
1222 Index View Specifiers
1223 """""""""""""""""""""
1225 An index view specifier looks like this (whitespace has been added for
1226 clarity)::
1228 /issue?status=unread,in-progress,resolved&
1229 topic=security,ui&
1230 :group=priority&
1231 :sort=-activity&
1232 :filters=status,topic&
1233 :columns=title,status,fixer
1236 The index view is determined by two parts of the specifier: the layout
1237 part and the filter part. The layout part consists of the query
1238 parameters that begin with colons, and it determines the way that the
1239 properties of selected items are displayed. The filter part consists of
1240 all the other query parameters, and it determines the criteria by which
1241 items are selected for display.
1243 The filter part is interactively manipulated with the form widgets
1244 displayed in the filter section. The layout part is interactively
1245 manipulated by clicking on the column headings in the table.
1247 The filter part selects the union of the sets of issues with values
1248 matching any specified Link properties and the intersection of the sets
1249 of issues with values matching any specified Multilink properties.
1251 The example specifies an index of "issue" items. Only issues with a
1252 "status" of either "unread" or "in-progres" or "resolved" are displayed,
1253 and only issues with "topic" values including both "security" and "ui"
1254 are displayed. The issues are grouped by priority, arranged in
1255 ascending order; and within groups, sorted by activity, arranged in
1256 descending order. The filter section shows filters for the "status" and
1257 "topic" properties, and the table includes columns for the "title",
1258 "status", and "fixer" properties.
1260 Associated with each issue class is a default layout specifier. The
1261 layout specifier in the above example is the default layout to be
1262 provided with the default bug-tracker schema described above in section
1263 4.4.
1265 Index Section
1266 """""""""""""
1268 The template for an index section describes one row of the index table.
1269 Fragments protected by a ``tal:condition="request/show/<property>"`` are
1270 included or omitted depending on whether the view specifier requests a
1271 column for a particular property. The table cells are filled by the
1272 ``tal:content="context/<property>"`` directive, which displays the value
1273 of the property.
1275 Here's a simple example of an index template::
1277 <tr>
1278 <td tal:condition="request/show/title"
1279 tal:content="contex/title"></td>
1280 <td tal:condition="request/show/status"
1281 tal:content="contex/status"></td>
1282 <td tal:condition="request/show/fixer"
1283 tal:content="contex/fixer"></td>
1284 </tr>
1286 Sorting
1287 """""""
1289 String and Date values are sorted in the natural way. Link properties
1290 are sorted according to the value of the "order" property on the linked
1291 items if it is present; or otherwise on the key string of the linked
1292 items; or finally on the item ids. Multilink properties are sorted
1293 according to how many links are present.
1295 Issue Views
1296 ~~~~~~~~~~~
1298 An issue view contains an editor section and a spool section. At the top
1299 of an issue view, links to superseding and superseded issues are always
1300 displayed.
1302 Issue View Specifiers
1303 """""""""""""""""""""
1305 An issue view specifier is simply the issue's designator::
1307 /patch23
1310 Editor Section
1311 """"""""""""""
1313 The editor section is generated from a template containing
1314 ``tal:content="context/<property>/<widget>"`` directives to insert the
1315 appropriate widgets for editing properties.
1317 Here's an example of a basic editor template::
1319 <table>
1320 <tr>
1321 <td colspan=2
1322 tal:content="python:context.title.field(size='60')"></td>
1323 </tr>
1324 <tr>
1325 <td tal:content="context/fixer/field"></td>
1326 <td tal:content="context/status/menu"></td>
1327 </tr>
1328 <tr>
1329 <td tal:content="context/nosy/field"></td>
1330 <td tal:content="context/priority/menu"></td>
1331 </tr>
1332 <tr>
1333 <td colspan=2>
1334 <textarea name=":note" rows=5 cols=60></textarea>
1335 </td>
1336 </tr>
1337 </table>
1339 As shown in the example, the editor template can also include a ":note"
1340 field, which is a text area for entering a note to go along with a
1341 change.
1343 When a change is submitted, the system automatically generates a message
1344 describing the changed properties. The message displays all of the
1345 property values on the issue and indicates which ones have changed. An
1346 example of such a message might be this::
1348 title: Polly Parrot is dead
1349 priority: critical
1350 status: unread -> in-progress
1351 fixer: (none)
1352 keywords: parrot,plumage,perch,nailed,dead
1354 If a note is given in the ":note" field, the note is appended to the
1355 description. The message is then added to the issue's message spool
1356 (thus triggering the standard detector to react by sending out this
1357 message to the nosy list).
1360 Spool Section
1361 """""""""""""
1363 The spool section lists messages in the issue's "messages" property.
1364 The index of messages displays the "date", "author", and "summary"
1365 properties on the message items, and selecting a message takes you to
1366 its content.
1368 Access Control
1369 --------------
1371 At each point that requires an action to be performed, the security
1372 mechanisms are asked if the current user has permission. This permission
1373 is defined as a Permission.
1375 Individual assignment of Permission to user is unwieldy. The concept of
1376 a Role, which encompasses several Permissions and may be assigned to
1377 many Users, is quite well developed in many projects. Roundup will take
1378 this path, and allow the multiple assignment of Roles to Users, and
1379 multiple Permissions to Roles. These definitions are not persistent -
1380 they're defined when the application initialises.
1382 There will be two levels of Permission. The Class level permissions
1383 define logical permissions associated with all items of a particular
1384 class (or all classes). The Item level permissions define logical
1385 permissions associated with specific items by way of their user-linked
1386 properties.
1389 Access Control Interface Specification
1390 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1392 The security module defines::
1394 class Permission:
1395 ''' Defines a Permission with the attributes
1396 - name
1397 - description
1398 - klass (optional)
1400 The klass may be unset, indicating that this permission is
1401 not locked to a particular hyperdb class. There may be
1402 multiple Permissions for the same name for different
1403 classes.
1404 '''
1406 class Role:
1407 ''' Defines a Role with the attributes
1408 - name
1409 - description
1410 - permissions
1411 '''
1413 class Security:
1414 def __init__(self, db):
1415 ''' Initialise the permission and role stores, and add in
1416 the base roles (for admin user).
1417 '''
1419 def getPermission(self, permission, classname=None):
1420 ''' Find the Permission matching the name and for the class,
1421 if the classname is specified.
1423 Raise ValueError if there is no exact match.
1424 '''
1426 def hasPermission(self, permission, userid, classname=None):
1427 ''' Look through all the Roles, and hence Permissions, and
1428 see if "permission" is there for the specified
1429 classname.
1430 '''
1432 def hasItemPermission(self, classname, itemid, **propspec):
1433 ''' Check the named properties of the given item to see if
1434 the userid appears in them. If it does, then the user is
1435 granted this permission check.
1437 'propspec' consists of a set of properties and values
1438 that must be present on the given item for access to be
1439 granted.
1441 If a property is a Link, the value must match the
1442 property value. If a property is a Multilink, the value
1443 must appear in the Multilink list.
1444 '''
1446 def addPermission(self, **propspec):
1447 ''' Create a new Permission with the properties defined in
1448 'propspec'
1449 '''
1451 def addRole(self, **propspec):
1452 ''' Create a new Role with the properties defined in
1453 'propspec'
1454 '''
1456 def addPermissionToRole(self, rolename, permission):
1457 ''' Add the permission to the role's permission list.
1459 'rolename' is the name of the role to add permission to.
1460 '''
1462 Modules such as ``cgi/client.py`` and ``mailgw.py`` define their own
1463 permissions like so (this example is ``cgi/client.py``)::
1465 def initialiseSecurity(security):
1466 ''' Create some Permissions and Roles on the security object
1468 This function is directly invoked by
1469 security.Security.__init__() as a part of the Security
1470 object instantiation.
1471 '''
1472 p = security.addPermission(name="Web Registration",
1473 description="Anonymous users may register through the web")
1474 security.addToRole('Anonymous', p)
1476 Detectors may also define roles in their init() function::
1478 def init(db):
1479 # register an auditor that checks that a user has the "May
1480 # Resolve" Permission before allowing them to set an issue
1481 # status to "resolved"
1482 db.issue.audit('set', checkresolvedok)
1483 p = db.security.addPermission(name="May Resolve", klass="issue")
1484 security.addToRole('Manager', p)
1486 The tracker dbinit module then has in ``open()``::
1488 # open the database - it must be modified to init the Security class
1489 # from security.py as db.security
1490 db = Database(config, name)
1492 # add some extra permissions and associate them with roles
1493 ei = db.security.addPermission(name="Edit", klass="issue",
1494 description="User is allowed to edit issues")
1495 db.security.addPermissionToRole('User', ei)
1496 ai = db.security.addPermission(name="View", klass="issue",
1497 description="User is allowed to access issues")
1498 db.security.addPermissionToRole('User', ai)
1500 In the dbinit ``init()``::
1502 # create the two default users
1503 user.create(username="admin", password=Password(adminpw),
1504 address=config.ADMIN_EMAIL, roles='Admin')
1505 user.create(username="anonymous", roles='Anonymous')
1507 Then in the code that matters, calls to ``hasPermission`` and
1508 ``hasItemPermission`` are made to determine if the user has permission
1509 to perform some action::
1511 if db.security.hasPermission('issue', 'Edit', userid):
1512 # all ok
1514 if db.security.hasItemPermission('issue', itemid,
1515 assignedto=userid):
1516 # all ok
1518 Code in the core will make use of these methods, as should code in
1519 auditors in custom templates. The HTML templating may access the access
1520 controls through the *user* attribute of the *request* variable. It
1521 exposes a ``hasPermission()`` method::
1523 tal:condition="python:request.user.hasPermission('Edit', 'issue')"
1525 or, if the *context* is *issue*, then the following is the same::
1527 tal:condition="python:request.user.hasPermission('Edit')"
1530 Authentication of Users
1531 ~~~~~~~~~~~~~~~~~~~~~~~
1533 Users must be authenticated correctly for the above controls to work.
1534 This is not done in the current mail gateway at all. Use of digital
1535 signing of messages could alleviate this problem.
1537 The exact mechanism of registering the digital signature should be
1538 flexible, with perhaps a level of trust. Users who supply their
1539 signature through their first message into the tracker should be at a
1540 lower level of trust to those who supply their signature to an admin for
1541 submission to their user details.
1544 Anonymous Users
1545 ~~~~~~~~~~~~~~~
1547 The "anonymous" user must always exist, and defines the access
1548 permissions for anonymous users. Unknown users accessing Roundup through
1549 the web or email interfaces will be logged in as the "anonymous" user.
1552 Use Cases
1553 ~~~~~~~~~
1555 public - end users can submit bugs, request new features, request
1556 support
1557 The Users would be given the default "User" Role which gives "View"
1558 and "Edit" Permission to the "issue" class.
1559 developer - developers can fix bugs, implement new features, provide
1560 support
1561 A new Role "Developer" is created with the Permission "Fixer" which
1562 is checked for in custom auditors that see whether the issue is
1563 being resolved with a particular resolution ("fixed", "implemented",
1564 "supported") and allows that resolution only if the permission is
1565 available.
1566 manager - approvers/managers can approve new features and signoff bug
1567 fixes
1568 A new Role "Manager" is created with the Permission "Signoff" which
1569 is checked for in custom auditors that see whether the issue status
1570 is being changed similar to the developer example. admin -
1571 administrators can add users and set user's roles The existing Role
1572 "Admin" has the Permissions "Edit" for all classes (including
1573 "user") and "Web Roles" which allow the desired actions.
1574 system - automated request handlers running various report/escalation
1575 scripts
1576 A combination of existing and new Roles, Permissions and auditors
1577 could be used here.
1578 privacy - issues that are only visible to some users
1579 A new property is added to the issue which marks the user or group
1580 of users who are allowed to view and edit the issue. An auditor will
1581 check for edit access, and the template user object can check for
1582 view access.
1585 Deployment Scenarios
1586 --------------------
1588 The design described above should be general enough to permit the use of
1589 Roundup for bug tracking, managing projects, managing patches, or
1590 holding discussions. By using items of multiple types, one could deploy
1591 a system that maintains requirement specifications, catalogs bugs, and
1592 manages submitted patches, where patches could be linked to the bugs and
1593 requirements they address.
1596 Acknowledgements
1597 ----------------
1599 My thanks are due to Christy Heyl for reviewing and contributing
1600 suggestions to this paper and motivating me to get it done, and to Jesse
1601 Vincent, Mark Miller, Christopher Simons, Jeff Dunmall, Wayne Gramlich,
1602 and Dean Tribble for their assistance with the first-round submission.
1605 Changes to this document
1606 ------------------------
1608 - Added Boolean and Number types
1609 - Added section Hyperdatabase Implementations
1610 - "Item" has been renamed to "Issue" to account for the more specific
1611 nature of the Class.
1612 - New Templating
1613 - Access Controls
1615 ------------------
1617 Back to `Table of Contents`_
1619 .. _`Table of Contents`: index.html
1620 .. _customisation: customizing.html