comparison doc/customizing.txt @ 2983:9614a101b68f

Stuff from the train ride this morning: - Extend the property concept in Permissions to allow a list of properties - Fix the cgi templating code to check the correct permission when rendering edit fields - A swag of changes (just the start) fixing up the customisation doc for the new tracker layout and permissions setup
author Richard Jones <richard@users.sourceforge.net>
date Tue, 30 Nov 2004 08:32:57 +0000
parents 1ae91c2fa5fe
children b9a55628a78d
comparison
equal deleted inserted replaced
2982:22f16d0646ce 2983:9614a101b68f
1 =================== 1 ===================
2 Customising Roundup 2 Customising Roundup
3 =================== 3 ===================
4 4
5 :Version: $Revision: 1.159 $ 5 :Version: $Revision: 1.160 $
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::
288 Note: if you modify the schema, you'll most likely need to edit the 288 Note: if you modify the schema, you'll most likely need to edit the
289 `web interface`_ HTML template files and `detectors`_ to reflect 289 `web interface`_ HTML template files and `detectors`_ to reflect
290 your changes. 290 your changes.
291 291
292 A tracker schema defines what data is stored in the tracker's database. 292 A tracker schema defines what data is stored in the tracker's database.
293 Schemas are defined using Python code in the ``dbinit.py`` module of your 293 Schemas are defined using Python code in the ``schema.py`` module of your
294 tracker. 294 tracker.
295 295
296 The ``dbinit.py`` module 296 The ``schema.py`` module
297 ------------------------ 297 ------------------------
298 298
299 The ``dbinit.py`` module contains two functions: 299 The ``schema.py`` module contains two functions:
300 300
301 **open** 301 **open**
302 This function defines what your tracker looks like on the inside, the 302 This function defines what your tracker looks like on the inside, the
303 **schema** of the tracker. It defines the **Classes** and **properties** 303 **schema** of the tracker. It defines the **Classes** and **properties**
304 on each class. It also defines the **security** for those Classes. The 304 on each class. It also defines the **security** for those Classes. The
700 priority, resolution, ...) may be done either before or after the 700 priority, resolution, ...) may be done either before or after the
701 tracker is initialised. The actual method of doing so is completely 701 tracker is initialised. The actual method of doing so is completely
702 different in each case though, so be careful to use the right one. 702 different in each case though, so be careful to use the right one.
703 703
704 **Changing content before tracker initialisation** 704 **Changing content before tracker initialisation**
705 Edit the dbinit module in your tracker to alter the items created in 705 Edit the schema module in your tracker to alter the items created in
706 using the ``create()`` methods. 706 using the ``create()`` methods.
707 707
708 **Changing content after tracker initialisation** 708 **Changing content after tracker initialisation**
709 As the "admin" user, click on the "class list" link in the web 709 As the "admin" user, click on the "class list" link in the web
710 interface to bring up a list of all database classes. Click on the 710 interface to bring up a list of all database classes. Click on the
721 Security / Access Controls 721 Security / Access Controls
722 ========================== 722 ==========================
723 723
724 A set of Permissions is built into the security module by default: 724 A set of Permissions is built into the security module by default:
725 725
726 - Create (everything)
726 - Edit (everything) 727 - Edit (everything)
727 - View (everything) 728 - View (everything)
728 729
729 Every Class you define in your tracker's schema also gets an Edit and View 730 Every Class you define in your tracker's schema also gets an Create, Edit
730 Permission of its own. 731 and View Permission of its own.
731 732
732 The default interfaces define: 733 The default interfaces define:
733 734
734 - Web Registration 735 - Web Registration
735 - Web Access 736 - Web Access
737 - Email Registration 738 - Email Registration
738 - Email Access 739 - Email Access
739 740
740 These are hooked into the default Roles: 741 These are hooked into the default Roles:
741 742
742 - Admin (Edit everything, View everything, Web Roles) 743 - Admin (Create, Edit, View and everything; Web Roles)
743 - User (Web Access, Email Access) 744 - User (Web Access; Email Access)
744 - Anonymous (Web Registration, Email Registration) 745 - Anonymous (Web Registration; Email Registration)
745 746
746 And finally, the "admin" user gets the "Admin" Role, and the "anonymous" 747 And finally, the "admin" user gets the "Admin" Role, and the "anonymous"
747 user gets "Anonymous" assigned when the database is initialised on 748 user gets "Anonymous" assigned when the tracker is installed.
748 installation. The two default schemas then define: 749
749 750 For the "User" Role, the "classic" tracker defines:
750 - Edit issue, View issue (both) 751
751 - Edit file, View file (both) 752 - Create, Edit and View issue, file, msg, query, keyword
752 - Edit msg, View msg (both) 753 - View priority, status
753 - Edit support, View support (extended only) 754 - View user
754 755 - Edit their own record
755 and assign those Permissions to the "User" Role. Put together, these 756
756 settings appear in the ``open()`` function of the tracker ``dbinit.py`` 757 And the "Anonymous" Role is defined as:
757 (the following is taken from the "minimal" template's ``dbinit.py``):: 758
759 - Create user (for registration)
760 - View issue, file, msg, query, keyword, priority, status
761
762 Put together, these settings appear in the tracker's ``schema.py`` file::
758 763
759 # 764 #
760 # SECURITY SETTINGS 765 # TRACKER SECURITY SETTINGS
761 # 766 #
762 # and give the regular users access to the web and email interface 767 # See the configuration and customisation document for information
768 # about security setup.
769
770 #
771 # REGULAR USERS
772 #
773 # Give the regular users access to the web and email interface
763 p = db.security.getPermission('Web Access') 774 p = db.security.getPermission('Web Access')
764 db.security.addPermissionToRole('User', p) 775 db.security.addPermissionToRole('User', p)
765 p = db.security.getPermission('Email Access') 776 p = db.security.getPermission('Email Access')
766 db.security.addPermissionToRole('User', p) 777 db.security.addPermissionToRole('User', p)
767 778
779 # Assign the access and edit Permissions for issue, file and message
780 # to regular users now
781 for cl in 'issue', 'file', 'msg', 'query', 'keyword':
782 p = db.security.getPermission('View', cl)
783 db.security.addPermissionToRole('User', p)
784 p = db.security.getPermission('Edit', cl)
785 db.security.addPermissionToRole('User', p)
786 p = db.security.getPermission('Create', cl)
787 db.security.addPermissionToRole('User', p)
788 for cl in 'priority', 'status':
789 p = db.security.getPermission('View', cl)
790 db.security.addPermissionToRole('User', p)
791
768 # May users view other user information? Comment these lines out 792 # May users view other user information? Comment these lines out
769 # if you don't want them to 793 # if you don't want them to
770 p = db.security.getPermission('View', 'user') 794 p = db.security.getPermission('View', 'user')
771 db.security.addPermissionToRole('User', p) 795 db.security.addPermissionToRole('User', p)
772 796
773 # Assign the appropriate permissions to the anonymous user's 797 # Users should be able to edit their own details. Note that this
774 # Anonymous role. Choices here are: 798 # permission is limited to only the situation where the Viewed or
775 # - Allow anonymous users to register through the web 799 # Edited item is their own.
776 p = db.security.getPermission('Web Registration') 800 def own_record(db, userid, itemid):
801 '''Determine whether the userid matches the item being accessed.'''
802 return userid == itemid
803 p = db.security.addPermission(name='View', klass='user', check=own_record,
804 description="User is allowed to view their own user details")
805 p = db.security.addPermission(name='Edit', klass='user', check=own_record,
806 description="User is allowed to edit their own user details")
807 db.security.addPermissionToRole('User', p)
808
809 #
810 # ANONYMOUS USER PERMISSIONS
811 #
812 # Let anonymous users access the web interface. Note that almost all
813 # trackers will need this Permission. The only situation where it's not
814 # required is in a tracker that uses an HTTP Basic Authenticated front-end.
815 p = db.security.getPermission('Web Access')
777 db.security.addPermissionToRole('Anonymous', p) 816 db.security.addPermissionToRole('Anonymous', p)
778 # - Allow anonymous (new) users to register through the email 817
779 # gateway 818 # Let anonymous users access the email interface (note that this implies
780 p = db.security.getPermission('Email Registration') 819 # that they will be registered automatically, hence they will need the
820 # "Create" user Permission below)
821 p = db.security.getPermission('Email Access')
781 db.security.addPermissionToRole('Anonymous', p) 822 db.security.addPermissionToRole('Anonymous', p)
823
824 # Assign the appropriate permissions to the anonymous user's Anonymous
825 # Role. Choices here are:
826 # - Allow anonymous users to register
827 p = db.security.getPermission('Create', 'user')
828 db.security.addPermissionToRole('Anonymous', p)
829
830 # Allow anonymous users access to view issues (and the related, linked
831 # information)
832 for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status':
833 p = db.security.getPermission('View', cl)
834 db.security.addPermissionToRole('Anonymous', p)
835
836 # [OPTIONAL]
837 # Allow anonymous users access to create or edit "issue" items (and the
838 # related file and message items)
839 #for cl in 'issue', 'file', 'msg':
840 # p = db.security.getPermission('Create', cl)
841 # db.security.addPermissionToRole('Anonymous', p)
842 # p = db.security.getPermission('Edit', cl)
843 # db.security.addPermissionToRole('Anonymous', p)
782 844
783 845
784 New User Roles 846 New User Roles
785 -------------- 847 --------------
786 848
804 Adding a new Permission 866 Adding a new Permission
805 ~~~~~~~~~~~~~~~~~~~~~~~ 867 ~~~~~~~~~~~~~~~~~~~~~~~
806 868
807 When adding a new Permission, you will need to: 869 When adding a new Permission, you will need to:
808 870
809 1. add it to your tracker's dbinit so it is created, using 871 1. add it to your tracker's ``schema.py`` so it is created, using
810 ``security.addPermission``, for example:: 872 ``security.addPermission``, for example::
811 873
812 self.security.addPermission(name="View", klass='frozzle', 874 self.security.addPermission(name="View", klass='frozzle',
813 description="User is allowed to access frozzles") 875 description="User is allowed to access frozzles")
814 876
817 "``roundup-admin security``") 879 "``roundup-admin security``")
818 3. add it to the relevant HTML interface templates 880 3. add it to the relevant HTML interface templates
819 4. add it to the appropriate xxxPermission methods on in your tracker 881 4. add it to the appropriate xxxPermission methods on in your tracker
820 interfaces module 882 interfaces module
821 883
884 The ``addPermission`` method takes a couple of optional parameters:
885
886 **properties**
887 A sequence of property names that are the only properties to apply the
888 new Permission to (eg. ``... klass='user', properties=('name',
889 'email') ...``)
890 **code**
891 A function to be execute which returns boolean determining whether the
892 Permission is allowed. The function has the signature ``check(db, userid,
893 itemid)`` where ``db`` is a handle on the open database, ``userid`` is
894 the user attempting access and ``itemid`` is the specific item being
895 accessed.
822 896
823 Example Scenarios 897 Example Scenarios
824 ~~~~~~~~~~~~~~~~~ 898 ~~~~~~~~~~~~~~~~~
825 899
826 **automatic registration of users in the e-mail gateway** 900 **automatic registration of users in the e-mail gateway**
1059 1133
1060 Each action class also has a ``*permission*`` method which determines whether 1134 Each action class also has a ``*permission*`` method which determines whether
1061 the action is permissible given the current user. The base permission checks 1135 the action is permissible given the current user. The base permission checks
1062 are: 1136 are:
1063 1137
1138 XXX REVIEW for Permissions changes
1139
1064 **login** 1140 **login**
1065 Determine whether the user has permission to log in. Base behaviour is 1141 Determine whether the user has permission to log in. Base behaviour is
1066 to check the user has "Web Access". 1142 to check the user has "Web Access".
1067 **logout** 1143 **logout**
1068 No permission checks are made. 1144 No permission checks are made.
1069 **register** 1145 **register**
1070 Determine whether the user has permission to register. Base behaviour 1146 Determine whether the user has permission to register. Base behaviour
1071 is to check the user has the "Web Registration" Permission. 1147 is to check the user has the "Web Registration" Permission.
1072 **edit** 1148 **edit**
1073 Determine whether the user has permission to edit this item. Base 1149 Determine whether the user has permission to edit this item. If we're
1074 behaviour is to check whether the user can edit this class. If we're
1075 editing the "user" class, users are allowed to edit their own details - 1150 editing the "user" class, users are allowed to edit their own details -
1076 unless they try to edit the "roles" property, which requires the 1151 unless they try to edit the "roles" property, which requires the
1077 special Permission "Web Roles". 1152 special Permission "Web Roles".
1078 **new** 1153 **new**
1079 Determine whether the user has permission to create (or edit) this 1154 Determine whether the user has permission to create this item. No
1080 item. Base behaviour is to check the user can edit this class. No
1081 additional property checks are made. Additionally, new user items may 1155 additional property checks are made. Additionally, new user items may
1082 be created if the user has the "Web Registration" Permission. 1156 be created if the user has the "Web Registration" Permission.
1083 **editCSV** 1157 **editCSV**
1084 Determine whether the user has permission to edit this class. Base 1158 Determine whether the user has permission to edit this class. Base
1085 behaviour is to check whether the user may edit this class. 1159 behaviour is to check whether the user may edit this class.
1162 The value of the form variable is converted 1236 The value of the form variable is converted
1163 appropriately, depending on the type of the property. 1237 appropriately, depending on the type of the property.
1164 1238
1165 For a Link('klass') property, the form value is a 1239 For a Link('klass') property, the form value is a
1166 single key for 'klass', where the key field is 1240 single key for 'klass', where the key field is
1167 specified in dbinit.py. 1241 specified in schema.py.
1168 1242
1169 For a Multilink('klass') property, the form value is a 1243 For a Multilink('klass') property, the form value is a
1170 comma-separated list of keys for 'klass', where the 1244 comma-separated list of keys for 'klass', where the
1171 key field is specified in dbinit.py. 1245 key field is specified in schema.py.
1172 1246
1173 Note that for simple-form-variables specifiying Link 1247 Note that for simple-form-variables specifiying Link
1174 and Multilink properties, the linked-to class must 1248 and Multilink properties, the linked-to class must
1175 have a key field. 1249 have a key field.
1176 1250
2508 Adding a field to the database 2582 Adding a field to the database
2509 :::::::::::::::::::::::::::::: 2583 ::::::::::::::::::::::::::::::
2510 2584
2511 This is the easiest part of the change. The category would just be a 2585 This is the easiest part of the change. The category would just be a
2512 plain string, nothing fancy. To change what is in the database you need 2586 plain string, nothing fancy. To change what is in the database you need
2513 to add some lines to the ``open()`` function in ``dbinit.py``. Under the 2587 to add some lines to the ``open()`` function in ``schema.py``. Under the
2514 comment:: 2588 comment::
2515 2589
2516 # add any additional database schema configuration here 2590 # add any additional database schema configuration here
2517 2591
2518 add:: 2592 add::
2531 2605
2532 Adding the above lines allows us to create categories, but they're not 2606 Adding the above lines allows us to create categories, but they're not
2533 tied to the issues that we are going to be creating. It's just a list of 2607 tied to the issues that we are going to be creating. It's just a list of
2534 categories off on its own, which isn't much use. We need to link it in 2608 categories off on its own, which isn't much use. We need to link it in
2535 with the issues. To do that, find the lines in the ``open()`` function 2609 with the issues. To do that, find the lines in the ``open()`` function
2536 in ``dbinit.py`` which set up the "issue" class, and then add a link to 2610 in ``schema.py`` which set up the "issue" class, and then add a link to
2537 the category:: 2611 the category::
2538 2612
2539 issue = IssueClass(db, "issue", ... , 2613 issue = IssueClass(db, "issue", ... ,
2540 category=Multilink("category"), ... ) 2614 category=Multilink("category"), ... )
2541 2615
2550 Populating the new category class 2624 Populating the new category class
2551 ::::::::::::::::::::::::::::::::: 2625 :::::::::::::::::::::::::::::::::
2552 2626
2553 If you haven't initialised the database with the roundup-admin 2627 If you haven't initialised the database with the roundup-admin
2554 "initialise" command, then you can add the following to the tracker 2628 "initialise" command, then you can add the following to the tracker
2555 ``dbinit.py`` in the ``init()`` function under the comment:: 2629 ``schema.py`` in the ``init()`` function under the comment::
2556 2630
2557 # add any additional database create steps here - but only if you 2631 # add any additional database create steps here - but only if you
2558 # haven't initialised the database with the admin "initialise" command 2632 # haven't initialised the database with the admin "initialise" command
2559 2633
2560 Add:: 2634 Add::
2589 doesn't suit us, as we want any user to be able to create new categories 2663 doesn't suit us, as we want any user to be able to create new categories
2590 as required, and obviously everyone needs to be able to view the 2664 as required, and obviously everyone needs to be able to view the
2591 categories of issues for it to be useful. 2665 categories of issues for it to be useful.
2592 2666
2593 We therefore need to change the security of the category objects. This 2667 We therefore need to change the security of the category objects. This
2594 is also done in the ``open()`` function of ``dbinit.py``. 2668 is also done in the ``open()`` function of ``schema.py``.
2595 2669
2596 There are currently two loops which set up permissions and then assign 2670 There are currently two loops which set up permissions and then assign
2597 them to various roles. Simply add the new "category" to both lists:: 2671 them to various roles. Simply add the new "category" to both lists::
2598 2672
2599 # Assign the access and edit permissions for issue, file and message 2673 # Assign the access and edit permissions for issue, file and message
2906 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2980 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2907 2981
2908 We want to log the dates and amount of time spent working on issues, and 2982 We want to log the dates and amount of time spent working on issues, and
2909 be able to give a summary of the total time spent on a particular issue. 2983 be able to give a summary of the total time spent on a particular issue.
2910 2984
2911 1. Add a new class to your tracker ``dbinit.py``:: 2985 1. Add a new class to your tracker ``schema.py``::
2912 2986
2913 # storage for time logging 2987 # storage for time logging
2914 timelog = Class(db, "timelog", period=Interval()) 2988 timelog = Class(db, "timelog", period=Interval())
2915 2989
2916 Note that we automatically get the date of the time log entry 2990 Note that we automatically get the date of the time log entry
2917 creation through the standard property "creation". 2991 creation through the standard property "creation".
2918 2992
2919 2. Link to the new class from your issue class (again, in 2993 2. Link to the new class from your issue class (again, in
2920 ``dbinit.py``):: 2994 ``schema.py``)::
2921 2995
2922 issue = IssueClass(db, "issue", 2996 issue = IssueClass(db, "issue",
2923 assignedto=Link("user"), topic=Multilink("keyword"), 2997 assignedto=Link("user"), topic=Multilink("keyword"),
2924 priority=Link("priority"), status=Link("status"), 2998 priority=Link("priority"), status=Link("status"),
2925 times=Multilink("timelog")) 2999 times=Multilink("timelog"))
3010 3084
3011 1. Figure out what information you're going to want to capture. OK, so 3085 1. Figure out what information you're going to want to capture. OK, so
3012 this is obvious, but sometimes it's better to actually sit down for a 3086 this is obvious, but sometimes it's better to actually sit down for a
3013 while and think about the schema you're going to implement. 3087 while and think about the schema you're going to implement.
3014 3088
3015 2. Add the new issue class to your tracker's ``dbinit.py`` - in this 3089 2. Add the new issue class to your tracker's ``schema.py`` - in this
3016 example, we're adding a "system support" class. Just after the "issue" 3090 example, we're adding a "system support" class. Just after the "issue"
3017 class definition in the "open" function, add:: 3091 class definition in the "open" function, add::
3018 3092
3019 support = IssueClass(db, "support", 3093 support = IssueClass(db, "support",
3020 assignedto=Link("user"), topic=Multilink("keyword"), 3094 assignedto=Link("user"), topic=Multilink("keyword"),
3458 We needed the ability to mark certain issues as "blockers" - that is, 3532 We needed the ability to mark certain issues as "blockers" - that is,
3459 they can't be resolved until another issue (the blocker) they rely on is 3533 they can't be resolved until another issue (the blocker) they rely on is
3460 resolved. To achieve this: 3534 resolved. To achieve this:
3461 3535
3462 1. Create a new property on the issue Class, 3536 1. Create a new property on the issue Class,
3463 ``blockers=Multilink("issue")``. Edit your tracker's dbinit.py file. 3537 ``blockers=Multilink("issue")``. Edit your tracker's schema.py file.
3464 Where the "issue" class is defined, something like:: 3538 Where the "issue" class is defined, something like::
3465 3539
3466 issue = IssueClass(db, "issue", 3540 issue = IssueClass(db, "issue",
3467 assignedto=Link("user"), topic=Multilink("keyword"), 3541 assignedto=Link("user"), topic=Multilink("keyword"),
3468 priority=Link("priority"), status=Link("status")) 3542 priority=Link("priority"), status=Link("status"))
3614 The change in the database to make is that for any user there should be 3688 The change in the database to make is that for any user there should be
3615 a list of topics for which he wants to be put on the nosy list. Adding 3689 a list of topics for which he wants to be put on the nosy list. Adding
3616 a ``Multilink`` of ``keyword`` seem to fullfill this (note that within 3690 a ``Multilink`` of ``keyword`` seem to fullfill this (note that within
3617 the code topics are called ``keywords``.) As such, all what has to be 3691 the code topics are called ``keywords``.) As such, all what has to be
3618 done is to add a new field to the definition of ``user`` within the 3692 done is to add a new field to the definition of ``user`` within the
3619 file ``dbinit.py``. We will call this new field ``nosy_keywords``, and 3693 file ``schema.py``. We will call this new field ``nosy_keywords``, and
3620 the updated definition of user will be:: 3694 the updated definition of user will be::
3621 3695
3622 user = Class(db, "user", 3696 user = Class(db, "user",
3623 username=String(), password=Password(), 3697 username=String(), password=Password(),
3624 address=String(), realname=String(), 3698 address=String(), realname=String(),
3762 ----------------------------------- 3836 -----------------------------------
3763 3837
3764 Restricting the list of users that are assignable to a task 3838 Restricting the list of users that are assignable to a task
3765 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3839 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3766 3840
3767 1. In your tracker's "dbinit.py", create a new Role, say "Developer":: 3841 1. In your tracker's ``schema.py``, create a new Role, say "Developer"::
3768 3842
3769 db.security.addRole(name='Developer', description='A developer') 3843 db.security.addRole(name='Developer', description='A developer')
3770 3844
3771 2. Just after that, create a new Permission, say "Fixer", specific to 3845 2. Just after that, create a new Permission, say "Fixer", specific to
3772 "issue":: 3846 "issue"::
3827 and has limited access. One of the Permissions they have is the new "Edit 3901 and has limited access. One of the Permissions they have is the new "Edit
3828 Own" on issues (regular users have "Edit".) We back up the permissions with 3902 Own" on issues (regular users have "Edit".) We back up the permissions with
3829 an auditor. 3903 an auditor.
3830 3904
3831 First up, we create the new Role and Permission structure in 3905 First up, we create the new Role and Permission structure in
3832 ``dbinit.py``:: 3906 ``schema.py``::
3833 3907
3834 # New users not approved by the admin 3908 # New users not approved by the admin
3835 db.security.addRole(name='Provisional User', 3909 db.security.addRole(name='Provisional User',
3836 description='New user registered via web or email') 3910 description='New user registered via web or email')
3837 p = db.security.addPermission(name='Edit Own', klass='issue', 3911 p = db.security.addPermission(name='Edit Own', klass='issue',

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