Code

- document experience from release(s)
[roundup.git] / doc / design.txt
index 4d9bcc00decf5daa27f1192fb7ec6fcfc1149643..0748fa29183de290805d49dcb868ba150f0cd292 100644 (file)
@@ -2,9 +2,7 @@
 Roundup - An Issue-Tracking System for Knowledge Workers
 ========================================================
 
-:Authors: Ka-Ping Yee (original__), Richard Jones (implementation)
-
-__ spec.html
+:Authors: Ka-Ping Yee (original), Richard Jones (implementation)
 
 .. contents::
 
@@ -14,10 +12,12 @@ Introduction
 This document presents a description of the components of the Roundup
 system and specifies their interfaces and behaviour in sufficient detail
 to guide an implementation. For the philosophy and rationale behind the
-Roundup design, see the first-round Software Carpentry submission for
-Roundup. This document fleshes out that design as well as specifying
+Roundup design, see the first-round Software Carpentry `submission for
+Roundup`__. This document fleshes out that design as well as specifying
 interfaces so that the components can be developed separately.
 
+__ spec.html
+
 
 The Layer Cake
 -----------------
@@ -50,13 +50,15 @@ Hyperdatabase
 -------------
 
 The lowest-level component to be implemented is the hyperdatabase. The
-hyperdatabase is intended to be a flexible data store that can hold
-configurable data in records which we call items.
+hyperdatabase is a flexible data store that can hold configurable data
+in records which we call items.
 
 The hyperdatabase is implemented on top of the storage layer, an
-external module for storing its data.  The storage layer could be a
-third-party RDBMS; for a "batteries-included" distribution, implementing
-the hyperdatabase on the standard bsddb module is suggested.
+external module for storing its data. The "batteries-includes" distribution
+implements the hyperdatabase on the standard anydbm module.  The storage
+layer could be a third-party RDBMS; for a low-maintenance solution,
+implementing the hyperdatabase on the SQLite RDBMS is suggested.
+
 
 Dates and Date Arithmetic
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -292,9 +294,6 @@ Here is the interface provided by the hyperdatabase::
     class Database:
         """A database for storing records containing flexible data
         types.
-
-        The id of the current user is available on the database as
-        'self.curuserid'.
         """
 
         def __init__(self, config, journaltag=None):
@@ -453,27 +452,50 @@ Here is the interface provided by the hyperdatabase::
             id is returned; otherwise a KeyError is raised.
             """
 
-        def find(self, propname, itemid):
+        def find(self, **propspec):
             """Get the ids of items in this class which link to the
             given items.
 
-            'propspec' consists of keyword args propname={itemid:1,}
-            'propname' must be the name of a property in this class, or
-            a KeyError is raised.  That property must be a Link or
-            Multilink property, or a TypeError is raised.
+            'propspec' consists of keyword args propname=itemid or
+                       propname={<itemid 1>:1, <itemid 2>: 1, ...}
+            'propname' must be the name of a property in this class,
+                       or a KeyError is raised.  That property must
+                       be a Link or Multilink property, or a TypeError
+                       is raised.
 
             Any item in this class whose 'propname' property links to
-            any of the itemids will be returned. Used by the full text
-            indexing, which knows that "foo" occurs in msg1, msg3 and
-            file7, so we have hits on these issues:
+            any of the itemids will be returned. Examples::
 
+                db.issue.find(messages='1')
                 db.issue.find(messages={'1':1,'3':1}, files={'7':1})
             """
 
         def filter(self, search_matches, filterspec, sort, group):
-            """ Return a list of the ids of the active items in this
-            class that match the 'filter' spec, sorted by the group spec
-            and then the sort spec.
+            """Return a list of the ids of the active nodes in this class that
+            match the 'filter' spec, sorted by the group spec and then the
+            sort spec.
+
+            "search_matches" is a container type
+
+            "filterspec" is {propname: value(s)}
+
+            "sort" and "group" are [(dir, prop), ...] where dir is '+', '-'
+            or None and prop is a prop name or None. Note that for
+            backward-compatibility reasons a single (dir, prop) tuple is
+            also allowed.
+
+            The filter must match all properties specificed. If the property
+            value to match is a list:
+
+            1. String properties must match all elements in the list, and
+            2. Other properties must match any of the elements in the list.
+
+            The propname in filterspec and prop in a sort/group spec may be
+            transitive, i.e., it may contain properties of the form
+            link.link.link.name, e.g. you can search for all issues where
+            a message was added by a certain user in the last week with a
+            filterspec of
+            {'messages.author' : '42', 'messages.creation' : '.-1w;'}
             """
 
         def list(self):
@@ -577,7 +599,7 @@ practice::
     4
     >>> db.issue.create(title="abuse", status=1)
     5
