comparison doc/customizing.txt @ 3130:7308c3c5a943

docs editing from Jean Jordaan
author Richard Jones <richard@users.sourceforge.net>
date Sat, 12 Feb 2005 00:47:33 +0000
parents 021b131bd816
children 1b15e9eeb592
comparison
equal deleted inserted replaced
3127:021b131bd816 3130:7308c3c5a943
1 =================== 1 ===================
2 Customising Roundup 2 Customising Roundup
3 =================== 3 ===================
4 4
5 :Version: $Revision: 1.171 $ 5 :Version: $Revision: 1.172 $
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::
555 ============================================ 555 ============================================
556 .. _detectors: 556 .. _detectors:
557 557
558 Detectors are initialised every time you open your tracker database, so 558 Detectors are initialised every time you open your tracker database, so
559 you're free to add and remove them any time, even after the database is 559 you're free to add and remove them any time, even after the database is
560 initialised via the "roundup-admin initialise" command. 560 initialised via the ``roundup-admin initialise`` command.
561 561
562 The detectors in your tracker fire *before* (**auditors**) and *after* 562 The detectors in your tracker fire *before* (**auditors**) and *after*
563 (**reactors**) changes to the contents of your database. They are Python 563 (**reactors**) changes to the contents of your database. They are Python
564 modules that sit in your tracker's ``detectors`` directory. You will 564 modules that sit in your tracker's ``detectors`` directory. You will
565 have some installed by default - have a look. You can write new 565 have some installed by default - have a look. You can write new
921 921
922 **properties** 922 **properties**
923 A sequence of property names that are the only properties to apply the 923 A sequence of property names that are the only properties to apply the
924 new Permission to (eg. ``... klass='user', properties=('name', 924 new Permission to (eg. ``... klass='user', properties=('name',
925 'email') ...``) 925 'email') ...``)
926 **code** 926 **check**
927 A function to be execute which returns boolean determining whether the 927 A function to be execute which returns boolean determining whether the
928 Permission is allowed. The function has the signature ``check(db, userid, 928 Permission is allowed. The function has the signature ``check(db, userid,
929 itemid)`` where ``db`` is a handle on the open database, ``userid`` is 929 itemid)`` where ``db`` is a handle on the open database, ``userid`` is
930 the user attempting access and ``itemid`` is the specific item being 930 the user attempting access and ``itemid`` is the specific item being
931 accessed. 931 accessed.
2588 In both cases, the value is a valid charset name (eg. ``utf-8`` or 2588 In both cases, the value is a valid charset name (eg. ``utf-8`` or
2589 ``kio8-r``). 2589 ``kio8-r``).
2590 2590
2591 Inside Roundup, all strings are stored and processed in utf-8. 2591 Inside Roundup, all strings are stored and processed in utf-8.
2592 Unfortunately, some older browsers do not work properly with 2592 Unfortunately, some older browsers do not work properly with
2593 utf8-encoded pages (e.g. Netscape Navigator 4 displays wrong 2593 utf-8-encoded pages (e.g. Netscape Navigator 4 displays wrong
2594 characters in form fields). This version allows to change 2594 characters in form fields). This version allows one to change
2595 the character set for http transfers. To do so, you may add 2595 the character set for http transfers. To do so, you may add
2596 the following code to your ``page.html`` template:: 2596 the following code to your ``page.html`` template::
2597 2597
2598 <tal:block define="uri string:${request/base}${request/env/PATH_INFO}"> 2598 <tal:block define="uri string:${request/base}${request/env/PATH_INFO}">
2599 <a tal:attributes="href python:request.indexargs_href(uri, 2599 <a tal:attributes="href python:request.indexargs_url(uri,
2600 {'@charset':'utf-8'})">utf-8</a> 2600 {'@charset':'utf-8'})">utf-8</a>
2601 <a tal:attributes="href python:request.indexargs_href(uri, 2601 <a tal:attributes="href python:request.indexargs_url(uri,
2602 {'@charset':'koi8-r'})">koi8-r</a> 2602 {'@charset':'koi8-r'})">koi8-r</a>
2603 </tal:block> 2603 </tal:block>
2604 2604
2605 (substitute ``koi8-r`` with appropriate charset for your language). 2605 (substitute ``koi8-r`` with appropriate charset for your language).
2606 Charset preference is kept in the browser cookie ``roundup_charset``. 2606 Charset preference is kept in the browser cookie ``roundup_charset``.
2607 2607
2608 Lines ``meta http-equiv`` added to the tracker templates in version 0.6.0 2608 ``meta http-equiv`` lines added to the tracker templates in version 0.6.0
2609 should be changed to include actual character set name:: 2609 should be changed to include actual character set name::
2610 2610
2611 <meta http-equiv="Content-Type" 2611 <meta http-equiv="Content-Type"
2612 tal:attributes="content string:text/html;; charset=${request/client/charset}" 2612 tal:attributes="content string:text/html;; charset=${request/client/charset}"
2613 /> 2613 />
2678 2678
2679 2679
2680 Introduction 2680 Introduction
2681 :::::::::::: 2681 ::::::::::::
2682 2682
2683 To make the classic schema of roundup useful as a TODO tracking system 2683 To make the classic schema of Roundup useful as a TODO tracking system
2684 for a group of systems administrators, it needed an extra data field per 2684 for a group of systems administrators, it needs an extra data field per
2685 issue: a category. 2685 issue: a category.
2686 2686
2687 This would let sysadmins quickly list all TODOs in their particular area 2687 This would let sysadmins quickly list all TODOs in their particular area
2688 of interest without having to do complex queries, and without relying on 2688 of interest without having to do complex queries, and without relying on
2689 the spelling capabilities of other sysadmins (a losing proposition at 2689 the spelling capabilities of other sysadmins (a losing proposition at
2693 Adding a field to the database 2693 Adding a field to the database
2694 :::::::::::::::::::::::::::::: 2694 ::::::::::::::::::::::::::::::
2695 2695
2696 This is the easiest part of the change. The category would just be a 2696 This is the easiest part of the change. The category would just be a
2697 plain string, nothing fancy. To change what is in the database you need 2697 plain string, nothing fancy. To change what is in the database you need
2698 to add some lines to the ``open()`` function in ``schema.py``. Under the 2698 to add some lines to the ``schema.py`` file of your tracker instance.
2699 comment:: 2699 Under the comment::
2700 2700
2701 # add any additional database schema configuration here 2701 # add any additional database schema configuration here
2702 2702
2703 add:: 2703 add::
2704 2704
2715 This also means that there can only be one category with a given name. 2715 This also means that there can only be one category with a given name.
2716 2716
2717 Adding the above lines allows us to create categories, but they're not 2717 Adding the above lines allows us to create categories, but they're not
2718 tied to the issues that we are going to be creating. It's just a list of 2718 tied to the issues that we are going to be creating. It's just a list of
2719 categories off on its own, which isn't much use. We need to link it in 2719 categories off on its own, which isn't much use. We need to link it in
2720 with the issues. To do that, find the lines in the ``open()`` function 2720 with the issues. To do that, find the lines
2721 in ``schema.py`` which set up the "issue" class, and then add a link to 2721 in ``schema.py`` which set up the "issue" class, and then add a link to
2722 the category:: 2722 the category::
2723 2723
2724 issue = IssueClass(db, "issue", ... , 2724 issue = IssueClass(db, "issue", ... ,
2725 category=Multilink("category"), ... ) 2725 category=Multilink("category"), ... )
2733 2733
2734 2734
2735 Populating the new category class 2735 Populating the new category class
2736 ::::::::::::::::::::::::::::::::: 2736 :::::::::::::::::::::::::::::::::
2737 2737
2738 If you haven't initialised the database with the roundup-admin 2738 If you haven't initialised the database with the ``roundup-admin``
2739 "initialise" command, then you can add the following to the tracker 2739 "initialise" command, then you can add the following to the tracker
2740 ``schema.py`` in the ``init()`` function under the comment:: 2740 ``initial_data.py`` under the comment::
2741 2741
2742 # add any additional database create steps here - but only if you 2742 # add any additional database creation steps here - but only if you
2743 # haven't initialised the database with the admin "initialise" command 2743 # haven't initialised the database with the admin "initialise" command
2744 2744
2745 Add:: 2745 Add::
2746 2746
2747 category = db.getclass('category') 2747 category = db.getclass('category')
2774 doesn't suit us, as we want any user to be able to create new categories 2774 doesn't suit us, as we want any user to be able to create new categories
2775 as required, and obviously everyone needs to be able to view the 2775 as required, and obviously everyone needs to be able to view the
2776 categories of issues for it to be useful. 2776 categories of issues for it to be useful.
2777 2777
2778 We therefore need to change the security of the category objects. This 2778 We therefore need to change the security of the category objects. This
2779 is also done in the ``open()`` function of ``schema.py``. 2779 is also done in ``schema.py``.
2780 2780
2781 There are currently two loops which set up permissions and then assign 2781 There are currently two loops which set up permissions and then assign
2782 them to various roles. Simply add the new "category" to both lists:: 2782 them to various roles. Simply add the new "category" to both lists::
2783 2783
2784 # Assign the access and edit permissions for issue, file and message 2784 # Assign the access and edit permissions for issue, file and message
2787 p = db.security.getPermission('View', cl) 2787 p = db.security.getPermission('View', cl)
2788 db.security.addPermissionToRole('User', 'View', cl) 2788 db.security.addPermissionToRole('User', 'View', cl)
2789 db.security.addPermissionToRole('User', 'Edit', cl) 2789 db.security.addPermissionToRole('User', 'Edit', cl)
2790 db.security.addPermissionToRole('User', 'Create', cl) 2790 db.security.addPermissionToRole('User', 'Create', cl)
2791 2791
2792 These lines assign the View and Edit Permissions to the "User" role, so 2792 These lines assign the "View" and "Edit" Permissions to the "User" role,
2793 that normal users can view and edit "category" objects. 2793 so that normal users can view and edit "category" objects.
2794 2794
2795 This is all the work that needs to be done for the database. It will 2795 This is all the work that needs to be done for the database. It will
2796 store categories, and let users view and edit them. Now on to the 2796 store categories, and let users view and edit them. Now on to the
2797 interface stuff. 2797 interface stuff.
2798 2798
2801 :::::::::::::::::::::::::::::::: 2801 ::::::::::::::::::::::::::::::::
2802 2802
2803 We need to give the users the ability to create new categories, and the 2803 We need to give the users the ability to create new categories, and the
2804 place to put the link to this functionality is in the left hand function 2804 place to put the link to this functionality is in the left hand function
2805 bar, under the "Issues" area. The file that defines how this area looks 2805 bar, under the "Issues" area. The file that defines how this area looks
2806 is ``html/page``, which is what we are going to be editing next. 2806 is ``html/page.html``, which is what we are going to be editing next.
2807 2807
2808 If you look at this file you can see that it contains a lot of 2808 If you look at this file you can see that it contains a lot of
2809 "classblock" sections which are chunks of HTML that will be included or 2809 "classblock" sections which are chunks of HTML that will be included or
2810 excluded in the output depending on whether the condition in the 2810 excluded in the output depending on whether the condition in the
2811 classblock is met. Under the end of the classblock for issue is where we 2811 classblock is met. We are going to add the category code at the end of
2812 are going to add the category code:: 2812 the classblock for the *issue* class::
2813 2813
2814 <p class="classblock" 2814 <p class="classblock"
2815 tal:condition="python:request.user.hasPermission('View', 'category')"> 2815 tal:condition="python:request.user.hasPermission('View', 'category')">
2816 <b>Categories</b><br> 2816 <b>Categories</b><br>
2817 <a tal:condition="python:request.user.hasPermission('Edit', 'category')" 2817 <a tal:condition="python:request.user.hasPermission('Edit', 'category')"
2832 2832
2833 Note that if you have permission to *view* but not to *edit* categories, 2833 Note that if you have permission to *view* but not to *edit* categories,
2834 then all you will see is a "Categories" header with nothing underneath 2834 then all you will see is a "Categories" header with nothing underneath
2835 it. This is obviously not very good interface design, but will do for 2835 it. This is obviously not very good interface design, but will do for
2836 now. I just claim that it is so I can add more links in this section 2836 now. I just claim that it is so I can add more links in this section
2837 later on. However to fix the problem you could change the condition in 2837 later on. However, to fix the problem you could change the condition in
2838 the classblock statement, so that only users with "Edit" permission 2838 the classblock statement, so that only users with "Edit" permission
2839 would see the "Categories" stuff. 2839 would see the "Categories" stuff.
2840 2840
2841 2841
2842 Setting up a page to edit categories 2842 Setting up a page to edit categories
2849 The link was for the *item* template of the *category* object. This 2849 The link was for the *item* template of the *category* object. This
2850 translates into Roundup looking for a file called ``category.item.html`` 2850 translates into Roundup looking for a file called ``category.item.html``
2851 in the ``html`` tracker directory. This is the file that we are going to 2851 in the ``html`` tracker directory. This is the file that we are going to
2852 write now. 2852 write now.
2853 2853
2854 First we add an info tag in a comment which doesn't affect the outcome 2854 First, we add an info tag in a comment which doesn't affect the outcome
2855 of the code at all, but is useful for debugging. If you load a page in a 2855 of the code at all, but is useful for debugging. If you load a page in a
2856 browser and look at the page source, you can see which sections come 2856 browser and look at the page source, you can see which sections come
2857 from which files by looking for these comments:: 2857 from which files by looking for these comments::
2858 2858
2859 <!-- category.item --> 2859 <!-- category.item -->
2889 2889
2890 <table class="form"> 2890 <table class="form">
2891 <tr><th class="header" colspan="2">Category</th></tr> 2891 <tr><th class="header" colspan="2">Category</th></tr>
2892 2892
2893 Next, we need the field into which the user is going to enter the new 2893 Next, we need the field into which the user is going to enter the new
2894 category. The "context.name.field(size=60)" bit tells Roundup to 2894 category. The ``context.name.field(size=60)`` bit tells Roundup to
2895 generate a normal HTML field of size 60, and the contents of that field 2895 generate a normal HTML field of size 60, and the contents of that field
2896 will be the "name" variable of the current context (which is 2896 will be the "name" variable of the current context (namely "category").
2897 "category"). The upshot of this is that when the user types something in 2897 The upshot of this is that when the user types something in
2898 to the form, a new category will be created with that name:: 2898 to the form, a new category will be created with that name::
2899 2899
2900 <tr> 2900 <tr>
2901 <th>Name</th> 2901 <th>Name</th>
2902 <td tal:content="structure python:context.name.field(size=60)"> 2902 <td tal:content="structure python:context.name.field(size=60)">
2966 that is pointless unless we can assign categories to issues. Just like 2966 that is pointless unless we can assign categories to issues. Just like
2967 the ``html/category.item.html`` file was used to define how to add a new 2967 the ``html/category.item.html`` file was used to define how to add a new
2968 category, the ``html/issue.item.html`` is used to define how a new issue 2968 category, the ``html/issue.item.html`` is used to define how a new issue
2969 is created. 2969 is created.
2970 2970
2971 Just like ``category.issue.html`` this file defines a form which has a 2971 Just like ``category.issue.html``, this file defines a form which has a
2972 table to lay things out. It doesn't matter where in the table we add new 2972 table to lay things out. It doesn't matter where in the table we add new
2973 stuff, it is entirely up to your sense of aesthetics:: 2973 stuff, it is entirely up to your sense of aesthetics::
2974 2974
2975 <th>Category</th> 2975 <th>Category</th>
2976 <td><span tal:replace="structure context/category/field" /> 2976 <td><span tal:replace="structure context/category/field" />
2988 2988
2989 2989
2990 Searching on categories 2990 Searching on categories
2991 ::::::::::::::::::::::: 2991 :::::::::::::::::::::::
2992 2992
2993 We can add categories, and create issues with categories. The next 2993 Now we can add categories, and create issues with categories. The next
2994 obvious thing that we would like to be able to do, would be to search 2994 obvious thing that we would like to be able to do, would be to search
2995 for issues based on their category, so that, for example, anyone working 2995 for issues based on their category, so that, for example, anyone working
2996 on the web server could look at all issues in the category "Web". 2996 on the web server could look at all issues in the category "Web".
2997 2997
2998 If you look for "Search Issues" in the 'html/page.html' file, you will 2998 If you look for "Search Issues" in the ``html/page.html`` file, you will
2999 find that it looks something like 2999 find that it looks something like
3000 ``<a href="issue?@template=search">Search Issues</a>``. This shows us 3000 ``<a href="issue?@template=search">Search Issues</a>``. This shows us
3001 that when you click on "Search Issues" it will be looking for a 3001 that when you click on "Search Issues" it will be looking for a
3002 ``issue.search.html`` file to display. So that is the file that we will 3002 ``issue.search.html`` file to display. So that is the file that we will
3003 change. 3003 change.
3004 3004
3005 If you look at this file it should be starting to seem familiar, although it 3005 If you look at this file it should begin to seem familiar, although it
3006 does use some new macros. You can add the new category search code anywhere you 3006 does use some new macros. You can add the new category search code anywhere you
3007 like within that form:: 3007 like within that form::
3008 3008
3009 <tr tal:define="name string:category; 3009 <tr tal:define="name string:category;
3010 db_klass string:category; 3010 db_klass string:category;
3014 <td metal:use-macro="column_input"></td> 3014 <td metal:use-macro="column_input"></td>
3015 <td metal:use-macro="sort_input"></td> 3015 <td metal:use-macro="sort_input"></td>
3016 <td metal:use-macro="group_input"></td> 3016 <td metal:use-macro="group_input"></td>
3017 </tr> 3017 </tr>
3018 3018
3019 The definitions in the <tr> opening tag are used by the macros: 3019 The definitions in the ``<tr>`` opening tag are used by the macros:
3020 3020
3021 - search_select expands to a drop-down box with all categories using db_klass 3021 - ``search_select`` expands to a drop-down box with all categories using
3022 and db_content. 3022 ``db_klass`` and ``db_content``.
3023 - column_input expands to a checkbox for selecting what columns should be 3023 - ``column_input`` expands to a checkbox for selecting what columns
3024 displayed. 3024 should be displayed.
3025 - sort_input expands to a radio button for selecting what property should be 3025 - ``sort_input`` expands to a radio button for selecting what property
3026 sorted on. 3026 should be sorted on.
3027 - group_input expands to a radio button for selecting what property should be 3027 - ``group_input`` expands to a radio button for selecting what property
3028 group on. 3028 should be grouped on.
3029 3029
3030 The category search code above would expand to the following:: 3030 The category search code above would expand to the following::
3031 3031
3032 <tr> 3032 <tr>
3033 <th>Category:</th> 3033 <th>Category:</th>
3078 The condition is the same as above: only display the condition when the 3078 The condition is the same as above: only display the condition when the
3079 user hasn't asked for it to be hidden. The next part is to set the 3079 user hasn't asked for it to be hidden. The next part is to set the
3080 content of the cell to be the category part of "i" - the current issue. 3080 content of the cell to be the category part of "i" - the current issue.
3081 3081
3082 Finally we have to edit ``html/page.html`` again. This time, we need to 3082 Finally we have to edit ``html/page.html`` again. This time, we need to
3083 tell it that when the user clicks on "Unasigned Issues" or "All Issues", 3083 tell it that when the user clicks on "Unassigned Issues" or "All Issues",
3084 the category column should be included in the resulting list. If you 3084 the category column should be included in the resulting list. If you
3085 scroll down the page file, you can see the links with lots of options. 3085 scroll down the page file, you can see the links with lots of options.
3086 The option that we are interested in is the ``:columns=`` one which 3086 The option that we are interested in is the ``:columns=`` one which
3087 tells roundup which fields of the issue to display. Simply add 3087 tells roundup which fields of the issue to display. Simply add
3088 "category" to that list and it all should work. 3088 "category" to that list and it all should work.
3111 3111
3112 the "times" property is the new link to the "timelog" class. 3112 the "times" property is the new link to the "timelog" class.
3113 3113
3114 3. We'll need to let people add in times to the issue, so in the web 3114 3. We'll need to let people add in times to the issue, so in the web
3115 interface we'll have a new entry field. This is a special field 3115 interface we'll have a new entry field. This is a special field
3116 because unlike the other fields in the issue.item template, it 3116 because unlike the other fields in the ``issue.item`` template, it
3117 affects a different item (a timelog item) and not the template's 3117 affects a different item (a timelog item) and not the template's
3118 item, an issue. We have a special syntax for form fields that affect 3118 item (an issue). We have a special syntax for form fields that affect
3119 items other than the template default item (see the cgi 3119 items other than the template default item (see the cgi
3120 documentation on `special form variables`_). In particular, we add a 3120 documentation on `special form variables`_). In particular, we add a
3121 field to capture a new timelog item's perdiod:: 3121 field to capture a new timelog item's period::
3122 3122
3123 <tr> 3123 <tr>
3124 <th>Time Log</th> 3124 <th>Time Log</th>
3125 <td colspan=3><input type="text" name="timelog-1@period" /> 3125 <td colspan=3><input type="text" name="timelog-1@period" />
3126 <br />(enter as '3y 1m 4d 2:40:02' or parts thereof) 3126 <br />(enter as '3y 1m 4d 2:40:02' or parts thereof)
3135 3135
3136 On submission, the "-1" timelog item will be created and assigned a 3136 On submission, the "-1" timelog item will be created and assigned a
3137 real item id. The "times" property of the issue will have the new id 3137 real item id. The "times" property of the issue will have the new id
3138 added to it. 3138 added to it.
3139 3139
3140 4. We want to display a total of the time log times that have been 3140 4. We want to display a total of the timelog times that have been
3141 accumulated for an issue. To do this, we'll need to actually write 3141 accumulated for an issue. To do this, we'll need to actually write
3142 some Python code, since it's beyond the scope of PageTemplates to 3142 some Python code, since it's beyond the scope of PageTemplates to
3143 perform such calculations. We do this by adding a module ``timespent.py`` 3143 perform such calculations. We do this by adding a module ``timespent.py``
3144 to the ``extensions`` directory in our tracker:: 3144 to the ``extensions`` directory in our tracker. The contents of this
3145 file is as follows::
3145 3146
3146 def totalTimeSpent(times): 3147 def totalTimeSpent(times):
3147 ''' Call me with a list of timelog items (which have an 3148 ''' Call me with a list of timelog items (which have an
3148 Interval "period" property) 3149 Interval "period" property)
3149 ''' 3150 '''
3156 instance.registerUtil('totalTimeSpent', totalTimeSpent) 3157 instance.registerUtil('totalTimeSpent', totalTimeSpent)
3157 3158
3158 We will now be able to access the ``totalTimeSpent`` function via the 3159 We will now be able to access the ``totalTimeSpent`` function via the
3159 ``utils`` variable in our templates, as shown in the next step. 3160 ``utils`` variable in our templates, as shown in the next step.
3160 3161
3161 5. Display the time log for an issue:: 3162 5. Display the timelog for an issue::
3162 3163
3163 <table class="otherinfo" tal:condition="context/times"> 3164 <table class="otherinfo" tal:condition="context/times">
3164 <tr><th colspan="3" class="header">Time Log 3165 <tr><th colspan="3" class="header">Time Log
3165 <tal:block 3166 <tal:block
3166 tal:replace="python:utils.totalTimeSpent(context.times)" /> 3167 tal:replace="python:utils.totalTimeSpent(context.times)" />
3177 use of the ``totalTimeSpent`` method which will total up the times 3178 use of the ``totalTimeSpent`` method which will total up the times
3178 for the issue and return a new Interval. That will be automatically 3179 for the issue and return a new Interval. That will be automatically
3179 displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours 3180 displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours
3180 and 40 minutes). 3181 and 40 minutes).
3181 3182
3182 8. If you're using a persistent web server - roundup-server or 3183 8. If you're using a persistent web server - ``roundup-server`` or
3183 mod_python for example - then you'll need to restart that to pick up 3184 ``mod_python`` for example - then you'll need to restart that to pick up
3184 the code changes. When that's done, you'll be able to use the new 3185 the code changes. When that's done, you'll be able to use the new
3185 time logging interface. 3186 time logging interface.
3186 3187
3187 3188
3188 Tracking different types of issues 3189 Tracking different types of issues
3197 this is obvious, but sometimes it's better to actually sit down for a 3198 this is obvious, but sometimes it's better to actually sit down for a
3198 while and think about the schema you're going to implement. 3199 while and think about the schema you're going to implement.
3199 3200
3200 2. Add the new issue class to your tracker's ``schema.py`` - in this 3201 2. Add the new issue class to your tracker's ``schema.py`` - in this
3201 example, we're adding a "system support" class. Just after the "issue" 3202 example, we're adding a "system support" class. Just after the "issue"
3202 class definition in the "open" function, add:: 3203 class definition, add::
3203 3204
3204 support = IssueClass(db, "support", 3205 support = IssueClass(db, "support",
3205 assignedto=Link("user"), topic=Multilink("keyword"), 3206 assignedto=Link("user"), topic=Multilink("keyword"),
3206 status=Link("status"), deadline=Date(), 3207 status=Link("status"), deadline=Date(),
3207 affects=Multilink("system")) 3208 affects=Multilink("system"))
3208 3209
3209 3. Copy the existing "issue.*" (item, search and index) templates in the 3210 3. Copy the existing ``issue.*`` (item, search and index) templates in the
3210 tracker's "html" to "support.*". Edit them so they use the properties 3211 tracker's ``html`` to ``support.*``. Edit them so they use the properties
3211 defined in the "support" class. Be sure to check for hidden form 3212 defined in the ``support`` class. Be sure to check for hidden form
3212 variables like "required" to make sure they have the correct set of 3213 variables like "required" to make sure they have the correct set of
3213 required properties. 3214 required properties.
3214 3215
3215 4. Edit the modules in the "detectors", adding lines to their "init" 3216 4. Edit the modules in the ``detectors``, adding lines to their ``init``
3216 functions where appropriate. Look for "audit" and "react" registrations 3217 functions where appropriate. Look for ``audit`` and ``react`` registrations
3217 on the "issue" class, and duplicate them for "support". 3218 on the ``issue`` class, and duplicate them for ``support``.
3218 3219
3219 5. Create a new sidebar box for the new support class. Duplicate the 3220 5. Create a new sidebar box for the new support class. Duplicate the
3220 existing issues one, changing the "issue" class name to "support". 3221 existing issues one, changing the ``issue`` class name to ``support``.
3221 3222
3222 6. Re-start your tracker and start using the new "support" class. 3223 6. Re-start your tracker and start using the new ``support`` class.
3223 3224
3224 3225
3225 Optionally, you might want to restrict the users able to access this new 3226 Optionally, you might want to restrict the users able to access this new
3226 class to just the users with a new "SysAdmin" Role. To do this, we add 3227 class to just the users with a new "SysAdmin" Role. To do this, we add
3227 some security declarations:: 3228 some security declarations::
3232 3233
3233 You would then (as an "admin" user) edit the details of the appropriate 3234 You would then (as an "admin" user) edit the details of the appropriate
3234 users, and add "SysAdmin" to their Roles list. 3235 users, and add "SysAdmin" to their Roles list.
3235 3236
3236 Alternatively, you might want to change the Edit/View permissions granted 3237 Alternatively, you might want to change the Edit/View permissions granted
3237 for the "issue" class so that it's only available to users with the "System" 3238 for the ``issue`` class so that it's only available to users with the "System"
3238 or "Developer" Role, and then the new class you're adding is available to 3239 or "Developer" Role, and then the new class you're adding is available to
3239 all with the "User" Role. 3240 all with the "User" Role.
3240 3241
3241 3242
3242 Using External User Databases 3243 Using External User Databases
3256 3257
3257 Each user of Roundup must still have their information stored in the Roundup 3258 Each user of Roundup must still have their information stored in the Roundup
3258 database - we just use the passwd file to check their password. To do this, we 3259 database - we just use the passwd file to check their password. To do this, we
3259 need to override the standard ``verifyPassword`` method defined in 3260 need to override the standard ``verifyPassword`` method defined in
3260 ``roundup.cgi.actions.LoginAction`` and register the new class. The 3261 ``roundup.cgi.actions.LoginAction`` and register the new class. The
3261 following is added as ``externapassword.py`` in the tracker ``extensions`` 3262 following is added as ``externalpassword.py`` in the tracker ``extensions``
3262 directory:: 3263 directory::
3263 3264
3264 from roundup.cgi.actions import LoginAction 3265 from roundup.cgi.actions import LoginAction
3265 3266
3266 class ExternalPasswordLoginAction(LoginAction): 3267 class ExternalPasswordLoginAction(LoginAction):
3305 which the users are removed when they no longer have access to a system. 3306 which the users are removed when they no longer have access to a system.
3306 3307
3307 To make use of the passwd file, we therefore synchronise between the two 3308 To make use of the passwd file, we therefore synchronise between the two
3308 user stores. We also use the passwd file to validate the user logins, as 3309 user stores. We also use the passwd file to validate the user logins, as
3309 described in the previous example, `using an external password 3310 described in the previous example, `using an external password
3310 validation source`_. We keep the users lists in sync using a fairly 3311 validation source`_. We keep the user lists in sync using a fairly
3311 simple script that runs once a day, or several times an hour if more 3312 simple script that runs once a day, or several times an hour if more
3312 immediate access is needed. In short, it: 3313 immediate access is needed. In short, it:
3313 3314
3314 1. parses the passwd file, finding usernames, passwords and real names, 3315 1. parses the passwd file, finding usernames, passwords and real names,
3315 2. compares that list to the current roundup user list: 3316 2. compares that list to the current roundup user list:
3320 3321
3321 3. send an email to administrators to let them know what's been done. 3322 3. send an email to administrators to let them know what's been done.
3322 3323
3323 The retiring and updating are simple operations, requiring only a call 3324 The retiring and updating are simple operations, requiring only a call
3324 to ``retire()`` or ``set()``. The creation operation requires more 3325 to ``retire()`` or ``set()``. The creation operation requires more
3325 information though - the user's email address and their roundup Roles. 3326 information though - the user's email address and their Roundup Roles.
3326 We're going to assume that the user's email address is the same as their 3327 We're going to assume that the user's email address is the same as their
3327 login name, so we just append the domain name to that. The Roles are 3328 login name, so we just append the domain name to that. The Roles are
3328 determined using the passwd group identifier - mapping their UN*X group 3329 determined using the passwd group identifier - mapping their UN*X group
3329 to an appropriate set of Roles. 3330 to an appropriate set of Roles.
3330 3331
3436 once an hour / day (or on demand if you can work that into your LDAP store 3437 once an hour / day (or on demand if you can work that into your LDAP store
3437 workflow). See the example `Using a UN*X passwd file as the user database`_ 3438 workflow). See the example `Using a UN*X passwd file as the user database`_
3438 for more information about doing this. 3439 for more information about doing this.
3439 3440
3440 To authenticate off the LDAP store (rather than using the passwords in the 3441 To authenticate off the LDAP store (rather than using the passwords in the
3441 roundup user database) you'd use the same python-ldap module inside an 3442 Roundup user database) you'd use the same python-ldap module inside an
3442 extension to the cgi interface. You'd do this by overriding the method called 3443 extension to the cgi interface. You'd do this by overriding the method called
3443 "verifyPassword" on the LoginAction class in your tracker's interfaces.py 3444 ``verifyPassword`` on the ``LoginAction`` class in your tracker's
3444 module (see `using an external password validation source`_). The method is 3445 ``extensions`` directory (see `using an external password validation
3445 implemented by default as:: 3446 source`_). The method is implemented by default as::
3446 3447
3447 def verifyPassword(self, userid, password): 3448 def verifyPassword(self, userid, password):
3448 ''' Verify the password that the user has supplied 3449 ''' Verify the password that the user has supplied
3449 ''' 3450 '''
3450 stored = self.db.user.get(self.userid, 'password') 3451 stored = self.db.user.get(self.userid, 'password')
3565 which filters out the users that have the vacation flag set to true. 3566 which filters out the users that have the vacation flag set to true.
3566 3567
3567 Adding in state transition control 3568 Adding in state transition control
3568 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3569 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3569 3570
3570 Sometimes tracker admins want to control the states that users may move 3571 Sometimes tracker admins want to control the states to which users may
3571 issues to. You can do this by following these steps: 3572 move issues. You can do this by following these steps:
3572 3573
3573 1. make "status" a required variable. This is achieved by adding the 3574 1. make "status" a required variable. This is achieved by adding the
3574 following to the top of the form in the ``issue.item.html`` 3575 following to the top of the form in the ``issue.item.html``
3575 template:: 3576 template::
3576 3577
3577 <input type="hidden" name="@required" value="status"> 3578 <input type="hidden" name="@required" value="status">
3578 3579
3579 this will force users to select a status. 3580 This will force users to select a status.
3580 3581
3581 2. add a Multilink property to the status class:: 3582 2. add a Multilink property to the status class::
3582 3583
3583 stat = Class(db, "status", ... , transitions=Multilink('status'), 3584 stat = Class(db, "status", ... , transitions=Multilink('status'),
3584 ...) 3585 ...)
3585 3586
3586 and then edit the statuses already created, either: 3587 and then edit the statuses already created, either:
3587 3588
3588 a. through the web using the class list -> status class editor, or 3589 a. through the web using the class list -> status class editor, or
3589 b. using the roundup-admin "set" command. 3590 b. using the ``roundup-admin`` "set" command.
3590 3591
3591 3. add an auditor module ``checktransition.py`` in your tracker's 3592 3. add an auditor module ``checktransition.py`` in your tracker's
3592 ``detectors`` directory, for example:: 3593 ``detectors`` directory, for example::
3593 3594
3594 def checktransition(db, cl, nodeid, newvalues): 3595 def checktransition(db, cl, nodeid, newvalues):
3641 3642
3642 We needed the ability to mark certain issues as "blockers" - that is, 3643 We needed the ability to mark certain issues as "blockers" - that is,
3643 they can't be resolved until another issue (the blocker) they rely on is 3644 they can't be resolved until another issue (the blocker) they rely on is
3644 resolved. To achieve this: 3645 resolved. To achieve this:
3645 3646
3646 1. Create a new property on the issue Class, 3647 1. Create a new property on the ``issue`` class:
3647 ``blockers=Multilink("issue")``. Edit your tracker's schema.py file. 3648 ``blockers=Multilink("issue")``. To do this, edit the definition of
3648 Where the "issue" class is defined, something like:: 3649 this class in your tracker's ``schema.py`` file. Change this::
3649 3650
3650 issue = IssueClass(db, "issue", 3651 issue = IssueClass(db, "issue",
3651 assignedto=Link("user"), topic=Multilink("keyword"), 3652 assignedto=Link("user"), topic=Multilink("keyword"),
3652 priority=Link("priority"), status=Link("status")) 3653 priority=Link("priority"), status=Link("status"))
3653 3654
3654 add the blockers entry like so:: 3655 to this, adding the blockers entry::
3655 3656
3656 issue = IssueClass(db, "issue", 3657 issue = IssueClass(db, "issue",
3657 blockers=Multilink("issue"), 3658 blockers=Multilink("issue"),
3658 assignedto=Link("user"), topic=Multilink("keyword"), 3659 assignedto=Link("user"), topic=Multilink("keyword"),
3659 priority=Link("priority"), status=Link("status")) 3660 priority=Link("priority"), status=Link("status"))
3660 3661
3661 2. Add the new "blockers" property to the issue.item edit page, using 3662 2. Add the new ``blockers`` property to the ``issue.item.html`` edit
3662 something like:: 3663 page, using something like::
3663 3664
3664 <th>Waiting On</th> 3665 <th>Waiting On</th>
3665 <td> 3666 <td>
3666 <span tal:replace="structure python:context.blockers.field(showid=1, 3667 <span tal:replace="structure python:context.blockers.field(showid=1,
3667 size=20)" /> 3668 size=20)" />
3675 You'll need to fiddle with your item page layout to find an 3676 You'll need to fiddle with your item page layout to find an
3676 appropriate place to put it - I'll leave that fun part up to you. 3677 appropriate place to put it - I'll leave that fun part up to you.
3677 Just make sure it appears in the first table, possibly somewhere near 3678 Just make sure it appears in the first table, possibly somewhere near
3678 the "superseders" field. 3679 the "superseders" field.
3679 3680
3680 3. Create a new detector module (attached) which enforces the rules: 3681 3. Create a new detector module (see below) which enforces the rules:
3681 3682
3682 - issues may not be resolved if they have blockers 3683 - issues may not be resolved if they have blockers
3683 - when a blocker is resolved, it's removed from issues it blocks 3684 - when a blocker is resolved, it's removed from issues it blocks
3684 3685
3685 The contents of the detector should be something like this:: 3686 The contents of the detector should be something like this::
3753 URLs so they filter out issues with any blockers. You do this by 3754 URLs so they filter out issues with any blockers. You do this by
3754 adding an additional filter on "blockers" for the value "-1". For 3755 adding an additional filter on "blockers" for the value "-1". For
3755 example, the existing "Show All" link in the "page" template (in the 3756 example, the existing "Show All" link in the "page" template (in the
3756 tracker's "html" directory) looks like this:: 3757 tracker's "html" directory) looks like this::
3757 3758
3758 <a href="issue?:sort=-activity&:group=priority&:filter=status& 3759 <a href="issue?@sort=-activity&@group=priority&@filter=status&
3759 :columns=id,activity,title,creator,assignedto,status& 3760 @columns=id,activity,title,creator,assignedto,status&
3760 status=-1,1,2,3,4,5,6,7">Show All</a><br> 3761 status=-1,1,2,3,4,5,6,7">Show All</a><br>
3761 3762
3762 modify it to add the "blockers" info to the URL (note, both the 3763 modify it to add the "blockers" info to the URL (note, both the
3763 ":filter" *and* "blockers" values must be specified):: 3764 "@filter" *and* "blockers" values must be specified)@@
3764 3765
3765 <a href="issue?:sort=-activity&:group=priority&:filter=status,blockers& 3766 <a href="issue?@sort=-activity&@group=priority&@filter=status,blockers&
3766 blockers=-1&:columns=id,activity,title,creator,assignedto,status& 3767 blockers=-1&@columns=id,activity,title,creator,assignedto,status&
3767 status=-1,1,2,3,4,5,6,7">Show All</a><br> 3768 status=-1,1,2,3,4,5,6,7">Show All</a><br>
3768 3769
3769 The above examples are line-wrapped on the trailing & and should 3770 The above examples are line-wrapped on the trailing & and should
3770 be unwrapped. 3771 be unwrapped.
3771 3772
3776 another issue's "blockers" property. 3777 another issue's "blockers" property.
3777 3778
3778 Add users to the nosy list based on the topic 3779 Add users to the nosy list based on the topic
3779 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3780 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3780 3781
3781 We need the ability to automatically add users to the nosy list based 3782 Let's say we need the ability to automatically add users to the nosy
3782 on the occurence of a topic. Every user should be allowed to edit his 3783 list based
3783 own list of topics for which he wants to be added to the nosy list. 3784 on the occurance of a topic. Every user should be allowed to edit their
3784 3785 own list of topics for which they want to be added to the nosy list.
3785 Below will be showed that such a change can be performed with only 3786
3786 minimal understanding of the roundup system, but with clever use 3787 Below, we'll show that this change can be done with minimal
3787 of Copy and Paste. 3788 understanding of the Roundup system, using only copy and paste.
3788 3789
3789 This requires three changes to the tracker: a change in the database to 3790 This requires three changes to the tracker: a change in the database to
3790 allow per-user recording of the lists of topics for which he wants to 3791 allow per-user recording of the lists of topics for which he wants to
3791 be put on the nosy list, a change in the user view allowing to edit 3792 be put on the nosy list, a change in the user view allowing them to edit
3792 this list of topics, and addition of an auditor which updates the nosy 3793 this list of topics, and addition of an auditor which updates the nosy
3793 list when a topic is set. 3794 list when a topic is set.
3794 3795
3795 Adding the nosy topic list 3796 Adding the nosy topic list
3796 :::::::::::::::::::::::::: 3797 ::::::::::::::::::::::::::
3797 3798
3798 The change in the database to make is that for any user there should be 3799 The change to make in the database, is that for any user there should be
3799 a list of topics for which he wants to be put on the nosy list. Adding 3800 a list of topics for which he wants to be put on the nosy list. Adding
3800 a ``Multilink`` of ``keyword`` seem to fullfill this (note that within 3801 a ``Multilink`` of ``keyword`` seems to fullfill this (note that within
3801 the code topics are called ``keywords``.) As such, all what has to be 3802 the code, topics are called ``keywords``.) As such, all that has to be
3802 done is to add a new field to the definition of ``user`` within the 3803 done is to add a new field to the definition of ``user`` within the
3803 file ``schema.py``. We will call this new field ``nosy_keywords``, and 3804 file ``schema.py``. We will call this new field ``nosy_keywords``, and
3804 the updated definition of user will be:: 3805 the updated definition of user will be::
3805 3806
3806 user = Class(db, "user", 3807 user = Class(db, "user",
3816 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 3817 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
3817 3818
3818 We want any user to be able to change the list of topics for which 3819 We want any user to be able to change the list of topics for which
3819 he will by default be added to the nosy list. We choose to add this 3820 he will by default be added to the nosy list. We choose to add this
3820 to the user view, as is generated by the file ``html/user.item.html``. 3821 to the user view, as is generated by the file ``html/user.item.html``.
3821 We easily can 3822 We can easily
3822 see that the topic field in the issue view has very similar editting 3823 see that the topic field in the issue view has very similar editing
3823 requirements as our nosy topics, both being a list of topics. As 3824 requirements as our nosy topics, both being lists of topics. As
3824 such, we search for Topics in ``issue.item.html``, and extract the 3825 such, we look for Topics in ``issue.item.html``, and extract the
3825 associated parts from there. We add this to ``user.item.html`` at the 3826 associated parts from there. We add this to ``user.item.html`` at the
3826 bottom of the list of viewed items (i.e. just below the 'Alternate 3827 bottom of the list of viewed items (i.e. just below the 'Alternate
3827 E-mail addresses' in the classic template):: 3828 E-mail addresses' in the classic template)::
3828 3829
3829 <tr> 3830 <tr>
3836 3837
3837 3838
3838 Addition of an auditor to update the nosy list 3839 Addition of an auditor to update the nosy list
3839 :::::::::::::::::::::::::::::::::::::::::::::: 3840 ::::::::::::::::::::::::::::::::::::::::::::::
3840 3841
3841 The more difficult part is the addition of the logic to actually 3842 The more difficult part is the logic to add
3842 at the users to the nosy list when it is required. 3843 the users to the nosy list when required.
3843 The choice is made to perform this action when the topics on an 3844 We choose to perform this action whenever the topics on an
3844 item are set, including when an item is created. 3845 item are set (this includes the creation of items).
3845 Here we choose to start out with a copy of the 3846 Here we choose to start out with a copy of the
3846 ``detectors/nosyreaction.py`` detector, which we copy to the file 3847 ``detectors/nosyreaction.py`` detector, which we copy to the file
3847 ``detectors/nosy_keyword_reaction.py``. 3848 ``detectors/nosy_keyword_reaction.py``.
3848 This looks like a good start as it also adds users 3849 This looks like a good start as it also adds users
3849 to the nosy list. A look through the code reveals that the 3850 to the nosy list. A look through the code reveals that the
3850 ``nosyreaction`` function actually is sending the e-mail, which 3851 ``nosyreaction`` function actually sends the e-mail.
3851 we do not need. As such, we can change the init function to:: 3852 We don't need this. Therefore, we can change the ``init`` function to::
3852 3853
3853 def init(db): 3854 def init(db):
3854 db.issue.audit('create', update_kw_nosy) 3855 db.issue.audit('create', update_kw_nosy)
3855 db.issue.audit('set', update_kw_nosy) 3856 db.issue.audit('set', update_kw_nosy)
3856 3857
3857 After that we rename the ``updatenosy`` function to ``update_kw_nosy``. 3858 After that, we rename the ``updatenosy`` function to ``update_kw_nosy``.
3858 The first two blocks of code in that function relate to settings 3859 The first two blocks of code in that function relate to setting
3859 ``current`` to a combination of the old and new nosy lists. This 3860 ``current`` to a combination of the old and new nosy lists. This
3860 functionality is left in the new auditor. The following block of 3861 functionality is left in the new auditor. The following block of
3861 code, which in ``updatenosy`` handled adding the assignedto user(s) 3862 code, which handled adding the assignedto user(s) to the nosy list in
3862 to the nosy list, should be replaced by a block of code to add the 3863 ``updatenosy``, should be replaced by a block of code to add the
3863 interested users to the nosy list. We choose here to loop over all 3864 interested users to the nosy list. We choose here to loop over all
3864 new topics, than loop over all users, 3865 new topics, than looping over all users,
3865 and assign the user to the nosy list when the topic in the user's 3866 and assign the user to the nosy list when the topic occurs in the user's
3866 nosy_keywords. The next part in ``updatenosy``, adding the author 3867 ``nosy_keywords``. The next part in ``updatenosy`` -- adding the author
3867 and/or recipients of a message to the nosy list, obviously is not 3868 and/or recipients of a message to the nosy list -- is obviously not
3868 relevant here and thus is deleted from the new auditor. The last 3869 relevant here and is thus deleted from the new auditor. The last
3869 part, copying the new nosy list to newvalues, does not have to be changed. 3870 part, copying the new nosy list to ``newvalues``, can stay as is.
3870 This brings the following function:: 3871 This results in the following function::
3871 3872
3872 def update_kw_nosy(db, cl, nodeid, newvalues): 3873 def update_kw_nosy(db, cl, nodeid, newvalues):
3873 '''Update the nosy list for changes to the topics 3874 '''Update the nosy list for changes to the topics
3874 ''' 3875 '''
3875 # nodeid will be None if this is a new node 3876 # nodeid will be None if this is a new node
3911 current[user_id] = 1 3912 current[user_id] = 1
3912 3913
3913 # that's it, save off the new nosy list 3914 # that's it, save off the new nosy list
3914 newvalues['nosy'] = current.keys() 3915 newvalues['nosy'] = current.keys()
3915 3916
3916 and these two function are the only ones needed in the file. 3917 These two function are the only ones needed in the file.
3917 3918
3918 TODO: update this example to use the find() Class method. 3919 TODO: update this example to use the ``find()`` Class method.
3919 3920
3920 Caveats 3921 Caveats
3921 ::::::: 3922 :::::::
3922 3923
3923 A few problems with the design here can be noted: 3924 A few problems with the design here can be noted:
3924 3925
3925 Multiple additions 3926 Multiple additions
3926 When a user, after automatic selection, is manually removed 3927 When a user, after automatic selection, is manually removed
3927 from the nosy list, he again is added to the nosy list when the 3928 from the nosy list, he is added to the nosy list again when the
3928 topic list of the issue is updated. A better design might be 3929 topic list of the issue is updated. A better design might be
3929 to only check which topics are new compared to the old list 3930 to only check which topics are new compared to the old list
3930 of topics, and only add users when they have indicated 3931 of topics, and only add users when they have indicated
3931 interest on a new topic. 3932 interest on a new topic.
3932 3933
3933 The code could also be changed to only trigger on the create() event, 3934 The code could also be changed to only trigger on the ``create()``
3934 rather than also on the set() event, thus only setting the nosy list 3935 event, rather than also on the ``set()`` event, thus only setting
3935 when the issue is created. 3936 the nosy list when the issue is created.
3936 3937
3937 Scalability 3938 Scalability
3938 In the auditor there is a loop over all users. For a site with 3939 In the auditor, there is a loop over all users. For a site with
3939 only few users this will pose no serious problem, however, with 3940 only few users this will pose no serious problem; however, with
3940 many users this will be a serious performance bottleneck. 3941 many users this will be a serious performance bottleneck.
3941 A way out will be to link from the topics to the users which 3942 A way out would be to link from the topics to the users who
3942 selected these topics a nosy topics. This will eliminate the 3943 selected these topics as nosy topics. This will eliminate the
3943 loop over all users. 3944 loop over all users.
3944 3945
3945 Changes to Security and Permissions 3946 Changes to Security and Permissions
3946 ----------------------------------- 3947 -----------------------------------
3947 3948
3960 3961
3961 3. Then assign the new Permission to your "Developer" Role:: 3962 3. Then assign the new Permission to your "Developer" Role::
3962 3963
3963 db.security.addPermissionToRole('Developer', p) 3964 db.security.addPermissionToRole('Developer', p)
3964 3965
3965 4. In the issue item edit page ("html/issue.item.html" in your tracker 3966 4. In the issue item edit page (``html/issue.item.html`` in your tracker
3966 directory), use the new Permission in restricting the "assignedto" 3967 directory), use the new Permission in restricting the "assignedto"
3967 list:: 3968 list::
3968 3969
3969 <select name="assignedto"> 3970 <select name="assignedto">
3970 <option value="-1">- no selection -</option> 3971 <option value="-1">- no selection -</option>
3977 tal:content="user/realname"></option> 3978 tal:content="user/realname"></option>
3978 </tal:block> 3979 </tal:block>
3979 </select> 3980 </select>
3980 3981
3981 For extra security, you may wish to setup an auditor to enforce the 3982 For extra security, you may wish to setup an auditor to enforce the
3982 Permission requirement (install this as "assignedtoFixer.py" in your 3983 Permission requirement (install this as ``assignedtoFixer.py`` in your
3983 tracker "detectors" directory):: 3984 tracker ``detectors`` directory)::
3984 3985
3985 def assignedtoMustBeFixer(db, cl, nodeid, newvalues): 3986 def assignedtoMustBeFixer(db, cl, nodeid, newvalues):
3986 ''' Ensure the assignedto value in newvalues is a used with the 3987 ''' Ensure the assignedto value in newvalues is used with the
3987 Fixer Permission 3988 Fixer Permission
3988 ''' 3989 '''
3989 if not newvalues.has_key('assignedto'): 3990 if not newvalues.has_key('assignedto'):
3990 # don't care 3991 # don't care
3991 return 3992 return
4004 4005
4005 4006
4006 Users may only edit their issues 4007 Users may only edit their issues
4007 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4008 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4008 4009
4009 Users registering themselves are granted Provisional access - meaning they 4010 In this case, users registering themselves are granted Provisional
4011 access, meaning they
4010 have access to edit the issues they submit, but not others. We create a new 4012 have access to edit the issues they submit, but not others. We create a new
4011 Role called "Provisional User" which is granted to newly-registered users, 4013 Role called "Provisional User" which is granted to newly-registered users,
4012 and has limited access. One of the Permissions they have is the new "Edit 4014 and has limited access. One of the Permissions they have is the new "Edit
4013 Own" on issues (regular users have "Edit".) 4015 Own" on issues (regular users have "Edit".)
4014 4016
4026 db.security.addPermissionToRole('Provisional User', 'Create', 'issue') 4028 db.security.addPermissionToRole('Provisional User', 'Create', 'issue')
4027 def own_issue(db, userid, itemid): 4029 def own_issue(db, userid, itemid):
4028 '''Determine whether the userid matches the creator of the issue.''' 4030 '''Determine whether the userid matches the creator of the issue.'''
4029 return userid == db.issue.get(itemid, 'creator') 4031 return userid == db.issue.get(itemid, 'creator')
4030 p = db.security.addPermission(name='Edit', klass='issue', 4032 p = db.security.addPermission(name='Edit', klass='issue',
4031 code=own_issue, description='Can only edit own issues') 4033 check=own_issue, description='Can only edit own issues')
4032 db.security.addPermissionToRole('Provisional User', p) 4034 db.security.addPermissionToRole('Provisional User', p)
4033 p = db.security.addPermission(name='View', klass='issue', 4035 p = db.security.addPermission(name='View', klass='issue',
4034 code=own_issue, description='Can only view own issues') 4036 check=own_issue, description='Can only view own issues')
4035 db.security.addPermissionToRole('Provisional User', p) 4037 db.security.addPermissionToRole('Provisional User', p)
4036 4038
4037 # Assign the Permissions for issue-related classes 4039 # Assign the Permissions for issue-related classes
4038 for cl in 'file', 'msg', 'query', 'keyword': 4040 for cl in 'file', 'msg', 'query', 'keyword':
4039 db.security.addPermissionToRole('Provisional User', 'View', cl) 4041 db.security.addPermissionToRole('Provisional User', 'View', cl)
4045 # and give the new users access to the web and email interface 4047 # and give the new users access to the web and email interface
4046 db.security.addPermissionToRole('Provisional User', 'Web Access') 4048 db.security.addPermissionToRole('Provisional User', 'Web Access')
4047 db.security.addPermissionToRole('Provisional User', 'Email Access') 4049 db.security.addPermissionToRole('Provisional User', 'Email Access')
4048 4050
4049 4051
4050 Then in the ``config.ini`` we change the Role assigned to newly-registered 4052 Then, in ``config.ini``, we change the Role assigned to newly-registered
4051 users, replacing the existing ``'User'`` values:: 4053 users, replacing the existing ``'User'`` values::
4052 4054
4053 [main] 4055 [main]
4054 ... 4056 ...
4055 new_web_user_roles = 'Provisional User' 4057 new_web_user_roles = 'Provisional User'
4080 --------------------------------- 4082 ---------------------------------
4081 4083
4082 Adding action links to the index page 4084 Adding action links to the index page
4083 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4085 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4084 4086
4085 Add a column to the item.index.html template. 4087 Add a column to the ``item.index.html`` template.
4086 4088
4087 Resolving the issue:: 4089 Resolving the issue::
4088 4090
4089 <a tal:attributes="href 4091 <a tal:attributes="href
4090 string:issue${i/id}?:status=resolved&:action=edit">resolve</a> 4092 string:issue${i/id}?:status=resolved&:action=edit">resolve</a>
4092 "Take" the issue:: 4094 "Take" the issue::
4093 4095
4094 <a tal:attributes="href 4096 <a tal:attributes="href
4095 string:issue${i/id}?:assignedto=${request/user/id}&:action=edit">take</a> 4097 string:issue${i/id}?:assignedto=${request/user/id}&:action=edit">take</a>
4096 4098
4097 ... and so on 4099 ... and so on.
4098 4100
4099 Colouring the rows in the issue index according to priority 4101 Colouring the rows in the issue index according to priority
4100 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4102 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4101 4103
4102 A simple ``tal:attributes`` statement will do the bulk of the work here. In 4104 A simple ``tal:attributes`` statement will do the bulk of the work here. In
4103 the ``issue.index.html`` template, add to the ``<tr>`` that displays the 4105 the ``issue.index.html`` template, add this to the ``<tr>`` that
4104 actual rows of data:: 4106 displays the rows of data::
4105 4107
4106 <tr tal:attributes="class string:priority-${i/priority/plain}"> 4108 <tr tal:attributes="class string:priority-${i/priority/plain}">
4107 4109
4108 and then in your stylesheet (``style.css``) specify the colouring for the 4110 and then in your stylesheet (``style.css``) specify the colouring for the
4109 different priorities, like:: 4111 different priorities, as follows::
4110 4112
4111 tr.priority-critical td { 4113 tr.priority-critical td {
4112 background-color: red; 4114 background-color: red;
4113 } 4115 }
4114 4116
4148 <td tal:condition="request/show/status" 4150 <td tal:condition="request/show/status"
4149 tal:content="structure i/status/field">&nbsp;</td> 4151 tal:content="structure i/status/field">&nbsp;</td>
4150 4152
4151 this will result in an edit field for the status property. 4153 this will result in an edit field for the status property.
4152 4154
4153 3. after the ``tal:block`` which lists the actual index items (marked by 4155 3. after the ``tal:block`` which lists the index items (marked by
4154 ``tal:repeat="i batch"``) add a new table row:: 4156 ``tal:repeat="i batch"``) add a new table row::
4155 4157
4156 <tr> 4158 <tr>
4157 <td tal:attributes="colspan python:len(request.columns)"> 4159 <td tal:attributes="colspan python:len(request.columns)">
4158 <input type="submit" value=" Save Changes "> 4160 <input type="submit" value=" Save Changes ">
4160 <tal:block replace="structure request/indexargs_form" /> 4162 <tal:block replace="structure request/indexargs_form" />
4161 </td> 4163 </td>
4162 </tr> 4164 </tr>
4163 4165
4164 which gives us a submit button, indicates that we are performing an edit 4166 which gives us a submit button, indicates that we are performing an edit
4165 on any changed statuses and the final block will make sure that the 4167 on any changed statuses. The final ``tal:block`` will make sure that the
4166 current index view parameters (filtering, columns, etc) will be used in 4168 current index view parameters (filtering, columns, etc) will be used in
4167 rendering the next page (the results of the editing). 4169 rendering the next page (the results of the editing).
4168 4170
4169 4171
4170 Displaying only message summaries in the issue display 4172 Displaying only message summaries in the issue display
4171 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4173 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4172 4174
4173 Alter the issue.item template section for messages to:: 4175 Alter the ``issue.item`` template section for messages to::
4174 4176
4175 <table class="messages" tal:condition="context/messages"> 4177 <table class="messages" tal:condition="context/messages">
4176 <tr><th colspan="5" class="header">Messages</th></tr> 4178 <tr><th colspan="5" class="header">Messages</th></tr>
4177 <tr tal:repeat="msg context/messages"> 4179 <tr tal:repeat="msg context/messages">
4178 <td><a tal:attributes="href string:msg${msg/id}" 4180 <td><a tal:attributes="href string:msg${msg/id}"
4192 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4194 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4193 4195
4194 This is pretty simple - all we need to do is copy the code from the 4196 This is pretty simple - all we need to do is copy the code from the
4195 example `displaying only message summaries in the issue display`_ into 4197 example `displaying only message summaries in the issue display`_ into
4196 our template alongside the summary display, and then introduce a switch 4198 our template alongside the summary display, and then introduce a switch
4197 that shows either one or the other. We'll use a new form variable, 4199 that shows either the one or the other. We'll use a new form variable,
4198 ``@whole_messages`` to achieve this:: 4200 ``@whole_messages`` to achieve this::
4199 4201
4200 <table class="messages" tal:condition="context/messages"> 4202 <table class="messages" tal:condition="context/messages">
4201 <tal:block tal:condition="not:request/form/@whole_messages/value | python:0"> 4203 <tal:block tal:condition="not:request/form/@whole_messages/value | python:0">
4202 <tr><th colspan="3" class="header">Messages</th> 4204 <tr><th colspan="3" class="header">Messages</th>
4269 . 4271 .
4270 . 4272 .
4271 . 4273 .
4272 </form> 4274 </form>
4273 4275
4274 Note that later in the form, I test the value of "cat" include form 4276 Note that later in the form, I use the value of "cat" to decide which
4275 elements that are appropriate. For example:: 4277 form elements should be displayed. For example::
4276 4278
4277 <tal:block tal:condition="python:cat in '6 10 13 14 15 16 17'.split()"> 4279 <tal:block tal:condition="python:cat in '6 10 13 14 15 16 17'.split()">
4278 <tr> 4280 <tr>
4279 <th>Operating System</th> 4281 <th>Operating System</th>
4280 <td tal:content="structure context/os/field"></td> 4282 <td tal:content="structure context/os/field"></td>

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