comparison doc/customizing.txt @ 2915:7d97c75e7cba

more docs
author Richard Jones <richard@users.sourceforge.net>
date Tue, 16 Nov 2004 23:36:59 +0000
parents c75a19894d3e
children ad4fb8a14a97
comparison
equal deleted inserted replaced
2914:c75a19894d3e 2915:7d97c75e7cba
1 =================== 1 ===================
2 Customising Roundup 2 Customising Roundup
3 =================== 3 ===================
4 4
5 :Version: $Revision: 1.156 $ 5 :Version: $Revision: 1.157 $
6 6
7 .. This document borrows from the ZopeBook section on ZPT. The original is at: 7 .. This document borrows from the ZopeBook section on ZPT. The original is at:
8 http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx 8 http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
9 9
10 .. contents:: 10 .. contents::
883 The web interface is provided by the ``roundup.cgi.client`` module and 883 The web interface is provided by the ``roundup.cgi.client`` module and
884 is used by ``roundup.cgi``, ``roundup-server`` and ``ZRoundup`` 884 is used by ``roundup.cgi``, ``roundup-server`` and ``ZRoundup``
885 (``ZRoundup`` is broken, until further notice). In all cases, we 885 (``ZRoundup`` is broken, until further notice). In all cases, we
886 determine which tracker is being accessed (the first part of the URL 886 determine which tracker is being accessed (the first part of the URL
887 path inside the scope of the CGI handler) and pass control on to the 887 path inside the scope of the CGI handler) and pass control on to the
888 tracker ``interfaces.Client`` class - which uses the ``Client`` class 888 ``roundup.cgi.client.Client`` class - which handles the rest of the
889 from ``roundup.cgi.client`` - which handles the rest of the access 889 access through its ``main()`` method. This means that you can do pretty
890 through its ``main()`` method. This means that you can do pretty much 890 much anything you want as a web interface to your tracker.
891 anything you want as a web interface to your tracker. 891
892 892
893 893
894 Repercussions of changing the tracker schema 894 Repercussions of changing the tracker schema
895 --------------------------------------------- 895 ---------------------------------------------
896 896
1049 - Also handle the ":queryname" variable and save off the query to the 1049 - Also handle the ":queryname" variable and save off the query to the
1050 user's query list. 1050 user's query list.
1051 1051
1052 Each of the actions is implemented by a corresponding ``*XxxAction*`` (where 1052 Each of the actions is implemented by a corresponding ``*XxxAction*`` (where
1053 "Xxx" is the name of the action) class in the ``roundup.cgi.actions`` module. 1053 "Xxx" is the name of the action) class in the ``roundup.cgi.actions`` module.
1054 These classes are registered with ``roundup.cgi.client.Client`` which also 1054 These classes are registered with ``roundup.cgi.client.Client``. If you need
1055 happens to be available in your tracker instance as ``interfaces.Client``. So 1055 to define new actions, you may add them there (see `defining new
1056 if you need to define new actions, you may add them there (see `defining new
1057 web actions`_). 1056 web actions`_).
1058 1057
1059 Each action class also has a ``*permission*`` method which determines whether 1058 Each action class also has a ``*permission*`` method which determines whether
1060 the action is permissible given the current user. The base permission checks 1059 the action is permissible given the current user. The base permission checks
1061 are: 1060 are:
1993 url_quote quote some text as safe for a URL (ie. space, %, ...) 1992 url_quote quote some text as safe for a URL (ie. space, %, ...)
1994 html_quote quote some text as safe in HTML (ie. <, >, ...) 1993 html_quote quote some text as safe in HTML (ie. <, >, ...)
1995 =============== ======================================================== 1994 =============== ========================================================
1996 1995
1997 You may add additional utility methods by writing them in your tracker 1996 You may add additional utility methods by writing them in your tracker
1998 ``extensions`` directory and registering them with the templating system. 1997 ``extensions`` directory and registering them with the templating system
1998 using ``instance.registerUtil`` (see `adding a time log to your issues`_ for
1999 an example of this).
1999 2000
2000 2001
2001 Batching 2002 Batching
2002 :::::::: 2003 ::::::::
2003 2004
2334 2335
2335 Defining new web actions 2336 Defining new web actions
2336 ------------------------ 2337 ------------------------
2337 2338
2338 You may define new actions to be triggered by the ``@action`` form variable. 2339 You may define new actions to be triggered by the ``@action`` form variable.
2339 These are added to the tracker ``interfaces.py`` as ``Action`` classes that get 2340 These are added to the tracker ``extensions`` directory and registered
2340 called by the the ``Client`` class. 2341 using ``instance.registerAction``.
2342
2343 All the existing Actions are defined in ``roundup.cgi.actions``.
2341 2344
2342 Adding action classes takes three steps; first you `define the new 2345 Adding action classes takes three steps; first you `define the new
2343 action class`_, then you `register the action class`_ with the cgi 2346 action class`_, then you `register the action class`_ with the cgi
2344 interface so it may be triggered by the ``@action`` form variable. 2347 interface so it may be triggered by the ``@action`` form variable.
2345 Finally you `use the new action`_ in your HTML form. 2348 Finally you `use the new action`_ in your HTML form.
2349 2352
2350 2353
2351 Define the new action class 2354 Define the new action class
2352 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2355 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2353 2356
2354 The action classes have the following interface:: 2357 Create a new action class in your tracker's ``extensions`` directory, for
2358 example ``myaction.py``::
2355 2359
2356 class MyAction(Action): 2360 class MyAction(Action):
2357 def handle(self): 2361 def handle(self):
2358 ''' Perform some action. No return value is required. 2362 ''' Perform some action. No return value is required.
2359 ''' 2363 '''
2360 2364
2361 The *self.client* attribute is an instance of your tracker ``instance.Client`` 2365 The *self.client* attribute is an instance of ``roundup.cgi.client.Client``.
2362 class - thus it's mostly implemented by ``roundup.cgi.client.Client``. See the 2366 See the docstring of that class for details of what it can do.
2363 docstring of that class for details of what it can do.
2364 2367
2365 The method will typically check the ``self.form`` variable's contents. 2368 The method will typically check the ``self.form`` variable's contents.
2366 It may then: 2369 It may then:
2367 2370
2368 - add information to ``self.client.ok_message`` or ``self.client.error_message`` 2371 - add information to ``self.client.ok_message`` or ``self.client.error_message``
2373 2376
2374 2377
2375 Register the action class 2378 Register the action class
2376 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 2379 ~~~~~~~~~~~~~~~~~~~~~~~~~~
2377 2380
2378 XXX update for new extensions setup (then search for interfaces.py and make 2381 The class is now written, but isn't available to the user until you register
2379 sure there's no examples that use the old style) 2382 it with the following code appended to your ``myaction.py`` file::
2380 2383
2381 The class is now written, but isn't available to the user until you add it to 2384 def init(instance):
2382 the ``instance.Client`` class ``actions`` variable, like so:: 2385 instance.registerAction('myaction', myActionClass)
2383
2384 actions = client.Client.actions + (
2385 ('myaction', myActionClass),
2386 )
2387 2386
2388 This maps the action name "myaction" to the action class we defined. 2387 This maps the action name "myaction" to the action class we defined.
2388
2389 2389
2390 Use the new action 2390 Use the new action
2391 ~~~~~~~~~~~~~~~~~~ 2391 ~~~~~~~~~~~~~~~~~~
2392 2392
2393 In your HTML form, add a hidden form element like so:: 2393 In your HTML form, add a hidden form element like so::
2936 added to it. 2936 added to it.
2937 2937
2938 4. We want to display a total of the time log times that have been 2938 4. We want to display a total of the time log times that have been
2939 accumulated for an issue. To do this, we'll need to actually write 2939 accumulated for an issue. To do this, we'll need to actually write
2940 some Python code, since it's beyond the scope of PageTemplates to 2940 some Python code, since it's beyond the scope of PageTemplates to
2941 perform such calculations. We do this by adding a method to the 2941 perform such calculations. We do this by adding a module ``timespent.py``
2942 TemplatingUtils class in our tracker ``interfaces.py`` module:: 2942 to the ``extensions`` directory in our tracker::
2943 2943
2944 class TemplatingUtils: 2944 def totalTimeSpent(times):
2945 ''' Methods implemented on this class will be available to HTML 2945 ''' Call me with a list of timelog items (which have an
2946 templates through the 'utils' variable. 2946 Interval "period" property)
2947 ''' 2947 '''
2948 def totalTimeSpent(self, times): 2948 total = Interval('0d')
2949 ''' Call me with a list of timelog items (which have an 2949 for time in times:
2950 Interval "period" property) 2950 total += time.period._value
2951 ''' 2951 return total
2952 total = Interval('0d') 2952
2953 for time in times: 2953 def init(instance):
2954 total += time.period._value 2954 instance.registerUtil('totalTimeSpent', totalTimeSpent)
2955 return total 2955
2956 2956 We will now be able to access the ``totalTimeSpent`` function via the
2957 XXX update this example for TemplatingUtils -> extensions 2957 ``utils`` variable in our templates, as shown in the next step.
2958
2959 Replace the ``pass`` line if one appears in your TemplatingUtils
2960 class. As indicated in the docstrings, we will be able to access the
2961 ``totalTimeSpent`` method via the ``utils`` variable in our templates.
2962 2958
2963 5. Display the time log for an issue:: 2959 5. Display the time log for an issue::
2964 2960
2965 <table class="otherinfo" tal:condition="context/times"> 2961 <table class="otherinfo" tal:condition="context/times">
2966 <tr><th colspan="3" class="header">Time Log 2962 <tr><th colspan="3" class="header">Time Log
3058 admin:aamrgyQfDFSHw 3054 admin:aamrgyQfDFSHw
3059 3055
3060 Each user of Roundup must still have their information stored in the Roundup 3056 Each user of Roundup must still have their information stored in the Roundup
3061 database - we just use the passwd file to check their password. To do this, we 3057 database - we just use the passwd file to check their password. To do this, we
3062 need to override the standard ``verifyPassword`` method defined in 3058 need to override the standard ``verifyPassword`` method defined in
3063 ``roundup.cgi.actions.LoginAction`` and register the new class with our 3059 ``roundup.cgi.actions.LoginAction`` and register the new class. The
3064 ``Client`` class in the tracker home ``interfaces.py`` module:: 3060 following is added as ``externapassword.py`` in the tracker ``extensions``
3061 directory::
3065 3062
3066 from roundup.cgi.actions import LoginAction 3063 from roundup.cgi.actions import LoginAction
3067 3064
3068 class ExternalPasswordLoginAction(LoginAction): 3065 class ExternalPasswordLoginAction(LoginAction):
3069 def verifyPassword(self, userid, password): 3066 def verifyPassword(self, userid, password):
3067 '''Look through the file, line by line, looking for a
3068 name that matches.
3069 '''
3070 # get the user's username 3070 # get the user's username
3071 username = self.db.user.get(userid, 'username') 3071 username = self.db.user.get(userid, 'username')
3072 3072
3073 # the passwords are stored in the "passwd.txt" file in the 3073 # the passwords are stored in the "passwd.txt" file in the
3074 # tracker home 3074 # tracker home
3081 return crypt.crypt(password, ent[1][:2]) == ent[1] 3081 return crypt.crypt(password, ent[1][:2]) == ent[1]
3082 3082
3083 # user doesn't exist in the file 3083 # user doesn't exist in the file
3084 return 0 3084 return 0
3085 3085
3086 class Client(client.Client): 3086 def init(instance):
3087 actions = ( 3087 instance.registerAction('login', ExternalPasswordLoginAction)
3088 ('login', ExternalPasswordLoginAction), 3088
3089 ) + client.Client.actions 3089 You should also remove the redundant password fields from the ``user.item``
3090
3091 What this does is look through the file, line by line, looking for a
3092 name that matches.
3093
3094 We also remove the redundant password fields from the ``user.item``
3095 template. 3090 template.
3096 3091
3097 3092
3098 Using a UN*X passwd file as the user database 3093 Using a UN*X passwd file as the user database
3099 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3094 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4087 ... the above section will only be displayed if the category is one 4082 ... the above section will only be displayed if the category is one
4088 of 6, 10, 13, 14, 15, 16 or 17. 4083 of 6, 10, 13, 14, 15, 16 or 17.
4089 4084
4090 3. Determine what actions need to be taken between the pages - these are 4085 3. Determine what actions need to be taken between the pages - these are
4091 usually to validate user choices and determine what page is next. Now encode 4086 usually to validate user choices and determine what page is next. Now encode
4092 those actions in a new ``Action`` class and insert hooks to those actions in 4087 those actions in a new ``Action`` class (see `defining new web actions`_)::
4093 the "actions" attribute on on the ``interfaces.Client`` class, like so (see 4088
4094 `defining new web actions`_):: 4089 from roundup.cgi.actions import Action
4095 4090
4096 class Page1SubmitAction(Action): 4091 class Page1SubmitAction(Action):
4097 def handle(self): 4092 def handle(self):
4098 ''' Verify that the user has selected a category, and then move 4093 ''' Verify that the user has selected a category, and then move
4099 on to page 2. 4094 on to page 2.
4103 self.error_message.append('You must select a category of report') 4098 self.error_message.append('You must select a category of report')
4104 return 4099 return
4105 # everything's ok, move on to the next page 4100 # everything's ok, move on to the next page
4106 self.template = 'add_page2' 4101 self.template = 'add_page2'
4107 4102
4108 actions = client.Client.actions + ( 4103 def init(instance):
4109 ('page1_submit', Page1SubmitAction), 4104 instance.registerAction('page1_submit', Page1SubmitAction)
4110 )
4111 4105
4112 4. Use the usual "new" action as the ``@action`` on the final page, and 4106 4. Use the usual "new" action as the ``@action`` on the final page, and
4113 you're done (the standard context/submit method can do this for you). 4107 you're done (the standard context/submit method can do this for you).
4114 4108
4115 4109

Roundup Issue Tracker: http://roundup-tracker.org/