Mercurial > p > roundup > code
diff doc/customizing.txt @ 1244:8dd4f736370b
merge from maintenance branch
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Thu, 03 Oct 2002 06:56:30 +0000 |
| parents | dd52bf10f934 |
| children | d8c98af869ff |
line wrap: on
line diff
--- a/doc/customizing.txt Wed Oct 02 19:15:46 2002 +0000 +++ b/doc/customizing.txt Thu Oct 03 06:56:30 2002 +0000 @@ -2,7 +2,7 @@ Customising Roundup =================== -:Version: $Revision: 1.50 $ +:Version: $Revision: 1.51 $ .. This document borrows from the ZopeBook section on ZPT. The original is at: http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx @@ -13,6 +13,9 @@ What You Can Do =============== +Before you get too far, it's probably worth having a quick read of the Roundup +`design documentation`_. + Customisation of Roundup can take one of five forms: 1. `tracker configuration`_ file changes @@ -20,6 +23,7 @@ 3. "definition" class `database content`_ changes 4. behavioural changes, through detectors_ 5. `access controls`_ +6. change the `web interface`_ The third case is special because it takes two distinctly different forms depending upon whether the tracker has been initialised or not. The other two @@ -190,45 +194,27 @@ pri = Class(db, "priority", name=String(), order=String()) pri.setkey("name") - pri.create(name="critical", order="1") - pri.create(name="urgent", order="2") - pri.create(name="bug", order="3") - pri.create(name="feature", order="4") - pri.create(name="wish", order="5") stat = Class(db, "status", name=String(), order=String()) stat.setkey("name") - stat.create(name="unread", order="1") - stat.create(name="deferred", order="2") - stat.create(name="chatting", order="3") - stat.create(name="need-eg", order="4") - stat.create(name="in-progress", order="5") - stat.create(name="testing", order="6") - stat.create(name="done-cbb", order="7") - stat.create(name="resolved", order="8") keyword = Class(db, "keyword", name=String()) keyword.setkey("name") - user = Class(db, "user", username=String(), password=String(), - address=String(), realname=String(), phone=String(), - organisation=String()) + user = Class(db, "user", username=String(), organisation=String(), + password=String(), address=String(), realname=String(), phone=String()) user.setkey("username") - user.create(username="admin", password=adminpw, - address=config.ADMIN_EMAIL) - - msg = FileClass(db, "msg", author=Link("user"), recipients=Multilink - ("user"), date=Date(), summary=String(), files=Multilink("file")) + + msg = FileClass(db, "msg", author=Link("user"), summary=String(), + date=Date(), recipients=Multilink("user"), files=Multilink("file")) file = FileClass(db, "file", name=String(), type=String()) - issue = IssueClass(db, "issue", assignedto=Link("user"), - topic=Multilink("keyword"), priority=Link("priority"), status=Link - ("status")) + issue = IssueClass(db, "issue", topic=Multilink("keyword"), + status=Link("status"), assignedto=Link("user"), + priority=Link("priority")) issue.setkey('title') -XXX security definitions - Classes and Properties - creating a new information store --------------------------------------------------------- @@ -434,6 +420,143 @@ database content changes. +Access Controls +=============== + +A set of Permissions are built in to the security module by default: + +- Edit (everything) +- View (everything) + +The default interfaces define: + +- Web Registration +- Web Access +- Web Roles +- Email Registration +- Email Access + +These are hooked into the default Roles: + +- Admin (Edit everything, View everything, Web Roles) +- User (Web Access, Email Access) +- Anonymous (Web Registration, Email Registration) + +And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user +gets the "Anonymous" assigned when the database is initialised on installation. +The two default schemas then define: + +- Edit issue, View issue (both) +- Edit file, View file (both) +- Edit msg, View msg (both) +- Edit support, View support (extended only) + +and assign those Permissions to the "User" Role. Put together, these settings +appear in the ``open()`` function of the tracker ``dbinit.py`` (the following +is taken from the "minimal" template ``dbinit.py``):: + + # + # SECURITY SETTINGS + # + # new permissions for this schema + for cl in ('user', ): + db.security.addPermission(name="Edit", klass=cl, + description="User is allowed to edit "+cl) + db.security.addPermission(name="View", klass=cl, + description="User is allowed to access "+cl) + + # and give the regular users access to the web and email interface + p = db.security.getPermission('Web Access') + db.security.addPermissionToRole('User', p) + p = db.security.getPermission('Email Access') + db.security.addPermissionToRole('User', p) + + # May users view other user information? Comment these lines out + # if you don't want them to + p = db.security.getPermission('View', 'user') + db.security.addPermissionToRole('User', p) + + # Assign the appropriate permissions to the anonymous user's Anonymous + # Role. Choices here are: + # - Allow anonymous users to register through the web + p = db.security.getPermission('Web Registration') + db.security.addPermissionToRole('Anonymous', p) + # - Allow anonymous (new) users to register through the email gateway + p = db.security.getPermission('Email Registration') + db.security.addPermissionToRole('Anonymous', p) + + +New User Roles +-------------- + +New users are assigned the Roles defined in the config file as: + +- NEW_WEB_USER_ROLES +- NEW_EMAIL_USER_ROLES + + +Changing Access Controls +------------------------ + +You may alter the configuration variables to change the Role that new web or +email users get, for example to not give them access to the web interface if +they register through email. + +You may use the ``roundup-admin`` "``security``" command to display the +current Role and Permission configuration in your tracker. + +Adding a new Permission +~~~~~~~~~~~~~~~~~~~~~~~ + +When adding a new Permission, you will need to: + +1. add it to your tracker's dbinit so it is created +2. enable it for the Roles that should have it (verify with + "``roundup-admin security``") +3. add it to the relevant HTML interface templates +4. add it to the appropriate xxxPermission methods on in your tracker + interfaces module + +Example Scenarios +~~~~~~~~~~~~~~~~~ + +**automatic registration of users in the e-mail gateway** + By giving the "anonymous" user the "Email Registration" Role, any + unidentified user will automatically be registered with the tracker (with + no password, so they won't be able to log in through the web until an admin + sets them a password). Note: this is the default behaviour in the tracker + templates that ship with Roundup. + +**anonymous access through the e-mail gateway** + Give the "anonymous" user the "Email Access" and ("Edit", "issue") Roles + but not giving them the "Email Registration" Role. This means that when an + unknown user sends email into the tracker, they're automatically logged in + as "anonymous". Since they don't have the "Email Registration" Role, they + won't be automatically registered, but since "anonymous" has permission + to use the gateway, they'll still be able to submit issues. Note that the + Sender information - their email address - will not be available - they're + *anonymous*. + +**only developers may be assigned issues** + Create a new Permission called "Fixer" for the "issue" class. Create a new + Role "Developer" which has that Permission, and assign that to the + appropriate users. Filter the list of users available in the assignedto + list to include only those users. Enforce the Permission with an auditor. See + the example `restricting the list of users that are assignable to a task`_. + +**only managers may sign off issues as complete** + Create a new Permission called "Closer" for the "issue" class. Create a new + Role "Manager" which has that Permission, and assign that to the appropriate + users. In your web interface, only display the "resolved" issue state option + when the user has the "Closer" Permissions. Enforce the Permission with + an auditor. This is very similar to the previous example, except that the + web interface check would look like:: + + <option tal:condition="python:request.user.hasPermission('Closer')" + value="resolved">Resolved</option> + + + Web Interface ============= @@ -777,9 +900,19 @@ stringified. Path expressions may have an optional ``path:`` prefix, though they are the default expression type, so it's not necessary. - XXX | components of expressions - - XXX "nothing" and "default" + If an expression evaluates to ``default`` then the expression is + "cancelled" - whatever HTML already exists in the template will remain + (tag content in the case of tal:content, attributes in the case of + tal:attributes). + + If an expression evaluates to ``nothing`` then the target of the expression + is removed (tag content in the case of tal:content, attributes in the case + of tal:attributes and the tag itself in the case of tal:replace). + + If an element in the path may not exist, then you can use the ``|`` + operator in the expression to provide an alternative. So, the expression + ``request/form/foo/value | default`` would simply leave the current HTML + in place if the "foo" form variable doesn't exist. **String Expressions** - eg. ``string:hello ${user/name}`` These expressions are simple string interpolations (though they can be just @@ -1421,91 +1554,70 @@ Defining new web actions ------------------------ -XXX - - -Access Controls -=============== - -A set of Permissions are built in to the security module by default: - -- Edit (everything) -- View (everything) - -The default interfaces define: - -- Web Registration -- Web Access -- Web Roles -- Email Registration -- Email Access - -These are hooked into the default Roles: - -- Admin (Edit everything, View everything, Web Roles) -- User (Web Access, Email Access) -- Anonymous (Web Registration, Email Registration) - -And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user -gets the "Anonymous" assigned when the database is initialised on installation. -The two default schemas then define: - -- Edit issue, View issue (both) -- Edit file, View file (both) -- Edit msg, View msg (both) -- Edit support, View support (extended only) - -and assign those Permissions to the "User" Role. New users are assigned the -Roles defined in the config file as: - -- NEW_WEB_USER_ROLES -- NEW_EMAIL_USER_ROLES - -You may alter the configuration variables to change the Role that new web or -email users get, for example to not give them access to the web interface if -they register through email. - -You may use the ``roundup-admin`` "``security``" command to display the -current Role and Permission configuration in your tracker. - -Adding a new Permission ------------------------ - -When adding a new Permission, you will need to: - -1. add it to your tracker's dbinit so it is created -2. enable it for the Roles that should have it (verify with - "``roundup-admin security``") -3. add it to the relevant HTML interface templates -4. add it to the appropriate xxxPermission methods on in your tracker - interfaces module - -Example Scenarios ------------------ - -**automatic registration of users in the e-mail gateway** - By giving the "anonymous" user the "Email Registration" Role, any - unidentified user will automatically be registered with the tracker (with - no password, so they won't be able to log in through the web until an admin - sets them a password). Note: this is the default behaviour in the tracker - templates that ship with Roundup. - -**anonymous access through the e-mail gateway** - Give the "anonymous" user the "Email Access" and ("Edit", "issue") Roles - but not giving them the "Email Registration" Role. This means that when an - unknown user sends email into the tracker, they're automatically logged in - as "anonymous". Since they don't have the "Email Registration" Role, they - won't be automatically registered, but since "anonymous" has permission - to use the gateway, they'll still be able to submit issues. Note that the - Sender information - their email address - will not be available - they're - *anonymous*. - -XXX more examples needed +You may define new actions to be triggered by the ``:action`` form variable. +These are added to the tracker ``interfaces.py`` as methods on the ``Client`` +class. + +Adding action methods takes three steps; first you `define the new action +method`_, then you `register the action method`_ with the cgi interface so +it may be triggered by the ``:action`` form variable. Finally you actually +`use the new action`_ in your HTML form. + +See "`setting up a "wizard" (or "druid") for controlled adding of issues`_" +for an example. + +Define the new action method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The action methods have the following interface:: + + def myActionMethod(self): + ''' Perform some action. No return value is required. + ''' + +The *self* argument is an instance of your tracker ``instance.Client`` class - +thus it's mostly implemented by ``roundup.cgi.Client``. See the docstring of +that class for details of what it can do. + +The method will typically check the ``self.form`` variable's contents. It +may then: + +- add information to ``self.ok_message`` or ``self.error_message`` +- change the ``self.template`` variable to alter what the user will see next +- raise Unauthorised, SendStaticFile, SendFile, NotFound or Redirect + exceptions + + +Register the action method +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The method is now written, but isn't available to the user until you add it to +the `instance.Client`` class ``actions`` variable, like so:: + + actions = client.Class.actions + ( + ('myaction', 'myActionMethod'), + ) + +This maps the action name "myaction" to the action method we defined. + + +Use the new action +~~~~~~~~~~~~~~~~~~ + +In your HTML form, add a hidden form element like so:: + + <input type="hidden" name=":action" value="myaction"> + +where "myaction" is the name you registered in the previous step. Examples ======== +.. contents:: + :local: + :depth: 1 + Adding a new field to the classic schema ---------------------------------------- @@ -2101,7 +2213,7 @@ hooks to those actions in the "actions" attribute on that class, like so:: actions = client.Class.actions + ( - ('page1_submit', page1SubmitAction), + ('page1_submit', 'page1SubmitAction'), ) def page1SubmitAction(self): @@ -2157,9 +2269,100 @@ We also remove the redundant password fields from the ``user.item`` template. +Adding a "vacation" flag to users for stopping nosy messages +------------------------------------------------------------ + +When users go on vacation and set up vacation email bouncing, you'll start to +see a lot of messages come back through Roundup "Fred is on vacation". Not +very useful, and relatively easy to stop. + +1. add a "vacation" flag to your users:: + + user = Class(db, "user", + username=String(), password=Password(), + address=String(), realname=String(), + phone=String(), organisation=String(), + alternate_addresses=String(), + roles=String(), queries=Multilink("query"), + vacation=Boolean()) + +2. edit your detector ``nosyreactor.py`` so that the ``nosyreaction()`` + consists of:: + + def nosyreaction(db, cl, nodeid, oldvalues): + # send a copy of all new messages to the nosy list + for msgid in determineNewMessages(cl, nodeid, oldvalues): + try: + users = db.user + messages = db.msg + + # figure the recipient ids + sendto = [] + r = {} + recipients = messages.get(msgid, 'recipients') + for recipid in messages.get(msgid, 'recipients'): + r[recipid] = 1 + + # figure the author's id, and indicate they've received the + # message + authid = messages.get(msgid, 'author') + + # possibly send the message to the author, as long as they aren't + # anonymous + if (db.config.MESSAGES_TO_AUTHOR == 'yes' and + users.get(authid, 'username') != 'anonymous'): + sendto.append(authid) + r[authid] = 1 + + # now figure the nosy people who weren't recipients + nosy = cl.get(nodeid, 'nosy') + for nosyid in nosy: + # Don't send nosy mail to the anonymous user (that user + # shouldn't appear in the nosy list, but just in case they + # do...) + if users.get(nosyid, 'username') == 'anonymous': + continue + # make sure they haven't seen the message already + if not r.has_key(nosyid): + # send it to them + sendto.append(nosyid) + recipients.append(nosyid) + + # generate a change note + if oldvalues: + note = cl.generateChangeNote(nodeid, oldvalues) + else: + note = cl.generateCreateNote(nodeid) + + # we have new recipients + if sendto: + # filter out the people on vacation + sendto = [i for i in sendto if not users.get(i, 'vacation', 0)] + + # map userids to addresses + sendto = [users.get(i, 'address') for i in sendto] + + # update the message's recipients list + messages.set(msgid, recipients=recipients) + + # send the message + cl.send_message(nodeid, msgid, note, sendto) + except roundupdb.MessageSendError, message: + raise roundupdb.DetectorError, message + + Note that this is the standard nosy reaction code, with the small addition + of:: + + # filter out the people on vacation + sendto = [i for i in sendto if not users.get(i, 'vacation', 0)] + + which filters out the users that have the vacation flag set to true. + + ------------------- Back to `Table of Contents`_ .. _`Table of Contents`: index.html - +.. _`design documentation`: design.html +
