X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=doc%2Fdesign.txt;h=0748fa29183de290805d49dcb868ba150f0cd292;hb=82254374e5abbaede04ff126d401f97a16032a25;hp=4d9bcc00decf5daa27f1192fb7ec6fcfc1149643;hpb=609cf46269f326f0480e99d1ccd18c8c39119571;p=roundup.git diff --git a/doc/design.txt b/doc/design.txt index 4d9bcc0..0748fa2 100644 --- a/doc/design.txt +++ b/doc/design.txt @@ -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={:1, : 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()) >>> 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