-    >>> hyperdb.Class(db, "user", username=hyperdb.Key(),
+    >>> hyperdb.Class(db, "user", username=hyperdb.String(),
     ... password=hyperdb.String())
     <hyperdb.Class "user">
     >>> db.issue.addprop(fixer=hyperdb.Link("user"))
@@ -696,13 +718,15 @@ superseder  hyperdb.Multilink("issue")
 =========== ==========================
 
 Also, two Date properties named "creation" and "activity" are fabricated
-by the Roundup database layer.  By "fabricated" we mean that no such
+by the Roundup database layer. Two user Link properties, "creator" and
+"actor" are also fabricated. By "fabricated" we mean that no such
 properties are actually stored in the hyperdatabase, but when properties
-on issues are requested, the "creation" and "activity" properties are
-made available. The value of the "creation" property is the date when an
-issue was created, and the value of the "activity" property is the date
-when any property on the issue was last edited (equivalently, these are
-the dates on the first and last records in the issue's journal).
+on issues are requested, the "creation"/"creator" and "activity"/"actor"
+properties are made available. The value of the "creation"/"creator"
+properties relate to issue creation, and the value of the "activity"/
+"actor" properties relate to the last editing of any property on the issue
+(equivalently, these are the dates on the first and last records in the
+issue's journal).
 
 
 Roundupdb Interface Specification
@@ -723,16 +747,10 @@ hyperdatabase, except for the following changes and additional methods::
         def set(self, **propvalues):
         def retire(self, itemid):
             """These operations trigger detectors and can be vetoed.
-            Attempts to modify the "creation" or "activity" properties
-            cause a KeyError.
+            Attempts to modify the "creation", "creator", "activity"
+            properties or "actor" cause a KeyError.
             """
 
-        # New methods:
-
-        def audit(self, event, detector):
-        def react(self, event, detector):
-            """Register a detector (see below for more details)."""
-
     class IssueClass(Class):
         # Overridden methods:
 
@@ -740,13 +758,14 @@ hyperdatabase, except for the following changes and additional methods::
             """The newly-created class automatically includes the
             "messages", "files", "nosy", and "superseder" properties.
             If the 'properties' dictionary attempts to specify any of
-            these properties or a "creation" or "activity" property, a
-            ValueError is raised."""
+            these properties or a "creation", "creator", "activity" or
+            "actor" property, a ValueError is raised."""
 
         def get(self, itemid, propname):
         def getprops(self):
             """In addition to the actual properties on the item, these
-            methods provide the "creation" and "activity" properties."""
+            methods provide the "creation", "creator", "activity" and
+            "actor" properties."""
 
         # New methods:
 
@@ -800,7 +819,7 @@ software bug tracker.  The database is set up like this::
     Class(db, "keyword", name=hyperdb.String())
 
     Class(db, "issue", fixer=hyperdb.Multilink("user"),
-                       topic=hyperdb.Multilink("keyword"),
+                       keyword=hyperdb.Multilink("keyword"),
                        priority=hyperdb.Link("priority"),
                        status=hyperdb.Link("status"))
 
@@ -852,20 +871,22 @@ The ``audit()`` and ``react()`` methods register detectors on a given
 class of items::
 
     class Class:
-        def audit(self, event, detector):
+        def audit(self, event, detector, priority=100):
             """Register an auditor on this class.
 
             'event' should be one of "create", "set", "retire", or
             "restore". 'detector' should be a function accepting four
-            arguments.
+            arguments. Detectors are called in priority order, execution
+            order is undefined for detectors with the same priority.
             """
 
-        def react(self, event, detector):
+        def react(self, event, detector, priority=100):
             """Register a reactor on this class.
 
             'event' should be one of "create", "set", "retire", or
             "restore". 'detector' should be a function accepting four
-            arguments.
+            arguments. Detectors are called in priority order, execution
+            order is undefined for detectors with the same priority.
             """
 
 Auditors are called with the arguments::
@@ -987,7 +1008,7 @@ module.)
 Command Interface Specification
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-A single command, roundup, provides basic access to the hyperdatabase
+A single command, ``roundup-admin``, provides basic access to the hyperdatabase
 from the command line::
 
     roundup-admin help
@@ -1018,11 +1039,12 @@ the printed results:
   are both accepted; an empty string, a single item, or a list of items
   joined by commas is accepted.
 
-When multiple items are specified to the roundup get or roundup set
+When multiple items are specified to the roundup-admin get or roundup-admin set
 commands, the specified properties are retrieved or set on all the
 listed items.
 
-When multiple results are returned by the roundup get or roundup find
+When multiple results are returned by the roundup-admin get or
+roundup-admin find
 commands, they are printed one per line (default) or joined by commas
 (with the -list) option.
 
@@ -1034,8 +1056,8 @@ To find all messages regarding in-progress issues that contain the word
 "spam", for example, you could execute the following command from the
 directory where the database dumps its files::
 
-    shell% for issue in `roundup find issue status=in-progress`; do
-    > grep -l spam `roundup get $issue messages`
+    shell% for issue in `roundup-admin find issue status=in-progress`; do
+    > grep -l spam `roundup-admin get $issue messages`
     > done
     msg23
     msg49
@@ -1045,8 +1067,8 @@ directory where the database dumps its files::
 
 Or, using the -list option, this can be written as a single command::
 
-    shell% grep -l spam `roundup get \
-        \`roundup find -list issue status=in-progress\` messages`
+    shell% grep -l spam `roundup-admin get \
+        \`roundup-admin find -list issue status=in-progress\` messages`
     msg23
     msg49
     msg50
@@ -1135,7 +1157,7 @@ Setting Properties
 The e-mail interface also provides a simple way to set properties on
 issues.  At the end of the subject line, ``propname=value`` pairs can be
 specified in square brackets, using the same conventions as for the
-roundup ``set`` shell command.
+roundup-admin ``set`` shell command.
 
 
 Web User Interface
@@ -1229,10 +1251,10 @@ An index view specifier looks like this (whitespace has been added for
 clarity)::
 
     /issue?status=unread,in-progress,resolved&
-        topic=security,ui&
-        :group=priority&
+        keyword=security,ui&
+        :group=priority,-status&
         :sort=-activity&
-        :filters=status,topic&
+        :filters=status,keyword&
         :columns=title,status,fixer
 
 
@@ -1253,12 +1275,12 @@ of issues with values matching any specified Multilink properties.
 
 The example specifies an index of "issue" items. Only issues with a
 "status" of either "unread" or "in-progres" or "resolved" are displayed,
-and only issues with "topic" values including both "security" and "ui"
-are displayed.  The issues are grouped by priority, arranged in
-ascending order; and within groups, sorted by activity, arranged in
-descending order.  The filter section shows filters for the "status" and
-"topic" properties, and the table includes columns for the "title",
-"status", and "fixer" properties.
+and only issues with "keyword" values including both "security" and "ui"
+are displayed.  The items are grouped by priority arranged in ascending
+order and in descending order by status; and within groups, sorted by
+activity, arranged in descending order. The filter section shows
+filters for the "status" and "keyword" properties, and the table includes
+columns for the "title", "status", and "fixer" properties.
 
 Associated with each issue class is a default layout specifier.  The
 layout specifier in the above example is the default layout to be
@@ -1382,11 +1404,12 @@ this path, and allow the multiple assignment of Roles to Users, and
 multiple Permissions to Roles. These definitions are not persistent -
 they're defined when the application initialises.
 
-There will be two levels of Permission. The Class level permissions
+There will be three levels of Permission. The Class level permissions
 define logical permissions associated with all items of a particular
 class (or all classes). The Item level permissions define logical
 permissions associated with specific items by way of their user-linked
-properties.
+properties. The Property level permissions define logical permissions
+associated with a specific property of an item.
 
 
 Access Control Interface Specification
@@ -1399,11 +1422,20 @@ The security module defines::
             - name
             - description
             - klass (optional)
+            - properties (optional)
+            - check function (optional)
 
             The klass may be unset, indicating that this permission is
             not locked to a particular hyperdb class. There may be
             multiple Permissions for the same name for different
             classes.
+
+            If property names are set, permission is restricted to those
+            properties only.
+
+            If check function is set, permission is granted only when
+            the function returns value interpreted as boolean true.
+            The function is called with arguments db, userid, itemid.
         '''
 
     class Role:
@@ -1419,36 +1451,41 @@ The security module defines::
                 the base roles (for admin user).
             '''
 
-        def getPermission(self, permission, classname=None):
-            ''' Find the Permission matching the name and for the class,
-                if the classname is specified.
+        def getPermission(self, permission, classname=None, properties=None,
+                check=None):
+            ''' Find the Permission exactly matching the name, class,
+                properties list and check function.
 
                 Raise ValueError if there is no exact match.
             '''
 
-        def hasPermission(self, permission, userid, classname=None):
+        def hasPermission(self, permission, userid, classname=None,
+                property=None, itemid=None):
             ''' Look through all the Roles, and hence Permissions, and
-                see if "permission" is there for the specified
-                classname.
-            '''
+                see if "permission" exists given the constraints of
+                classname, property and itemid.
 
-        def hasItemPermission(self, classname, itemid, **propspec):
-            ''' Check the named properties of the given item to see if
-                the userid appears in them. If it does, then the user is
-                granted this permission check.
+                If classname is specified (and only classname) then the
+                search will match if there is *any* Permission for that
+                classname, even if the Permission has additional
+                constraints.
 
-                'propspec' consists of a set of properties and values
-                that must be present on the given item for access to be
-                granted.
+                If property is specified, the Permission matched must have
+                either no properties listed or the property must appear in
+                the list.
 
-                If a property is a Link, the value must match the
-                property value. If a property is a Multilink, the value
-                must appear in the Multilink list.
+                If itemid is specified, the Permission matched must have
+                either no check function defined or the check function,
+                when invoked, must return a True value.
+
+                Note that this functionality is actually implemented by the
+                Permission.test() method.
             '''
 
         def addPermission(self, **propspec):
             ''' Create a new Permission with the properties defined in
-                'propspec'
+                'propspec'. See the Permission class for the possible
+                keyword args.
             '''
 
         def addRole(self, **propspec):
@@ -1614,11 +1651,7 @@ Changes to this document
   nature of the Class.
 - New Templating
 - Access Controls
+- Added "actor" property
 
-------------------
-
-Back to `Table of Contents`_
-
-.. _`Table of Contents`: index.html
 .. _customisation: customizing.html