Mercurial > p > roundup > code
comparison doc/customizing.txt @ 3131:96086801bd61 maint-0.8
merge from HEAD
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sat, 12 Feb 2005 00:54:36 +0000 |
| parents | f0051e4bc8b7 |
| children | 5ce9c9a1399d |
comparison
equal
deleted
inserted
replaced
| 3128:f0051e4bc8b7 | 3131:96086801bd61 |
|---|---|
| 1 =================== | 1 =================== |
| 2 Customising Roundup | 2 Customising Roundup |
| 3 =================== | 3 =================== |
| 4 | 4 |
| 5 :Version: $Revision: 1.161.2.7 $ | 5 :Version: $Revision: 1.161.2.8 $ |
| 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. |
| 2585 In both cases, the value is a valid charset name (eg. ``utf-8`` or | 2585 In both cases, the value is a valid charset name (eg. ``utf-8`` or |
| 2586 ``kio8-r``). | 2586 ``kio8-r``). |
| 2587 | 2587 |
| 2588 Inside Roundup, all strings are stored and processed in utf-8. | 2588 Inside Roundup, all strings are stored and processed in utf-8. |
| 2589 Unfortunately, some older browsers do not work properly with | 2589 Unfortunately, some older browsers do not work properly with |
| 2590 utf8-encoded pages (e.g. Netscape Navigator 4 displays wrong | 2590 utf-8-encoded pages (e.g. Netscape Navigator 4 displays wrong |
| 2591 characters in form fields). This version allows to change | 2591 characters in form fields). This version allows one to change |
| 2592 the character set for http transfers. To do so, you may add | 2592 the character set for http transfers. To do so, you may add |
| 2593 the following code to your ``page.html`` template:: | 2593 the following code to your ``page.html`` template:: |
| 2594 | 2594 |
| 2595 <tal:block define="uri string:${request/base}${request/env/PATH_INFO}"> | 2595 <tal:block define="uri string:${request/base}${request/env/PATH_INFO}"> |
| 2596 <a tal:attributes="href python:request.indexargs_href(uri, | 2596 <a tal:attributes="href python:request.indexargs_url(uri, |
| 2597 {'@charset':'utf-8'})">utf-8</a> | 2597 {'@charset':'utf-8'})">utf-8</a> |
| 2598 <a tal:attributes="href python:request.indexargs_href(uri, | 2598 <a tal:attributes="href python:request.indexargs_url(uri, |
| 2599 {'@charset':'koi8-r'})">koi8-r</a> | 2599 {'@charset':'koi8-r'})">koi8-r</a> |
| 2600 </tal:block> | 2600 </tal:block> |
| 2601 | 2601 |
| 2602 (substitute ``koi8-r`` with appropriate charset for your language). | 2602 (substitute ``koi8-r`` with appropriate charset for your language). |
| 2603 Charset preference is kept in the browser cookie ``roundup_charset``. | 2603 Charset preference is kept in the browser cookie ``roundup_charset``. |
| 2604 | 2604 |
| 2605 Lines ``meta http-equiv`` added to the tracker templates in version 0.6.0 | 2605 ``meta http-equiv`` lines added to the tracker templates in version 0.6.0 |
| 2606 should be changed to include actual character set name:: | 2606 should be changed to include actual character set name:: |
| 2607 | 2607 |
| 2608 <meta http-equiv="Content-Type" | 2608 <meta http-equiv="Content-Type" |
| 2609 tal:attributes="content string:text/html;; charset=${request/client/charset}" | 2609 tal:attributes="content string:text/html;; charset=${request/client/charset}" |
| 2610 /> | 2610 /> |
| 2675 | 2675 |
| 2676 | 2676 |
| 2677 Introduction | 2677 Introduction |
| 2678 :::::::::::: | 2678 :::::::::::: |
| 2679 | 2679 |
| 2680 To make the classic schema of roundup useful as a TODO tracking system | 2680 To make the classic schema of Roundup useful as a TODO tracking system |
| 2681 for a group of systems administrators, it needed an extra data field per | 2681 for a group of systems administrators, it needs an extra data field per |
| 2682 issue: a category. | 2682 issue: a category. |
| 2683 | 2683 |
| 2684 This would let sysadmins quickly list all TODOs in their particular area | 2684 This would let sysadmins quickly list all TODOs in their particular area |
| 2685 of interest without having to do complex queries, and without relying on | 2685 of interest without having to do complex queries, and without relying on |
| 2686 the spelling capabilities of other sysadmins (a losing proposition at | 2686 the spelling capabilities of other sysadmins (a losing proposition at |
| 2690 Adding a field to the database | 2690 Adding a field to the database |
| 2691 :::::::::::::::::::::::::::::: | 2691 :::::::::::::::::::::::::::::: |
| 2692 | 2692 |
| 2693 This is the easiest part of the change. The category would just be a | 2693 This is the easiest part of the change. The category would just be a |
| 2694 plain string, nothing fancy. To change what is in the database you need | 2694 plain string, nothing fancy. To change what is in the database you need |
| 2695 to add some lines to the ``open()`` function in ``schema.py``. Under the | 2695 to add some lines to the ``schema.py`` file of your tracker instance. |
| 2696 comment:: | 2696 Under the comment:: |
| 2697 | 2697 |
| 2698 # add any additional database schema configuration here | 2698 # add any additional database schema configuration here |
| 2699 | 2699 |
| 2700 add:: | 2700 add:: |
| 2701 | 2701 |
| 2712 This also means that there can only be one category with a given name. | 2712 This also means that there can only be one category with a given name. |
| 2713 | 2713 |
| 2714 Adding the above lines allows us to create categories, but they're not | 2714 Adding the above lines allows us to create categories, but they're not |
| 2715 tied to the issues that we are going to be creating. It's just a list of | 2715 tied to the issues that we are going to be creating. It's just a list of |
| 2716 categories off on its own, which isn't much use. We need to link it in | 2716 categories off on its own, which isn't much use. We need to link it in |
| 2717 with the issues. To do that, find the lines in the ``open()`` function | 2717 with the issues. To do that, find the lines |
| 2718 in ``schema.py`` which set up the "issue" class, and then add a link to | 2718 in ``schema.py`` which set up the "issue" class, and then add a link to |
| 2719 the category:: | 2719 the category:: |
| 2720 | 2720 |
| 2721 issue = IssueClass(db, "issue", ... , | 2721 issue = IssueClass(db, "issue", ... , |
| 2722 category=Multilink("category"), ... ) | 2722 category=Multilink("category"), ... ) |
| 2730 | 2730 |
| 2731 | 2731 |
| 2732 Populating the new category class | 2732 Populating the new category class |
| 2733 ::::::::::::::::::::::::::::::::: | 2733 ::::::::::::::::::::::::::::::::: |
| 2734 | 2734 |
| 2735 If you haven't initialised the database with the roundup-admin | 2735 If you haven't initialised the database with the ``roundup-admin`` |
| 2736 "initialise" command, then you can add the following to the tracker | 2736 "initialise" command, then you can add the following to the tracker |
| 2737 ``schema.py`` in the ``init()`` function under the comment:: | 2737 ``initial_data.py`` under the comment:: |
| 2738 | 2738 |
| 2739 # add any additional database create steps here - but only if you | 2739 # add any additional database creation steps here - but only if you |
| 2740 # haven't initialised the database with the admin "initialise" command | 2740 # haven't initialised the database with the admin "initialise" command |
| 2741 | 2741 |
| 2742 Add:: | 2742 Add:: |
| 2743 | 2743 |
| 2744 category = db.getclass('category') | 2744 category = db.getclass('category') |
| 2771 doesn't suit us, as we want any user to be able to create new categories | 2771 doesn't suit us, as we want any user to be able to create new categories |
| 2772 as required, and obviously everyone needs to be able to view the | 2772 as required, and obviously everyone needs to be able to view the |
| 2773 categories of issues for it to be useful. | 2773 categories of issues for it to be useful. |
| 2774 | 2774 |
| 2775 We therefore need to change the security of the category objects. This | 2775 We therefore need to change the security of the category objects. This |
| 2776 is also done in the ``open()`` function of ``schema.py``. | 2776 is also done in ``schema.py``. |
| 2777 | 2777 |
| 2778 There are currently two loops which set up permissions and then assign | 2778 There are currently two loops which set up permissions and then assign |
| 2779 them to various roles. Simply add the new "category" to both lists:: | 2779 them to various roles. Simply add the new "category" to both lists:: |
| 2780 | 2780 |
| 2781 # Assign the access and edit permissions for issue, file and message | 2781 # Assign the access and edit permissions for issue, file and message |
| 2784 p = db.security.getPermission('View', cl) | 2784 p = db.security.getPermission('View', cl) |
| 2785 db.security.addPermissionToRole('User', 'View', cl) | 2785 db.security.addPermissionToRole('User', 'View', cl) |
| 2786 db.security.addPermissionToRole('User', 'Edit', cl) | 2786 db.security.addPermissionToRole('User', 'Edit', cl) |
| 2787 db.security.addPermissionToRole('User', 'Create', cl) | 2787 db.security.addPermissionToRole('User', 'Create', cl) |
| 2788 | 2788 |
| 2789 These lines assign the View and Edit Permissions to the "User" role, so | 2789 These lines assign the "View" and "Edit" Permissions to the "User" role, |
| 2790 that normal users can view and edit "category" objects. | 2790 so that normal users can view and edit "category" objects. |
| 2791 | 2791 |
| 2792 This is all the work that needs to be done for the database. It will | 2792 This is all the work that needs to be done for the database. It will |
| 2793 store categories, and let users view and edit them. Now on to the | 2793 store categories, and let users view and edit them. Now on to the |
| 2794 interface stuff. | 2794 interface stuff. |
| 2795 | 2795 |
| 2798 :::::::::::::::::::::::::::::::: | 2798 :::::::::::::::::::::::::::::::: |
| 2799 | 2799 |
| 2800 We need to give the users the ability to create new categories, and the | 2800 We need to give the users the ability to create new categories, and the |
| 2801 place to put the link to this functionality is in the left hand function | 2801 place to put the link to this functionality is in the left hand function |
| 2802 bar, under the "Issues" area. The file that defines how this area looks | 2802 bar, under the "Issues" area. The file that defines how this area looks |
| 2803 is ``html/page``, which is what we are going to be editing next. | 2803 is ``html/page.html``, which is what we are going to be editing next. |
| 2804 | 2804 |
| 2805 If you look at this file you can see that it contains a lot of | 2805 If you look at this file you can see that it contains a lot of |
| 2806 "classblock" sections which are chunks of HTML that will be included or | 2806 "classblock" sections which are chunks of HTML that will be included or |
| 2807 excluded in the output depending on whether the condition in the | 2807 excluded in the output depending on whether the condition in the |
| 2808 classblock is met. Under the end of the classblock for issue is where we | 2808 classblock is met. We are going to add the category code at the end of |
| 2809 are going to add the category code:: | 2809 the classblock for the *issue* class:: |
| 2810 | 2810 |
| 2811 <p class="classblock" | 2811 <p class="classblock" |
| 2812 tal:condition="python:request.user.hasPermission('View', 'category')"> | 2812 tal:condition="python:request.user.hasPermission('View', 'category')"> |
| 2813 <b>Categories</b><br> | 2813 <b>Categories</b><br> |
| 2814 <a tal:condition="python:request.user.hasPermission('Edit', 'category')" | 2814 <a tal:condition="python:request.user.hasPermission('Edit', 'category')" |
| 2829 | 2829 |
| 2830 Note that if you have permission to *view* but not to *edit* categories, | 2830 Note that if you have permission to *view* but not to *edit* categories, |
| 2831 then all you will see is a "Categories" header with nothing underneath | 2831 then all you will see is a "Categories" header with nothing underneath |
| 2832 it. This is obviously not very good interface design, but will do for | 2832 it. This is obviously not very good interface design, but will do for |
| 2833 now. I just claim that it is so I can add more links in this section | 2833 now. I just claim that it is so I can add more links in this section |
| 2834 later on. However to fix the problem you could change the condition in | 2834 later on. However, to fix the problem you could change the condition in |
| 2835 the classblock statement, so that only users with "Edit" permission | 2835 the classblock statement, so that only users with "Edit" permission |
| 2836 would see the "Categories" stuff. | 2836 would see the "Categories" stuff. |
| 2837 | 2837 |
| 2838 | 2838 |
| 2839 Setting up a page to edit categories | 2839 Setting up a page to edit categories |
| 2846 The link was for the *item* template of the *category* object. This | 2846 The link was for the *item* template of the *category* object. This |
| 2847 translates into Roundup looking for a file called ``category.item.html`` | 2847 translates into Roundup looking for a file called ``category.item.html`` |
| 2848 in the ``html`` tracker directory. This is the file that we are going to | 2848 in the ``html`` tracker directory. This is the file that we are going to |
| 2849 write now. | 2849 write now. |
| 2850 | 2850 |
| 2851 First we add an info tag in a comment which doesn't affect the outcome | 2851 First, we add an info tag in a comment which doesn't affect the outcome |
| 2852 of the code at all, but is useful for debugging. If you load a page in a | 2852 of the code at all, but is useful for debugging. If you load a page in a |
| 2853 browser and look at the page source, you can see which sections come | 2853 browser and look at the page source, you can see which sections come |
| 2854 from which files by looking for these comments:: | 2854 from which files by looking for these comments:: |
| 2855 | 2855 |
| 2856 <!-- category.item --> | 2856 <!-- category.item --> |
| 2886 | 2886 |
| 2887 <table class="form"> | 2887 <table class="form"> |
| 2888 <tr><th class="header" colspan="2">Category</th></tr> | 2888 <tr><th class="header" colspan="2">Category</th></tr> |
| 2889 | 2889 |
| 2890 Next, we need the field into which the user is going to enter the new | 2890 Next, we need the field into which the user is going to enter the new |
| 2891 category. The "context.name.field(size=60)" bit tells Roundup to | 2891 category. The ``context.name.field(size=60)`` bit tells Roundup to |
| 2892 generate a normal HTML field of size 60, and the contents of that field | 2892 generate a normal HTML field of size 60, and the contents of that field |
| 2893 will be the "name" variable of the current context (which is | 2893 will be the "name" variable of the current context (namely "category"). |
| 2894 "category"). The upshot of this is that when the user types something in | 2894 The upshot of this is that when the user types something in |
| 2895 to the form, a new category will be created with that name:: | 2895 to the form, a new category will be created with that name:: |
| 2896 | 2896 |
| 2897 <tr> | 2897 <tr> |
| 2898 <th>Name</th> | 2898 <th>Name</th> |
| 2899 <td tal:content="structure python:context.name.field(size=60)"> | 2899 <td tal:content="structure python:context.name.field(size=60)"> |
| 2963 that is pointless unless we can assign categories to issues. Just like | 2963 that is pointless unless we can assign categories to issues. Just like |
| 2964 the ``html/category.item.html`` file was used to define how to add a new | 2964 the ``html/category.item.html`` file was used to define how to add a new |
| 2965 category, the ``html/issue.item.html`` is used to define how a new issue | 2965 category, the ``html/issue.item.html`` is used to define how a new issue |
| 2966 is created. | 2966 is created. |
| 2967 | 2967 |
| 2968 Just like ``category.issue.html`` this file defines a form which has a | 2968 Just like ``category.issue.html``, this file defines a form which has a |
| 2969 table to lay things out. It doesn't matter where in the table we add new | 2969 table to lay things out. It doesn't matter where in the table we add new |
| 2970 stuff, it is entirely up to your sense of aesthetics:: | 2970 stuff, it is entirely up to your sense of aesthetics:: |
| 2971 | 2971 |
| 2972 <th>Category</th> | 2972 <th>Category</th> |
| 2973 <td><span tal:replace="structure context/category/field" /> | 2973 <td><span tal:replace="structure context/category/field" /> |
| 2985 | 2985 |
| 2986 | 2986 |
| 2987 Searching on categories | 2987 Searching on categories |
| 2988 ::::::::::::::::::::::: | 2988 ::::::::::::::::::::::: |
| 2989 | 2989 |
| 2990 We can add categories, and create issues with categories. The next | 2990 Now we can add categories, and create issues with categories. The next |
| 2991 obvious thing that we would like to be able to do, would be to search | 2991 obvious thing that we would like to be able to do, would be to search |
| 2992 for issues based on their category, so that, for example, anyone working | 2992 for issues based on their category, so that, for example, anyone working |
| 2993 on the web server could look at all issues in the category "Web". | 2993 on the web server could look at all issues in the category "Web". |
| 2994 | 2994 |
| 2995 If you look for "Search Issues" in the 'html/page.html' file, you will | 2995 If you look for "Search Issues" in the ``html/page.html`` file, you will |
| 2996 find that it looks something like | 2996 find that it looks something like |
| 2997 ``<a href="issue?@template=search">Search Issues</a>``. This shows us | 2997 ``<a href="issue?@template=search">Search Issues</a>``. This shows us |
| 2998 that when you click on "Search Issues" it will be looking for a | 2998 that when you click on "Search Issues" it will be looking for a |
| 2999 ``issue.search.html`` file to display. So that is the file that we will | 2999 ``issue.search.html`` file to display. So that is the file that we will |
| 3000 change. | 3000 change. |
| 3001 | 3001 |
| 3002 If you look at this file it should be starting to seem familiar, although it | 3002 If you look at this file it should begin to seem familiar, although it |
| 3003 does use some new macros. You can add the new category search code anywhere you | 3003 does use some new macros. You can add the new category search code anywhere you |
| 3004 like within that form:: | 3004 like within that form:: |
| 3005 | 3005 |
| 3006 <tr tal:define="name string:category; | 3006 <tr tal:define="name string:category; |
| 3007 db_klass string:category; | 3007 db_klass string:category; |
| 3011 <td metal:use-macro="column_input"></td> | 3011 <td metal:use-macro="column_input"></td> |
| 3012 <td metal:use-macro="sort_input"></td> | 3012 <td metal:use-macro="sort_input"></td> |
| 3013 <td metal:use-macro="group_input"></td> | 3013 <td metal:use-macro="group_input"></td> |
| 3014 </tr> | 3014 </tr> |
| 3015 | 3015 |
| 3016 The definitions in the <tr> opening tag are used by the macros: | 3016 The definitions in the ``<tr>`` opening tag are used by the macros: |
| 3017 | 3017 |
| 3018 - search_select expands to a drop-down box with all categories using db_klass | 3018 - ``search_select`` expands to a drop-down box with all categories using |
| 3019 and db_content. | 3019 ``db_klass`` and ``db_content``. |
| 3020 - column_input expands to a checkbox for selecting what columns should be | 3020 - ``column_input`` expands to a checkbox for selecting what columns |
| 3021 displayed. | 3021 should be displayed. |
| 3022 - sort_input expands to a radio button for selecting what property should be | 3022 - ``sort_input`` expands to a radio button for selecting what property |
| 3023 sorted on. | 3023 should be sorted on. |
| 3024 - group_input expands to a radio button for selecting what property should be | 3024 - ``group_input`` expands to a radio button for selecting what property |
| 3025 group on. | 3025 should be grouped on. |
| 3026 | 3026 |
| 3027 The category search code above would expand to the following:: | 3027 The category search code above would expand to the following:: |
| 3028 | 3028 |
| 3029 <tr> | 3029 <tr> |
| 3030 <th>Category:</th> | 3030 <th>Category:</th> |
| 3075 The condition is the same as above: only display the condition when the | 3075 The condition is the same as above: only display the condition when the |
| 3076 user hasn't asked for it to be hidden. The next part is to set the | 3076 user hasn't asked for it to be hidden. The next part is to set the |
| 3077 content of the cell to be the category part of "i" - the current issue. | 3077 content of the cell to be the category part of "i" - the current issue. |
| 3078 | 3078 |
| 3079 Finally we have to edit ``html/page.html`` again. This time, we need to | 3079 Finally we have to edit ``html/page.html`` again. This time, we need to |
| 3080 tell it that when the user clicks on "Unasigned Issues" or "All Issues", | 3080 tell it that when the user clicks on "Unassigned Issues" or "All Issues", |
| 3081 the category column should be included in the resulting list. If you | 3081 the category column should be included in the resulting list. If you |
| 3082 scroll down the page file, you can see the links with lots of options. | 3082 scroll down the page file, you can see the links with lots of options. |
| 3083 The option that we are interested in is the ``:columns=`` one which | 3083 The option that we are interested in is the ``:columns=`` one which |
| 3084 tells roundup which fields of the issue to display. Simply add | 3084 tells roundup which fields of the issue to display. Simply add |
| 3085 "category" to that list and it all should work. | 3085 "category" to that list and it all should work. |
| 3108 | 3108 |
| 3109 the "times" property is the new link to the "timelog" class. | 3109 the "times" property is the new link to the "timelog" class. |
| 3110 | 3110 |
| 3111 3. We'll need to let people add in times to the issue, so in the web | 3111 3. We'll need to let people add in times to the issue, so in the web |
| 3112 interface we'll have a new entry field. This is a special field | 3112 interface we'll have a new entry field. This is a special field |
| 3113 because unlike the other fields in the issue.item template, it | 3113 because unlike the other fields in the ``issue.item`` template, it |
| 3114 affects a different item (a timelog item) and not the template's | 3114 affects a different item (a timelog item) and not the template's |
| 3115 item, an issue. We have a special syntax for form fields that affect | 3115 item (an issue). We have a special syntax for form fields that affect |
| 3116 items other than the template default item (see the cgi | 3116 items other than the template default item (see the cgi |
| 3117 documentation on `special form variables`_). In particular, we add a | 3117 documentation on `special form variables`_). In particular, we add a |
| 3118 field to capture a new timelog item's perdiod:: | 3118 field to capture a new timelog item's period:: |
| 3119 | 3119 |
| 3120 <tr> | 3120 <tr> |
| 3121 <th>Time Log</th> | 3121 <th>Time Log</th> |
| 3122 <td colspan=3><input type="text" name="timelog-1@period" /> | 3122 <td colspan=3><input type="text" name="timelog-1@period" /> |
| 3123 <br />(enter as '3y 1m 4d 2:40:02' or parts thereof) | 3123 <br />(enter as '3y 1m 4d 2:40:02' or parts thereof) |
| 3132 | 3132 |
| 3133 On submission, the "-1" timelog item will be created and assigned a | 3133 On submission, the "-1" timelog item will be created and assigned a |
| 3134 real item id. The "times" property of the issue will have the new id | 3134 real item id. The "times" property of the issue will have the new id |
| 3135 added to it. | 3135 added to it. |
| 3136 | 3136 |
| 3137 4. We want to display a total of the time log times that have been | 3137 4. We want to display a total of the timelog times that have been |
| 3138 accumulated for an issue. To do this, we'll need to actually write | 3138 accumulated for an issue. To do this, we'll need to actually write |
| 3139 some Python code, since it's beyond the scope of PageTemplates to | 3139 some Python code, since it's beyond the scope of PageTemplates to |
| 3140 perform such calculations. We do this by adding a module ``timespent.py`` | 3140 perform such calculations. We do this by adding a module ``timespent.py`` |
| 3141 to the ``extensions`` directory in our tracker:: | 3141 to the ``extensions`` directory in our tracker. The contents of this |
| 3142 file is as follows:: | |
| 3142 | 3143 |
| 3143 def totalTimeSpent(times): | 3144 def totalTimeSpent(times): |
| 3144 ''' Call me with a list of timelog items (which have an | 3145 ''' Call me with a list of timelog items (which have an |
| 3145 Interval "period" property) | 3146 Interval "period" property) |
| 3146 ''' | 3147 ''' |
| 3153 instance.registerUtil('totalTimeSpent', totalTimeSpent) | 3154 instance.registerUtil('totalTimeSpent', totalTimeSpent) |
| 3154 | 3155 |
| 3155 We will now be able to access the ``totalTimeSpent`` function via the | 3156 We will now be able to access the ``totalTimeSpent`` function via the |
| 3156 ``utils`` variable in our templates, as shown in the next step. | 3157 ``utils`` variable in our templates, as shown in the next step. |
| 3157 | 3158 |
| 3158 5. Display the time log for an issue:: | 3159 5. Display the timelog for an issue:: |
| 3159 | 3160 |
| 3160 <table class="otherinfo" tal:condition="context/times"> | 3161 <table class="otherinfo" tal:condition="context/times"> |
| 3161 <tr><th colspan="3" class="header">Time Log | 3162 <tr><th colspan="3" class="header">Time Log |
| 3162 <tal:block | 3163 <tal:block |
| 3163 tal:replace="python:utils.totalTimeSpent(context.times)" /> | 3164 tal:replace="python:utils.totalTimeSpent(context.times)" /> |
| 3174 use of the ``totalTimeSpent`` method which will total up the times | 3175 use of the ``totalTimeSpent`` method which will total up the times |
| 3175 for the issue and return a new Interval. That will be automatically | 3176 for the issue and return a new Interval. That will be automatically |
| 3176 displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours | 3177 displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours |
| 3177 and 40 minutes). | 3178 and 40 minutes). |
| 3178 | 3179 |
| 3179 8. If you're using a persistent web server - roundup-server or | 3180 8. If you're using a persistent web server - ``roundup-server`` or |
| 3180 mod_python for example - then you'll need to restart that to pick up | 3181 ``mod_python`` for example - then you'll need to restart that to pick up |
| 3181 the code changes. When that's done, you'll be able to use the new | 3182 the code changes. When that's done, you'll be able to use the new |
| 3182 time logging interface. | 3183 time logging interface. |
| 3183 | 3184 |
| 3184 | 3185 |
| 3185 Tracking different types of issues | 3186 Tracking different types of issues |
| 3194 this is obvious, but sometimes it's better to actually sit down for a | 3195 this is obvious, but sometimes it's better to actually sit down for a |
| 3195 while and think about the schema you're going to implement. | 3196 while and think about the schema you're going to implement. |
| 3196 | 3197 |
| 3197 2. Add the new issue class to your tracker's ``schema.py`` - in this | 3198 2. Add the new issue class to your tracker's ``schema.py`` - in this |
| 3198 example, we're adding a "system support" class. Just after the "issue" | 3199 example, we're adding a "system support" class. Just after the "issue" |
| 3199 class definition in the "open" function, add:: | 3200 class definition, add:: |
| 3200 | 3201 |
| 3201 support = IssueClass(db, "support", | 3202 support = IssueClass(db, "support", |
| 3202 assignedto=Link("user"), topic=Multilink("keyword"), | 3203 assignedto=Link("user"), topic=Multilink("keyword"), |
| 3203 status=Link("status"), deadline=Date(), | 3204 status=Link("status"), deadline=Date(), |
| 3204 affects=Multilink("system")) | 3205 affects=Multilink("system")) |
| 3205 | 3206 |
| 3206 3. Copy the existing "issue.*" (item, search and index) templates in the | 3207 3. Copy the existing ``issue.*`` (item, search and index) templates in the |
| 3207 tracker's "html" to "support.*". Edit them so they use the properties | 3208 tracker's ``html`` to ``support.*``. Edit them so they use the properties |
| 3208 defined in the "support" class. Be sure to check for hidden form | 3209 defined in the ``support`` class. Be sure to check for hidden form |
| 3209 variables like "required" to make sure they have the correct set of | 3210 variables like "required" to make sure they have the correct set of |
| 3210 required properties. | 3211 required properties. |
| 3211 | 3212 |
| 3212 4. Edit the modules in the "detectors", adding lines to their "init" | 3213 4. Edit the modules in the ``detectors``, adding lines to their ``init`` |
| 3213 functions where appropriate. Look for "audit" and "react" registrations | 3214 functions where appropriate. Look for ``audit`` and ``react`` registrations |
| 3214 on the "issue" class, and duplicate them for "support". | 3215 on the ``issue`` class, and duplicate them for ``support``. |
| 3215 | 3216 |
| 3216 5. Create a new sidebar box for the new support class. Duplicate the | 3217 5. Create a new sidebar box for the new support class. Duplicate the |
| 3217 existing issues one, changing the "issue" class name to "support". | 3218 existing issues one, changing the ``issue`` class name to ``support``. |
| 3218 | 3219 |
| 3219 6. Re-start your tracker and start using the new "support" class. | 3220 6. Re-start your tracker and start using the new ``support`` class. |
| 3220 | 3221 |
| 3221 | 3222 |
| 3222 Optionally, you might want to restrict the users able to access this new | 3223 Optionally, you might want to restrict the users able to access this new |
| 3223 class to just the users with a new "SysAdmin" Role. To do this, we add | 3224 class to just the users with a new "SysAdmin" Role. To do this, we add |
| 3224 some security declarations:: | 3225 some security declarations:: |
| 3229 | 3230 |
| 3230 You would then (as an "admin" user) edit the details of the appropriate | 3231 You would then (as an "admin" user) edit the details of the appropriate |
| 3231 users, and add "SysAdmin" to their Roles list. | 3232 users, and add "SysAdmin" to their Roles list. |
| 3232 | 3233 |
| 3233 Alternatively, you might want to change the Edit/View permissions granted | 3234 Alternatively, you might want to change the Edit/View permissions granted |
| 3234 for the "issue" class so that it's only available to users with the "System" | 3235 for the ``issue`` class so that it's only available to users with the "System" |
| 3235 or "Developer" Role, and then the new class you're adding is available to | 3236 or "Developer" Role, and then the new class you're adding is available to |
| 3236 all with the "User" Role. | 3237 all with the "User" Role. |
| 3237 | 3238 |
| 3238 | 3239 |
| 3239 Using External User Databases | 3240 Using External User Databases |
| 3253 | 3254 |
| 3254 Each user of Roundup must still have their information stored in the Roundup | 3255 Each user of Roundup must still have their information stored in the Roundup |
| 3255 database - we just use the passwd file to check their password. To do this, we | 3256 database - we just use the passwd file to check their password. To do this, we |
| 3256 need to override the standard ``verifyPassword`` method defined in | 3257 need to override the standard ``verifyPassword`` method defined in |
| 3257 ``roundup.cgi.actions.LoginAction`` and register the new class. The | 3258 ``roundup.cgi.actions.LoginAction`` and register the new class. The |
| 3258 following is added as ``externapassword.py`` in the tracker ``extensions`` | 3259 following is added as ``externalpassword.py`` in the tracker ``extensions`` |
| 3259 directory:: | 3260 directory:: |
| 3260 | 3261 |
| 3261 from roundup.cgi.actions import LoginAction | 3262 from roundup.cgi.actions import LoginAction |
| 3262 | 3263 |
| 3263 class ExternalPasswordLoginAction(LoginAction): | 3264 class ExternalPasswordLoginAction(LoginAction): |
| 3302 which the users are removed when they no longer have access to a system. | 3303 which the users are removed when they no longer have access to a system. |
| 3303 | 3304 |
| 3304 To make use of the passwd file, we therefore synchronise between the two | 3305 To make use of the passwd file, we therefore synchronise between the two |
| 3305 user stores. We also use the passwd file to validate the user logins, as | 3306 user stores. We also use the passwd file to validate the user logins, as |
| 3306 described in the previous example, `using an external password | 3307 described in the previous example, `using an external password |
| 3307 validation source`_. We keep the users lists in sync using a fairly | 3308 validation source`_. We keep the user lists in sync using a fairly |
| 3308 simple script that runs once a day, or several times an hour if more | 3309 simple script that runs once a day, or several times an hour if more |
| 3309 immediate access is needed. In short, it: | 3310 immediate access is needed. In short, it: |
| 3310 | 3311 |
| 3311 1. parses the passwd file, finding usernames, passwords and real names, | 3312 1. parses the passwd file, finding usernames, passwords and real names, |
| 3312 2. compares that list to the current roundup user list: | 3313 2. compares that list to the current roundup user list: |
| 3317 | 3318 |
| 3318 3. send an email to administrators to let them know what's been done. | 3319 3. send an email to administrators to let them know what's been done. |
| 3319 | 3320 |
| 3320 The retiring and updating are simple operations, requiring only a call | 3321 The retiring and updating are simple operations, requiring only a call |
| 3321 to ``retire()`` or ``set()``. The creation operation requires more | 3322 to ``retire()`` or ``set()``. The creation operation requires more |
| 3322 information though - the user's email address and their roundup Roles. | 3323 information though - the user's email address and their Roundup Roles. |
| 3323 We're going to assume that the user's email address is the same as their | 3324 We're going to assume that the user's email address is the same as their |
| 3324 login name, so we just append the domain name to that. The Roles are | 3325 login name, so we just append the domain name to that. The Roles are |
| 3325 determined using the passwd group identifier - mapping their UN*X group | 3326 determined using the passwd group identifier - mapping their UN*X group |
| 3326 to an appropriate set of Roles. | 3327 to an appropriate set of Roles. |
| 3327 | 3328 |
| 3433 once an hour / day (or on demand if you can work that into your LDAP store | 3434 once an hour / day (or on demand if you can work that into your LDAP store |
| 3434 workflow). See the example `Using a UN*X passwd file as the user database`_ | 3435 workflow). See the example `Using a UN*X passwd file as the user database`_ |
| 3435 for more information about doing this. | 3436 for more information about doing this. |
| 3436 | 3437 |
| 3437 To authenticate off the LDAP store (rather than using the passwords in the | 3438 To authenticate off the LDAP store (rather than using the passwords in the |
| 3438 roundup user database) you'd use the same python-ldap module inside an | 3439 Roundup user database) you'd use the same python-ldap module inside an |
| 3439 extension to the cgi interface. You'd do this by overriding the method called | 3440 extension to the cgi interface. You'd do this by overriding the method called |
| 3440 "verifyPassword" on the LoginAction class in your tracker's interfaces.py | 3441 ``verifyPassword`` on the ``LoginAction`` class in your tracker's |
| 3441 module (see `using an external password validation source`_). The method is | 3442 ``extensions`` directory (see `using an external password validation |
| 3442 implemented by default as:: | 3443 source`_). The method is implemented by default as:: |
| 3443 | 3444 |
| 3444 def verifyPassword(self, userid, password): | 3445 def verifyPassword(self, userid, password): |
| 3445 ''' Verify the password that the user has supplied | 3446 ''' Verify the password that the user has supplied |
| 3446 ''' | 3447 ''' |
| 3447 stored = self.db.user.get(self.userid, 'password') | 3448 stored = self.db.user.get(self.userid, 'password') |
| 3562 which filters out the users that have the vacation flag set to true. | 3563 which filters out the users that have the vacation flag set to true. |
| 3563 | 3564 |
| 3564 Adding in state transition control | 3565 Adding in state transition control |
| 3565 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 3566 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 3566 | 3567 |
| 3567 Sometimes tracker admins want to control the states that users may move | 3568 Sometimes tracker admins want to control the states to which users may |
| 3568 issues to. You can do this by following these steps: | 3569 move issues. You can do this by following these steps: |
| 3569 | 3570 |
| 3570 1. make "status" a required variable. This is achieved by adding the | 3571 1. make "status" a required variable. This is achieved by adding the |
| 3571 following to the top of the form in the ``issue.item.html`` | 3572 following to the top of the form in the ``issue.item.html`` |
| 3572 template:: | 3573 template:: |
| 3573 | 3574 |
| 3574 <input type="hidden" name="@required" value="status"> | 3575 <input type="hidden" name="@required" value="status"> |
| 3575 | 3576 |
| 3576 this will force users to select a status. | 3577 This will force users to select a status. |
| 3577 | 3578 |
| 3578 2. add a Multilink property to the status class:: | 3579 2. add a Multilink property to the status class:: |
| 3579 | 3580 |
| 3580 stat = Class(db, "status", ... , transitions=Multilink('status'), | 3581 stat = Class(db, "status", ... , transitions=Multilink('status'), |
| 3581 ...) | 3582 ...) |
| 3582 | 3583 |
| 3583 and then edit the statuses already created, either: | 3584 and then edit the statuses already created, either: |
| 3584 | 3585 |
| 3585 a. through the web using the class list -> status class editor, or | 3586 a. through the web using the class list -> status class editor, or |
| 3586 b. using the roundup-admin "set" command. | 3587 b. using the ``roundup-admin`` "set" command. |
| 3587 | 3588 |
| 3588 3. add an auditor module ``checktransition.py`` in your tracker's | 3589 3. add an auditor module ``checktransition.py`` in your tracker's |
| 3589 ``detectors`` directory, for example:: | 3590 ``detectors`` directory, for example:: |
| 3590 | 3591 |
| 3591 def checktransition(db, cl, nodeid, newvalues): | 3592 def checktransition(db, cl, nodeid, newvalues): |
| 3638 | 3639 |
| 3639 We needed the ability to mark certain issues as "blockers" - that is, | 3640 We needed the ability to mark certain issues as "blockers" - that is, |
| 3640 they can't be resolved until another issue (the blocker) they rely on is | 3641 they can't be resolved until another issue (the blocker) they rely on is |
| 3641 resolved. To achieve this: | 3642 resolved. To achieve this: |
| 3642 | 3643 |
| 3643 1. Create a new property on the issue Class, | 3644 1. Create a new property on the ``issue`` class: |
| 3644 ``blockers=Multilink("issue")``. Edit your tracker's schema.py file. | 3645 ``blockers=Multilink("issue")``. To do this, edit the definition of |
| 3645 Where the "issue" class is defined, something like:: | 3646 this class in your tracker's ``schema.py`` file. Change this:: |
| 3646 | 3647 |
| 3647 issue = IssueClass(db, "issue", | 3648 issue = IssueClass(db, "issue", |
| 3648 assignedto=Link("user"), topic=Multilink("keyword"), | 3649 assignedto=Link("user"), topic=Multilink("keyword"), |
| 3649 priority=Link("priority"), status=Link("status")) | 3650 priority=Link("priority"), status=Link("status")) |
| 3650 | 3651 |
| 3651 add the blockers entry like so:: | 3652 to this, adding the blockers entry:: |
| 3652 | 3653 |
| 3653 issue = IssueClass(db, "issue", | 3654 issue = IssueClass(db, "issue", |
| 3654 blockers=Multilink("issue"), | 3655 blockers=Multilink("issue"), |
| 3655 assignedto=Link("user"), topic=Multilink("keyword"), | 3656 assignedto=Link("user"), topic=Multilink("keyword"), |
| 3656 priority=Link("priority"), status=Link("status")) | 3657 priority=Link("priority"), status=Link("status")) |
| 3657 | 3658 |
| 3658 2. Add the new "blockers" property to the issue.item edit page, using | 3659 2. Add the new ``blockers`` property to the ``issue.item.html`` edit |
| 3659 something like:: | 3660 page, using something like:: |
| 3660 | 3661 |
| 3661 <th>Waiting On</th> | 3662 <th>Waiting On</th> |
| 3662 <td> | 3663 <td> |
| 3663 <span tal:replace="structure python:context.blockers.field(showid=1, | 3664 <span tal:replace="structure python:context.blockers.field(showid=1, |
| 3664 size=20)" /> | 3665 size=20)" /> |
| 3672 You'll need to fiddle with your item page layout to find an | 3673 You'll need to fiddle with your item page layout to find an |
| 3673 appropriate place to put it - I'll leave that fun part up to you. | 3674 appropriate place to put it - I'll leave that fun part up to you. |
| 3674 Just make sure it appears in the first table, possibly somewhere near | 3675 Just make sure it appears in the first table, possibly somewhere near |
| 3675 the "superseders" field. | 3676 the "superseders" field. |
| 3676 | 3677 |
| 3677 3. Create a new detector module (attached) which enforces the rules: | 3678 3. Create a new detector module (see below) which enforces the rules: |
| 3678 | 3679 |
| 3679 - issues may not be resolved if they have blockers | 3680 - issues may not be resolved if they have blockers |
| 3680 - when a blocker is resolved, it's removed from issues it blocks | 3681 - when a blocker is resolved, it's removed from issues it blocks |
| 3681 | 3682 |
| 3682 The contents of the detector should be something like this:: | 3683 The contents of the detector should be something like this:: |
| 3750 URLs so they filter out issues with any blockers. You do this by | 3751 URLs so they filter out issues with any blockers. You do this by |
| 3751 adding an additional filter on "blockers" for the value "-1". For | 3752 adding an additional filter on "blockers" for the value "-1". For |
| 3752 example, the existing "Show All" link in the "page" template (in the | 3753 example, the existing "Show All" link in the "page" template (in the |
| 3753 tracker's "html" directory) looks like this:: | 3754 tracker's "html" directory) looks like this:: |
| 3754 | 3755 |
| 3755 <a href="issue?:sort=-activity&:group=priority&:filter=status& | 3756 <a href="issue?@sort=-activity&@group=priority&@filter=status& |
| 3756 :columns=id,activity,title,creator,assignedto,status& | 3757 @columns=id,activity,title,creator,assignedto,status& |
| 3757 status=-1,1,2,3,4,5,6,7">Show All</a><br> | 3758 status=-1,1,2,3,4,5,6,7">Show All</a><br> |
| 3758 | 3759 |
| 3759 modify it to add the "blockers" info to the URL (note, both the | 3760 modify it to add the "blockers" info to the URL (note, both the |
| 3760 ":filter" *and* "blockers" values must be specified):: | 3761 "@filter" *and* "blockers" values must be specified)@@ |
| 3761 | 3762 |
| 3762 <a href="issue?:sort=-activity&:group=priority&:filter=status,blockers& | 3763 <a href="issue?@sort=-activity&@group=priority&@filter=status,blockers& |
| 3763 blockers=-1&:columns=id,activity,title,creator,assignedto,status& | 3764 blockers=-1&@columns=id,activity,title,creator,assignedto,status& |
| 3764 status=-1,1,2,3,4,5,6,7">Show All</a><br> | 3765 status=-1,1,2,3,4,5,6,7">Show All</a><br> |
| 3765 | 3766 |
| 3766 The above examples are line-wrapped on the trailing & and should | 3767 The above examples are line-wrapped on the trailing & and should |
| 3767 be unwrapped. | 3768 be unwrapped. |
| 3768 | 3769 |
| 3773 another issue's "blockers" property. | 3774 another issue's "blockers" property. |
| 3774 | 3775 |
| 3775 Add users to the nosy list based on the topic | 3776 Add users to the nosy list based on the topic |
| 3776 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 3777 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 3777 | 3778 |
| 3778 We need the ability to automatically add users to the nosy list based | 3779 Let's say we need the ability to automatically add users to the nosy |
| 3779 on the occurence of a topic. Every user should be allowed to edit his | 3780 list based |
| 3780 own list of topics for which he wants to be added to the nosy list. | 3781 on the occurance of a topic. Every user should be allowed to edit their |
| 3781 | 3782 own list of topics for which they want to be added to the nosy list. |
| 3782 Below will be showed that such a change can be performed with only | 3783 |
| 3783 minimal understanding of the roundup system, but with clever use | 3784 Below, we'll show that this change can be done with minimal |
| 3784 of Copy and Paste. | 3785 understanding of the Roundup system, using only copy and paste. |
| 3785 | 3786 |
| 3786 This requires three changes to the tracker: a change in the database to | 3787 This requires three changes to the tracker: a change in the database to |
| 3787 allow per-user recording of the lists of topics for which he wants to | 3788 allow per-user recording of the lists of topics for which he wants to |
| 3788 be put on the nosy list, a change in the user view allowing to edit | 3789 be put on the nosy list, a change in the user view allowing them to edit |
| 3789 this list of topics, and addition of an auditor which updates the nosy | 3790 this list of topics, and addition of an auditor which updates the nosy |
| 3790 list when a topic is set. | 3791 list when a topic is set. |
| 3791 | 3792 |
| 3792 Adding the nosy topic list | 3793 Adding the nosy topic list |
| 3793 :::::::::::::::::::::::::: | 3794 :::::::::::::::::::::::::: |
| 3794 | 3795 |
| 3795 The change in the database to make is that for any user there should be | 3796 The change to make in the database, is that for any user there should be |
| 3796 a list of topics for which he wants to be put on the nosy list. Adding | 3797 a list of topics for which he wants to be put on the nosy list. Adding |
| 3797 a ``Multilink`` of ``keyword`` seem to fullfill this (note that within | 3798 a ``Multilink`` of ``keyword`` seems to fullfill this (note that within |
| 3798 the code topics are called ``keywords``.) As such, all what has to be | 3799 the code, topics are called ``keywords``.) As such, all that has to be |
| 3799 done is to add a new field to the definition of ``user`` within the | 3800 done is to add a new field to the definition of ``user`` within the |
| 3800 file ``schema.py``. We will call this new field ``nosy_keywords``, and | 3801 file ``schema.py``. We will call this new field ``nosy_keywords``, and |
| 3801 the updated definition of user will be:: | 3802 the updated definition of user will be:: |
| 3802 | 3803 |
| 3803 user = Class(db, "user", | 3804 user = Class(db, "user", |
| 3813 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | 3814 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
| 3814 | 3815 |
| 3815 We want any user to be able to change the list of topics for which | 3816 We want any user to be able to change the list of topics for which |
| 3816 he will by default be added to the nosy list. We choose to add this | 3817 he will by default be added to the nosy list. We choose to add this |
| 3817 to the user view, as is generated by the file ``html/user.item.html``. | 3818 to the user view, as is generated by the file ``html/user.item.html``. |
| 3818 We easily can | 3819 We can easily |
| 3819 see that the topic field in the issue view has very similar editting | 3820 see that the topic field in the issue view has very similar editing |
| 3820 requirements as our nosy topics, both being a list of topics. As | 3821 requirements as our nosy topics, both being lists of topics. As |
| 3821 such, we search for Topics in ``issue.item.html``, and extract the | 3822 such, we look for Topics in ``issue.item.html``, and extract the |
| 3822 associated parts from there. We add this to ``user.item.html`` at the | 3823 associated parts from there. We add this to ``user.item.html`` at the |
| 3823 bottom of the list of viewed items (i.e. just below the 'Alternate | 3824 bottom of the list of viewed items (i.e. just below the 'Alternate |
| 3824 E-mail addresses' in the classic template):: | 3825 E-mail addresses' in the classic template):: |
| 3825 | 3826 |
| 3826 <tr> | 3827 <tr> |
| 3833 | 3834 |
| 3834 | 3835 |
| 3835 Addition of an auditor to update the nosy list | 3836 Addition of an auditor to update the nosy list |
| 3836 :::::::::::::::::::::::::::::::::::::::::::::: | 3837 :::::::::::::::::::::::::::::::::::::::::::::: |
| 3837 | 3838 |
| 3838 The more difficult part is the addition of the logic to actually | 3839 The more difficult part is the logic to add |
| 3839 at the users to the nosy list when it is required. | 3840 the users to the nosy list when required. |
| 3840 The choice is made to perform this action when the topics on an | 3841 We choose to perform this action whenever the topics on an |
| 3841 item are set, including when an item is created. | 3842 item are set (this includes the creation of items). |
| 3842 Here we choose to start out with a copy of the | 3843 Here we choose to start out with a copy of the |
| 3843 ``detectors/nosyreaction.py`` detector, which we copy to the file | 3844 ``detectors/nosyreaction.py`` detector, which we copy to the file |
| 3844 ``detectors/nosy_keyword_reaction.py``. | 3845 ``detectors/nosy_keyword_reaction.py``. |
| 3845 This looks like a good start as it also adds users | 3846 This looks like a good start as it also adds users |
| 3846 to the nosy list. A look through the code reveals that the | 3847 to the nosy list. A look through the code reveals that the |
| 3847 ``nosyreaction`` function actually is sending the e-mail, which | 3848 ``nosyreaction`` function actually sends the e-mail. |
| 3848 we do not need. As such, we can change the init function to:: | 3849 We don't need this. Therefore, we can change the ``init`` function to:: |
| 3849 | 3850 |
| 3850 def init(db): | 3851 def init(db): |
| 3851 db.issue.audit('create', update_kw_nosy) | 3852 db.issue.audit('create', update_kw_nosy) |
| 3852 db.issue.audit('set', update_kw_nosy) | 3853 db.issue.audit('set', update_kw_nosy) |
| 3853 | 3854 |
| 3854 After that we rename the ``updatenosy`` function to ``update_kw_nosy``. | 3855 After that, we rename the ``updatenosy`` function to ``update_kw_nosy``. |
| 3855 The first two blocks of code in that function relate to settings | 3856 The first two blocks of code in that function relate to setting |
| 3856 ``current`` to a combination of the old and new nosy lists. This | 3857 ``current`` to a combination of the old and new nosy lists. This |
| 3857 functionality is left in the new auditor. The following block of | 3858 functionality is left in the new auditor. The following block of |
| 3858 code, which in ``updatenosy`` handled adding the assignedto user(s) | 3859 code, which handled adding the assignedto user(s) to the nosy list in |
| 3859 to the nosy list, should be replaced by a block of code to add the | 3860 ``updatenosy``, should be replaced by a block of code to add the |
| 3860 interested users to the nosy list. We choose here to loop over all | 3861 interested users to the nosy list. We choose here to loop over all |
| 3861 new topics, than loop over all users, | 3862 new topics, than looping over all users, |
| 3862 and assign the user to the nosy list when the topic in the user's | 3863 and assign the user to the nosy list when the topic occurs in the user's |
| 3863 nosy_keywords. The next part in ``updatenosy``, adding the author | 3864 ``nosy_keywords``. The next part in ``updatenosy`` -- adding the author |
| 3864 and/or recipients of a message to the nosy list, obviously is not | 3865 and/or recipients of a message to the nosy list -- is obviously not |
| 3865 relevant here and thus is deleted from the new auditor. The last | 3866 relevant here and is thus deleted from the new auditor. The last |
| 3866 part, copying the new nosy list to newvalues, does not have to be changed. | 3867 part, copying the new nosy list to ``newvalues``, can stay as is. |
| 3867 This brings the following function:: | 3868 This results in the following function:: |
| 3868 | 3869 |
| 3869 def update_kw_nosy(db, cl, nodeid, newvalues): | 3870 def update_kw_nosy(db, cl, nodeid, newvalues): |
| 3870 '''Update the nosy list for changes to the topics | 3871 '''Update the nosy list for changes to the topics |
| 3871 ''' | 3872 ''' |
| 3872 # nodeid will be None if this is a new node | 3873 # nodeid will be None if this is a new node |
| 3908 current[user_id] = 1 | 3909 current[user_id] = 1 |
| 3909 | 3910 |
| 3910 # that's it, save off the new nosy list | 3911 # that's it, save off the new nosy list |
| 3911 newvalues['nosy'] = current.keys() | 3912 newvalues['nosy'] = current.keys() |
| 3912 | 3913 |
| 3913 and these two function are the only ones needed in the file. | 3914 These two function are the only ones needed in the file. |
| 3914 | 3915 |
| 3915 TODO: update this example to use the find() Class method. | 3916 TODO: update this example to use the ``find()`` Class method. |
| 3916 | 3917 |
| 3917 Caveats | 3918 Caveats |
| 3918 ::::::: | 3919 ::::::: |
| 3919 | 3920 |
| 3920 A few problems with the design here can be noted: | 3921 A few problems with the design here can be noted: |
| 3921 | 3922 |
| 3922 Multiple additions | 3923 Multiple additions |
| 3923 When a user, after automatic selection, is manually removed | 3924 When a user, after automatic selection, is manually removed |
| 3924 from the nosy list, he again is added to the nosy list when the | 3925 from the nosy list, he is added to the nosy list again when the |
| 3925 topic list of the issue is updated. A better design might be | 3926 topic list of the issue is updated. A better design might be |
| 3926 to only check which topics are new compared to the old list | 3927 to only check which topics are new compared to the old list |
| 3927 of topics, and only add users when they have indicated | 3928 of topics, and only add users when they have indicated |
| 3928 interest on a new topic. | 3929 interest on a new topic. |
| 3929 | 3930 |
| 3930 The code could also be changed to only trigger on the create() event, | 3931 The code could also be changed to only trigger on the ``create()`` |
| 3931 rather than also on the set() event, thus only setting the nosy list | 3932 event, rather than also on the ``set()`` event, thus only setting |
| 3932 when the issue is created. | 3933 the nosy list when the issue is created. |
| 3933 | 3934 |
| 3934 Scalability | 3935 Scalability |
| 3935 In the auditor there is a loop over all users. For a site with | 3936 In the auditor, there is a loop over all users. For a site with |
| 3936 only few users this will pose no serious problem, however, with | 3937 only few users this will pose no serious problem; however, with |
| 3937 many users this will be a serious performance bottleneck. | 3938 many users this will be a serious performance bottleneck. |
| 3938 A way out will be to link from the topics to the users which | 3939 A way out would be to link from the topics to the users who |
| 3939 selected these topics a nosy topics. This will eliminate the | 3940 selected these topics as nosy topics. This will eliminate the |
| 3940 loop over all users. | 3941 loop over all users. |
| 3941 | 3942 |
| 3942 Changes to Security and Permissions | 3943 Changes to Security and Permissions |
| 3943 ----------------------------------- | 3944 ----------------------------------- |
| 3944 | 3945 |
| 3957 | 3958 |
| 3958 3. Then assign the new Permission to your "Developer" Role:: | 3959 3. Then assign the new Permission to your "Developer" Role:: |
| 3959 | 3960 |
| 3960 db.security.addPermissionToRole('Developer', p) | 3961 db.security.addPermissionToRole('Developer', p) |
| 3961 | 3962 |
| 3962 4. In the issue item edit page ("html/issue.item.html" in your tracker | 3963 4. In the issue item edit page (``html/issue.item.html`` in your tracker |
| 3963 directory), use the new Permission in restricting the "assignedto" | 3964 directory), use the new Permission in restricting the "assignedto" |
| 3964 list:: | 3965 list:: |
| 3965 | 3966 |
| 3966 <select name="assignedto"> | 3967 <select name="assignedto"> |
| 3967 <option value="-1">- no selection -</option> | 3968 <option value="-1">- no selection -</option> |
| 3974 tal:content="user/realname"></option> | 3975 tal:content="user/realname"></option> |
| 3975 </tal:block> | 3976 </tal:block> |
| 3976 </select> | 3977 </select> |
| 3977 | 3978 |
| 3978 For extra security, you may wish to setup an auditor to enforce the | 3979 For extra security, you may wish to setup an auditor to enforce the |
| 3979 Permission requirement (install this as "assignedtoFixer.py" in your | 3980 Permission requirement (install this as ``assignedtoFixer.py`` in your |
| 3980 tracker "detectors" directory):: | 3981 tracker ``detectors`` directory):: |
| 3981 | 3982 |
| 3982 def assignedtoMustBeFixer(db, cl, nodeid, newvalues): | 3983 def assignedtoMustBeFixer(db, cl, nodeid, newvalues): |
| 3983 ''' Ensure the assignedto value in newvalues is a used with the | 3984 ''' Ensure the assignedto value in newvalues is used with the |
| 3984 Fixer Permission | 3985 Fixer Permission |
| 3985 ''' | 3986 ''' |
| 3986 if not newvalues.has_key('assignedto'): | 3987 if not newvalues.has_key('assignedto'): |
| 3987 # don't care | 3988 # don't care |
| 3988 return | 3989 return |
| 4001 | 4002 |
| 4002 | 4003 |
| 4003 Users may only edit their issues | 4004 Users may only edit their issues |
| 4004 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 4005 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4005 | 4006 |
| 4006 Users registering themselves are granted Provisional access - meaning they | 4007 In this case, users registering themselves are granted Provisional |
| 4008 access, meaning they | |
| 4007 have access to edit the issues they submit, but not others. We create a new | 4009 have access to edit the issues they submit, but not others. We create a new |
| 4008 Role called "Provisional User" which is granted to newly-registered users, | 4010 Role called "Provisional User" which is granted to newly-registered users, |
| 4009 and has limited access. One of the Permissions they have is the new "Edit | 4011 and has limited access. One of the Permissions they have is the new "Edit |
| 4010 Own" on issues (regular users have "Edit".) | 4012 Own" on issues (regular users have "Edit".) |
| 4011 | 4013 |
| 4023 db.security.addPermissionToRole('Provisional User', 'Create', 'issue') | 4025 db.security.addPermissionToRole('Provisional User', 'Create', 'issue') |
| 4024 def own_issue(db, userid, itemid): | 4026 def own_issue(db, userid, itemid): |
| 4025 '''Determine whether the userid matches the creator of the issue.''' | 4027 '''Determine whether the userid matches the creator of the issue.''' |
| 4026 return userid == db.issue.get(itemid, 'creator') | 4028 return userid == db.issue.get(itemid, 'creator') |
| 4027 p = db.security.addPermission(name='Edit', klass='issue', | 4029 p = db.security.addPermission(name='Edit', klass='issue', |
| 4028 code=own_issue, description='Can only edit own issues') | 4030 check=own_issue, description='Can only edit own issues') |
| 4029 db.security.addPermissionToRole('Provisional User', p) | 4031 db.security.addPermissionToRole('Provisional User', p) |
| 4030 p = db.security.addPermission(name='View', klass='issue', | 4032 p = db.security.addPermission(name='View', klass='issue', |
| 4031 code=own_issue, description='Can only view own issues') | 4033 check=own_issue, description='Can only view own issues') |
| 4032 db.security.addPermissionToRole('Provisional User', p) | 4034 db.security.addPermissionToRole('Provisional User', p) |
| 4033 | 4035 |
| 4034 # Assign the Permissions for issue-related classes | 4036 # Assign the Permissions for issue-related classes |
| 4035 for cl in 'file', 'msg', 'query', 'keyword': | 4037 for cl in 'file', 'msg', 'query', 'keyword': |
| 4036 db.security.addPermissionToRole('Provisional User', 'View', cl) | 4038 db.security.addPermissionToRole('Provisional User', 'View', cl) |
| 4042 # and give the new users access to the web and email interface | 4044 # and give the new users access to the web and email interface |
| 4043 db.security.addPermissionToRole('Provisional User', 'Web Access') | 4045 db.security.addPermissionToRole('Provisional User', 'Web Access') |
| 4044 db.security.addPermissionToRole('Provisional User', 'Email Access') | 4046 db.security.addPermissionToRole('Provisional User', 'Email Access') |
| 4045 | 4047 |
| 4046 | 4048 |
| 4047 Then in the ``config.ini`` we change the Role assigned to newly-registered | 4049 Then, in ``config.ini``, we change the Role assigned to newly-registered |
| 4048 users, replacing the existing ``'User'`` values:: | 4050 users, replacing the existing ``'User'`` values:: |
| 4049 | 4051 |
| 4050 [main] | 4052 [main] |
| 4051 ... | 4053 ... |
| 4052 new_web_user_roles = 'Provisional User' | 4054 new_web_user_roles = 'Provisional User' |
| 4057 --------------------------------- | 4059 --------------------------------- |
| 4058 | 4060 |
| 4059 Adding action links to the index page | 4061 Adding action links to the index page |
| 4060 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 4062 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4061 | 4063 |
| 4062 Add a column to the item.index.html template. | 4064 Add a column to the ``item.index.html`` template. |
| 4063 | 4065 |
| 4064 Resolving the issue:: | 4066 Resolving the issue:: |
| 4065 | 4067 |
| 4066 <a tal:attributes="href | 4068 <a tal:attributes="href |
| 4067 string:issue${i/id}?:status=resolved&:action=edit">resolve</a> | 4069 string:issue${i/id}?:status=resolved&:action=edit">resolve</a> |
| 4069 "Take" the issue:: | 4071 "Take" the issue:: |
| 4070 | 4072 |
| 4071 <a tal:attributes="href | 4073 <a tal:attributes="href |
| 4072 string:issue${i/id}?:assignedto=${request/user/id}&:action=edit">take</a> | 4074 string:issue${i/id}?:assignedto=${request/user/id}&:action=edit">take</a> |
| 4073 | 4075 |
| 4074 ... and so on | 4076 ... and so on. |
| 4075 | 4077 |
| 4076 Colouring the rows in the issue index according to priority | 4078 Colouring the rows in the issue index according to priority |
| 4077 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 4079 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4078 | 4080 |
| 4079 A simple ``tal:attributes`` statement will do the bulk of the work here. In | 4081 A simple ``tal:attributes`` statement will do the bulk of the work here. In |
| 4080 the ``issue.index.html`` template, add to the ``<tr>`` that displays the | 4082 the ``issue.index.html`` template, add this to the ``<tr>`` that |
| 4081 actual rows of data:: | 4083 displays the rows of data:: |
| 4082 | 4084 |
| 4083 <tr tal:attributes="class string:priority-${i/priority/plain}"> | 4085 <tr tal:attributes="class string:priority-${i/priority/plain}"> |
| 4084 | 4086 |
| 4085 and then in your stylesheet (``style.css``) specify the colouring for the | 4087 and then in your stylesheet (``style.css``) specify the colouring for the |
| 4086 different priorities, like:: | 4088 different priorities, as follows:: |
| 4087 | 4089 |
| 4088 tr.priority-critical td { | 4090 tr.priority-critical td { |
| 4089 background-color: red; | 4091 background-color: red; |
| 4090 } | 4092 } |
| 4091 | 4093 |
| 4125 <td tal:condition="request/show/status" | 4127 <td tal:condition="request/show/status" |
| 4126 tal:content="structure i/status/field"> </td> | 4128 tal:content="structure i/status/field"> </td> |
| 4127 | 4129 |
| 4128 this will result in an edit field for the status property. | 4130 this will result in an edit field for the status property. |
| 4129 | 4131 |
| 4130 3. after the ``tal:block`` which lists the actual index items (marked by | 4132 3. after the ``tal:block`` which lists the index items (marked by |
| 4131 ``tal:repeat="i batch"``) add a new table row:: | 4133 ``tal:repeat="i batch"``) add a new table row:: |
| 4132 | 4134 |
| 4133 <tr> | 4135 <tr> |
| 4134 <td tal:attributes="colspan python:len(request.columns)"> | 4136 <td tal:attributes="colspan python:len(request.columns)"> |
| 4135 <input type="submit" value=" Save Changes "> | 4137 <input type="submit" value=" Save Changes "> |
| 4137 <tal:block replace="structure request/indexargs_form" /> | 4139 <tal:block replace="structure request/indexargs_form" /> |
| 4138 </td> | 4140 </td> |
| 4139 </tr> | 4141 </tr> |
| 4140 | 4142 |
| 4141 which gives us a submit button, indicates that we are performing an edit | 4143 which gives us a submit button, indicates that we are performing an edit |
| 4142 on any changed statuses and the final block will make sure that the | 4144 on any changed statuses. The final ``tal:block`` will make sure that the |
| 4143 current index view parameters (filtering, columns, etc) will be used in | 4145 current index view parameters (filtering, columns, etc) will be used in |
| 4144 rendering the next page (the results of the editing). | 4146 rendering the next page (the results of the editing). |
| 4145 | 4147 |
| 4146 | 4148 |
| 4147 Displaying only message summaries in the issue display | 4149 Displaying only message summaries in the issue display |
| 4148 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 4150 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4149 | 4151 |
| 4150 Alter the issue.item template section for messages to:: | 4152 Alter the ``issue.item`` template section for messages to:: |
| 4151 | 4153 |
| 4152 <table class="messages" tal:condition="context/messages"> | 4154 <table class="messages" tal:condition="context/messages"> |
| 4153 <tr><th colspan="5" class="header">Messages</th></tr> | 4155 <tr><th colspan="5" class="header">Messages</th></tr> |
| 4154 <tr tal:repeat="msg context/messages"> | 4156 <tr tal:repeat="msg context/messages"> |
| 4155 <td><a tal:attributes="href string:msg${msg/id}" | 4157 <td><a tal:attributes="href string:msg${msg/id}" |
| 4169 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 4171 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4170 | 4172 |
| 4171 This is pretty simple - all we need to do is copy the code from the | 4173 This is pretty simple - all we need to do is copy the code from the |
| 4172 example `displaying only message summaries in the issue display`_ into | 4174 example `displaying only message summaries in the issue display`_ into |
| 4173 our template alongside the summary display, and then introduce a switch | 4175 our template alongside the summary display, and then introduce a switch |
| 4174 that shows either one or the other. We'll use a new form variable, | 4176 that shows either the one or the other. We'll use a new form variable, |
| 4175 ``@whole_messages`` to achieve this:: | 4177 ``@whole_messages`` to achieve this:: |
| 4176 | 4178 |
| 4177 <table class="messages" tal:condition="context/messages"> | 4179 <table class="messages" tal:condition="context/messages"> |
| 4178 <tal:block tal:condition="not:request/form/@whole_messages/value | python:0"> | 4180 <tal:block tal:condition="not:request/form/@whole_messages/value | python:0"> |
| 4179 <tr><th colspan="3" class="header">Messages</th> | 4181 <tr><th colspan="3" class="header">Messages</th> |
| 4246 . | 4248 . |
| 4247 . | 4249 . |
| 4248 . | 4250 . |
| 4249 </form> | 4251 </form> |
| 4250 | 4252 |
| 4251 Note that later in the form, I test the value of "cat" include form | 4253 Note that later in the form, I use the value of "cat" to decide which |
| 4252 elements that are appropriate. For example:: | 4254 form elements should be displayed. For example:: |
| 4253 | 4255 |
| 4254 <tal:block tal:condition="python:cat in '6 10 13 14 15 16 17'.split()"> | 4256 <tal:block tal:condition="python:cat in '6 10 13 14 15 16 17'.split()"> |
| 4255 <tr> | 4257 <tr> |
| 4256 <th>Operating System</th> | 4258 <th>Operating System</th> |
| 4257 <td tal:content="structure context/os/field"></td> | 4259 <td tal:content="structure context/os/field"></td> |
