diff --git a/doc/customizing.txt b/doc/customizing.txt
index 60ec72115ee1308d2091910cc0c50619ef19093b..eb6b8adceeed1c70a49388a0f576c9aea12d2d35 100644 (file)
--- a/doc/customizing.txt
+++ b/doc/customizing.txt
Customising Roundup
===================
-:Version: $Revision: 1.61 $
+:Version: $Revision: 1.72 $
.. This document borrows from the ZopeBook section on ZPT. The original is at:
http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
The email address that e-mail sent to roundup should go to. Think of it as the
tracker's personal e-mail address.
-**TRACKER_WEB** - ``'http://your.tracker.url.example/'``
+**TRACKER_WEB** - ``'http://tracker.example/cgi-bin/roundup.cgi/bugs/'``
The web address that the tracker is viewable at. This will be included in
- information sent to users of the tracker.
+ information sent to users of the tracker. The URL **must** include the
+ cgi-bin part or anything else that is required to get to the home page of
+ the tracker. You **must** include a trailing '/' in the URL.
**ADMIN_EMAIL** - ``'roundup-admin@%s'%MAIL_DOMAIN``
The email address that roundup will complain to if it runs into trouble.
+**EMAIL_FROM_TAG** - ``''``
+ Additional text to include in the "name" part of the ``From:`` address used
+ in nosy messages. If the sending user is "Foo Bar", the ``From:`` line is
+ usually::
+ "Foo Bar" <issue_tracker@tracker.example>
+
+ the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so::
+
+ "Foo Bar EMAIL_FROM_TAG" <issue_tracker@tracker.example>
+
**MESSAGES_TO_AUTHOR** - ``'yes'`` or``'no'``
Send nosy messages to the author of the message.
# The email address that mail to roundup should go to
TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
- # The web address that the tracker is viewable at
- TRACKER_WEB = 'http://your.tracker.url.example/'
+ # The web address that the tracker is viewable at. This will be included in
+ # information sent to users of the tracker. The URL MUST include the cgi-bin
+ # part or anything else that is required to get to the home page of the
+ # tracker. You MUST include a trailing '/' in the URL.
+ TRACKER_WEB = 'http://tracker.example/cgi-bin/roundup.cgi/bugs/'
# The email address that roundup will complain to if it runs into trouble
ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
+ # Additional text to include in the "name" part of the From: address used
+ # in nosy messages. If the sending user is "Foo Bar", the From: line is
+ # usually: "Foo Bar" <issue_tracker@tracker.example>
+ # the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so:
+ # "Foo Bar EMAIL_FROM_TAG" <issue_tracker@tracker.example>
+ EMAIL_FROM_TAG = ""
+
# Send nosy messages to the author of the message
MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no'
MAIL_DEFAULT_CLASS = 'issue' # use "issue" class by default
#MAIL_DEFAULT_CLASS = '' # disable (or just comment the var out)
+ #
+ # SECURITY DEFINITIONS
+ #
+ # define the Roles that a user gets when they register with the tracker
+ # these are a comma-separated string of role names (e.g. 'Admin,User')
+ NEW_WEB_USER_ROLES = 'User'
+ NEW_EMAIL_USER_ROLES = 'User'
+
Tracker Schema
==============
the create() methods.
**Changing content after tracker initialisation**
- Use the roundup-admin interface's create, set and retire methods to add,
- alter or remove items from the classes in question.
+ As the "admin" user, click on the "class list" link in the web interface
+ to bring up a list of all database classes. Click on the name of the class
+ you wish to change the content of.
+ You may also use the roundup-admin interface's create, set and retire
+ methods to add, alter or remove items from the classes in question.
See "`adding a new field to the classic schema`_" for an example that requires
database content changes.
normal "User" Role minus the "Web Access" Permission. This will allow users
to send in emails to the tracker, but not access the web interface.
+**let some users edit the details of all users**
+ Create a new Role called "User Admin" which has the Permission for editing
+ users::
+
+ db.security.addRole(name='User Admin', description='Managing users')
+ p = db.security.getPermission('Edit', 'user')
+ db.security.addPermissionToRole('User Admin', p)
+
+ and assign the Role to the users who need the permission.
+
Web Interface
=============
the :note if it's supplied.
:required=property,property,...
The named properties are required to be filled in the form.
+ :remove:<propname>=id(s)
+ The ids will be removed from the multilink property. You may have multiple
+ :remove:<propname> form elements for a single <propname>.
+ :add:<propname>=id(s)
+ The ids will be added to the multilink property. You may have multiple
+ :add:<propname> form elements for a single <propname>.
**new**
Add a new item to the database. You may use the same special form elements
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
+ These expressions are simple string interpolations - though they can be just
plain strings with no interpolation if you want. The expression in the
``${ ... }`` is just a path expression as above.
`hyperdb class wrapper`_ or a `hyperdb item wrapper`_
**request**
Includes information about the current request, including:
- - the url
- the current index information (``filterspec``, ``filter`` args,
``properties``, etc) parsed out of the form.
- methods for easy filterspec link generation
Attribute Description
=============== =============================================================
_name the name of the property
-_value the value of the property if any
+_value the value of the property if any - this is the actual value
+ retrieved from the hyperdb for this property
=============== =============================================================
There are several methods available on these wrapper objects:
-=========== =================================================================
-Method Description
-=========== =================================================================
-plain render a "plain" representation of the property
-field render a form edit field for the property
-stext only on String properties - render the value of the
- property as StructuredText (requires the StructureText module
- to be installed separately)
-multiline only on String properties - render a multiline form edit
- field for the property
-email only on String properties - render the value of the
- property as an obscured email address
-confirm only on Password properties - render a second form edit field for
- the property, used for confirmation that the user typed the
- password correctly. Generates a field with name "name:confirm".
-reldate only on Date properties - render the interval between the
- date and now
-pretty only on Interval properties - render the interval in a
- pretty format (eg. "yesterday")
-menu only on Link and Multilink properties - render a form select
- list for this property
-reverse only on Multilink properties - produce a list of the linked
- items in reverse order
-=========== =================================================================
+========= =====================================================================
+Method Description
+========= =====================================================================
+plain render a "plain" representation of the property. This method may
+ take two arguments:
+
+ escape
+ If true, escape the text so it is HTML safe (default: no). The
+ reason this defaults to off is that text is usually escaped
+ at a later stage by the TAL commands, unless the "structure"
+ option is used in the template. The following are all equivalent::
+
+ <p tal:content="structure python:msg.content.plain(escape=1)" />
+ <p tal:content="python:msg.content.plain()" />
+ <p tal:content="msg/content/plain" />
+ <p tal:content="msg/content" />
+
+ Usually you'll only want to use the escape option in a complex
+ expression.
+
+ hyperlink
+ If true, turn URLs, email addresses and hyperdb item designators
+ in the text into hyperlinks (default: no). Note that you'll need
+ to use the "structure" TAL option if you want to use this::
+
+ <p tal:content="structure python:msg.content.plain(hyperlink=1)" />
+
+ Note also that the text is automatically HTML-escape before the
+ hyperlinking transformation.
+
+field render an appropriate form edit field for the property - for most
+ types this is a text entry box, but for Booleans it's a tri-state
+ yes/no/neither selection.
+stext only on String properties - render the value of the
+ property as StructuredText (requires the StructureText module
+ to be installed separately)
+multiline only on String properties - render a multiline form edit
+ field for the property
+email only on String properties - render the value of the
+ property as an obscured email address
+confirm only on Password properties - render a second form edit field for
+ the property, used for confirmation that the user typed the
+ password correctly. Generates a field with name "name:confirm".
+reldate only on Date properties - render the interval between the
+ date and now
+pretty only on Interval properties - render the interval in a
+ pretty format (eg. "yesterday")
+menu only on Link and Multilink properties - render a form select
+ list for this property
+reverse only on Multilink properties - produce a list of the linked
+ items in reverse order
+========= =====================================================================
The request variable
~~~~~~~~~~~~~~~~~~~~
=========== =================================================================
form the CGI form as a cgi.FieldStorage
env the CGI environment variables
-url the current URL path for this request
base the base URL for this tracker
user a HTMLUser instance for this user
classname the current classname (possibly None)
Searching Views
---------------
+Note: if you add a new column to the ``:columns`` form variable potentials
+ then you will need to add the column to the appropriate `index views`_
+ template so it is actually displayed.
+
This is one of the class context views. The template used is typically
"*classname*.search". The form on this page should have "search" as its
``:action`` variable. The "search" action:
template schema does not.
-
Item Views
----------
which displays only the allowed status to transition to.
-Displaying entire message contents in the issue display
--------------------------------------------------------
+Displaying only message summaries in the issue display
+------------------------------------------------------
Alter the issue.item template section for messages to::
<table class="messages" tal:condition="context/messages">
- <tr><th colspan=3 class="header">Messages</th></tr>
- <tal:block tal:repeat="msg context/messages/reverse">
- <tr>
- <th><a tal:attributes="href string:msg${msg/id}"
- tal:content="string:msg${msg/id}"></a></th>
- <th tal:content="string:Author: ${msg/author}">author</th>
- <th tal:content="string:Date: ${msg/date}">date</th>
- </tr>
- <tr>
- <td colspan="3" class="content">
- <pre tal:content="msg/content">content</pre>
- </td>
- </tr>
- </tal:block>
+ <tr><th colspan=5 class="header">Messages</th></tr>
+ <tr tal:repeat="msg context/messages">
+ <td><a tal:attributes="href string:msg${msg/id}"
+ tal:content="string:msg${msg/id}"></a></td>
+ <td tal:content="msg/author">author</td>
+ <td nowrap tal:content="msg/date/pretty">date</td>
+ <td tal:content="msg/summary">summary</td>
+ <td>
+ <a tal:attributes="href string:?:remove:messages=${msg/id}&:action=edit">remove</a>
+ </td>
+ </tr>
</table>
Restricting the list of users that are assignable to a task
The code to do this is::
- actions = client.Client.actions + (
- ('edit_with_timelog', 'timelogEditAction'),
- )
+ class Client(client.Client):
+ ''' derives basic CGI implementation from the standard module,
+ with any specific extensions
+ '''
+ actions = client.Client.actions + (
+ ('edit_with_timelog', 'timelogEditAction'),
+ )
+
+ def timelogEditAction(self):
+ ''' Handle the creation of a new time log entry if necessary.
+
+ If we create a new entry, fake up a CGI form value for the
+ altered "times" property of the issue being edited.
+
+ Punt to the regular edit action when we're done.
+ '''
+ # if there's a timelog value specified, create an entry
+ if self.form.has_key(':timelog') and \
+ self.form[':timelog'].value.strip():
+ period = Interval(self.form[':timelog'].value)
+ # create it
+ newid = self.db.timelog.create(period=period)
+
+ # if we're editing an existing item, get the old timelog value
+ if self.nodeid:
+ l = self.db.issue.get(self.nodeid, 'times')
+ l.append(newid)
+ else:
+ l = [newid]
- def timelogEditAction(self):
- ''' Handle the creation of a new time log entry if necessary.
+ # now make the fake CGI form values
+ for entry in l:
+ self.form.list.append(MiniFieldStorage('times', entry))
- If we create a new entry, fake up a CGI form value for the altered
- "times" property of the issue being edited.
+ # punt to the normal edit action
+ return self.editItemAction()
+
+ you add this code to your Client class in your tracker's ``interfaces.py``
+ file. Locate the section that looks like::
- Punt to the regular edit action when we're done.
- '''
- # if there's a timelog value specified, create an entry
- if self.form.has_key(':timelog') and \
- self.form[':timelog'].value.strip():
- period = Interval(self.form[':timelog'].value)
- # create it
- newid = self.db.timelog.create(period=period)
-
- # if we're editing an existing item, get the old timelog value
- if self.nodeid:
- l = self.db.issue.get(self.nodeid, 'times')
- l.append(newid)
- else:
- l = [newid]
-
- # now make the fake CGI form values
- for entry in l:
- self.form.list.append(MiniFieldStorage('times', entry))
-
- # punt to the normal edit action
- return self.editItemAction()
+ class Client:
+ ''' derives basic CGI implementation from the standard module,
+ with any specific extensions
+ '''
+ pass
- you add this code to your Client class in your tracker's ``interfaces.py``
- file.
+ and insert this code in place of the ``pass`` statement.
5. You'll also need to modify your ``issue.item`` form submit action so it
- calls the time logging action we just created::
+ calls the time logging action we just created. The current template will
+ look like this::
+
+ <tr>
+ <td> </td>
+ <td colspan=3 tal:content="structure context/submit">
+ submit button will go here
+ </td>
+ </tr>
+
+ replace it with this::
<tr>
<td> </td>
</td>
</tr>
- Note that the "context/submit" command usually handles all that for you -
- isn't it handy? The important change is setting the action to
- "edit_with_timelog" for edit operations (where the item exists)
+ The important change is setting the action to "edit_with_timelog" for
+ edit operations (where the item exists)
6. We want to display a total of the time log times that have been accumulated
for an issue. To do this, we'll need to actually write some Python code,
We do this by adding a method to the TemplatingUtils class in our tracker
``interfaces.py`` module::
-
class TemplatingUtils:
''' Methods implemented on this class will be available to HTML
templates through the 'utils' variable.
total += time.period._value
return total
+ Replace the ``pass`` line as we did in step 4 above with the Client class.
As indicated in the docstrings, we will be able to access the
- ``totalTimeSpent`` method via the ``utils`` variable in our templates. See
+ ``totalTimeSpent`` method via the ``utils`` variable in our templates.
7. Display the time log for an issue::
-------------------------------------------------------------------
This is pretty simple - all we need to do is copy the code from the example
-`displaying entire message contents in the issue display`_ into our template
+`displaying only message summaries in the issue display`_ into our template
alongside the summary display, and then introduce a switch that shows either
one or the other. We'll use a new form variable, ``:whole_messages`` to
achieve this::