Mercurial > p > roundup > code
comparison doc/customizing.txt @ 1244:8dd4f736370b
merge from maintenance branch
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Thu, 03 Oct 2002 06:56:30 +0000 |
| parents | dd52bf10f934 |
| children | d8c98af869ff |
comparison
equal
deleted
inserted
replaced
| 1243:3a028d2f7830 | 1244:8dd4f736370b |
|---|---|
| 1 =================== | 1 =================== |
| 2 Customising Roundup | 2 Customising Roundup |
| 3 =================== | 3 =================== |
| 4 | 4 |
| 5 :Version: $Revision: 1.50 $ | 5 :Version: $Revision: 1.51 $ |
| 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:: |
| 11 :depth: 1 | 11 :depth: 1 |
| 12 | 12 |
| 13 What You Can Do | 13 What You Can Do |
| 14 =============== | 14 =============== |
| 15 | |
| 16 Before you get too far, it's probably worth having a quick read of the Roundup | |
| 17 `design documentation`_. | |
| 15 | 18 |
| 16 Customisation of Roundup can take one of five forms: | 19 Customisation of Roundup can take one of five forms: |
| 17 | 20 |
| 18 1. `tracker configuration`_ file changes | 21 1. `tracker configuration`_ file changes |
| 19 2. database, or `tracker schema`_ changes | 22 2. database, or `tracker schema`_ changes |
| 20 3. "definition" class `database content`_ changes | 23 3. "definition" class `database content`_ changes |
| 21 4. behavioural changes, through detectors_ | 24 4. behavioural changes, through detectors_ |
| 22 5. `access controls`_ | 25 5. `access controls`_ |
| 26 6. change the `web interface`_ | |
| 23 | 27 |
| 24 The third case is special because it takes two distinctly different forms | 28 The third case is special because it takes two distinctly different forms |
| 25 depending upon whether the tracker has been initialised or not. The other two | 29 depending upon whether the tracker has been initialised or not. The other two |
| 26 may be done at any time, before or after tracker initialisation. Yes, this | 30 may be done at any time, before or after tracker initialisation. Yes, this |
| 27 includes adding or removing properties from classes. | 31 includes adding or removing properties from classes. |
| 188 Schemas are defined using Python code in the ``dbinit.py`` module of your | 192 Schemas are defined using Python code in the ``dbinit.py`` module of your |
| 189 tracker. The "classic" schema looks like this:: | 193 tracker. The "classic" schema looks like this:: |
| 190 | 194 |
| 191 pri = Class(db, "priority", name=String(), order=String()) | 195 pri = Class(db, "priority", name=String(), order=String()) |
| 192 pri.setkey("name") | 196 pri.setkey("name") |
| 193 pri.create(name="critical", order="1") | |
| 194 pri.create(name="urgent", order="2") | |
| 195 pri.create(name="bug", order="3") | |
| 196 pri.create(name="feature", order="4") | |
| 197 pri.create(name="wish", order="5") | |
| 198 | 197 |
| 199 stat = Class(db, "status", name=String(), order=String()) | 198 stat = Class(db, "status", name=String(), order=String()) |
| 200 stat.setkey("name") | 199 stat.setkey("name") |
| 201 stat.create(name="unread", order="1") | |
| 202 stat.create(name="deferred", order="2") | |
| 203 stat.create(name="chatting", order="3") | |
| 204 stat.create(name="need-eg", order="4") | |
| 205 stat.create(name="in-progress", order="5") | |
| 206 stat.create(name="testing", order="6") | |
| 207 stat.create(name="done-cbb", order="7") | |
| 208 stat.create(name="resolved", order="8") | |
| 209 | 200 |
| 210 keyword = Class(db, "keyword", name=String()) | 201 keyword = Class(db, "keyword", name=String()) |
| 211 keyword.setkey("name") | 202 keyword.setkey("name") |
| 212 | 203 |
| 213 user = Class(db, "user", username=String(), password=String(), | 204 user = Class(db, "user", username=String(), organisation=String(), |
| 214 address=String(), realname=String(), phone=String(), | 205 password=String(), address=String(), realname=String(), phone=String()) |
| 215 organisation=String()) | |
| 216 user.setkey("username") | 206 user.setkey("username") |
| 217 user.create(username="admin", password=adminpw, | 207 |
| 218 address=config.ADMIN_EMAIL) | 208 msg = FileClass(db, "msg", author=Link("user"), summary=String(), |
| 219 | 209 date=Date(), recipients=Multilink("user"), files=Multilink("file")) |
| 220 msg = FileClass(db, "msg", author=Link("user"), recipients=Multilink | |
| 221 ("user"), date=Date(), summary=String(), files=Multilink("file")) | |
| 222 | 210 |
| 223 file = FileClass(db, "file", name=String(), type=String()) | 211 file = FileClass(db, "file", name=String(), type=String()) |
| 224 | 212 |
| 225 issue = IssueClass(db, "issue", assignedto=Link("user"), | 213 issue = IssueClass(db, "issue", topic=Multilink("keyword"), |
| 226 topic=Multilink("keyword"), priority=Link("priority"), status=Link | 214 status=Link("status"), assignedto=Link("user"), |
| 227 ("status")) | 215 priority=Link("priority")) |
| 228 issue.setkey('title') | 216 issue.setkey('title') |
| 229 | |
| 230 XXX security definitions | |
| 231 | 217 |
| 232 Classes and Properties - creating a new information store | 218 Classes and Properties - creating a new information store |
| 233 --------------------------------------------------------- | 219 --------------------------------------------------------- |
| 234 | 220 |
| 235 In the tracker above, we've defined 7 classes of information: | 221 In the tracker above, we've defined 7 classes of information: |
| 430 alter or remove items from the classes in question. | 416 alter or remove items from the classes in question. |
| 431 | 417 |
| 432 | 418 |
| 433 See "`adding a new field to the classic schema`_" for an example that requires | 419 See "`adding a new field to the classic schema`_" for an example that requires |
| 434 database content changes. | 420 database content changes. |
| 421 | |
| 422 | |
| 423 Access Controls | |
| 424 =============== | |
| 425 | |
| 426 A set of Permissions are built in to the security module by default: | |
| 427 | |
| 428 - Edit (everything) | |
| 429 - View (everything) | |
| 430 | |
| 431 The default interfaces define: | |
| 432 | |
| 433 - Web Registration | |
| 434 - Web Access | |
| 435 - Web Roles | |
| 436 - Email Registration | |
| 437 - Email Access | |
| 438 | |
| 439 These are hooked into the default Roles: | |
| 440 | |
| 441 - Admin (Edit everything, View everything, Web Roles) | |
| 442 - User (Web Access, Email Access) | |
| 443 - Anonymous (Web Registration, Email Registration) | |
| 444 | |
| 445 And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user | |
| 446 gets the "Anonymous" assigned when the database is initialised on installation. | |
| 447 The two default schemas then define: | |
| 448 | |
| 449 - Edit issue, View issue (both) | |
| 450 - Edit file, View file (both) | |
| 451 - Edit msg, View msg (both) | |
| 452 - Edit support, View support (extended only) | |
| 453 | |
| 454 and assign those Permissions to the "User" Role. Put together, these settings | |
| 455 appear in the ``open()`` function of the tracker ``dbinit.py`` (the following | |
| 456 is taken from the "minimal" template ``dbinit.py``):: | |
| 457 | |
| 458 # | |
| 459 # SECURITY SETTINGS | |
| 460 # | |
| 461 # new permissions for this schema | |
| 462 for cl in ('user', ): | |
| 463 db.security.addPermission(name="Edit", klass=cl, | |
| 464 description="User is allowed to edit "+cl) | |
| 465 db.security.addPermission(name="View", klass=cl, | |
| 466 description="User is allowed to access "+cl) | |
| 467 | |
| 468 # and give the regular users access to the web and email interface | |
| 469 p = db.security.getPermission('Web Access') | |
| 470 db.security.addPermissionToRole('User', p) | |
| 471 p = db.security.getPermission('Email Access') | |
| 472 db.security.addPermissionToRole('User', p) | |
| 473 | |
| 474 # May users view other user information? Comment these lines out | |
| 475 # if you don't want them to | |
| 476 p = db.security.getPermission('View', 'user') | |
| 477 db.security.addPermissionToRole('User', p) | |
| 478 | |
| 479 # Assign the appropriate permissions to the anonymous user's Anonymous | |
| 480 # Role. Choices here are: | |
| 481 # - Allow anonymous users to register through the web | |
| 482 p = db.security.getPermission('Web Registration') | |
| 483 db.security.addPermissionToRole('Anonymous', p) | |
| 484 # - Allow anonymous (new) users to register through the email gateway | |
| 485 p = db.security.getPermission('Email Registration') | |
| 486 db.security.addPermissionToRole('Anonymous', p) | |
| 487 | |
| 488 | |
| 489 New User Roles | |
| 490 -------------- | |
| 491 | |
| 492 New users are assigned the Roles defined in the config file as: | |
| 493 | |
| 494 - NEW_WEB_USER_ROLES | |
| 495 - NEW_EMAIL_USER_ROLES | |
| 496 | |
| 497 | |
| 498 Changing Access Controls | |
| 499 ------------------------ | |
| 500 | |
| 501 You may alter the configuration variables to change the Role that new web or | |
| 502 email users get, for example to not give them access to the web interface if | |
| 503 they register through email. | |
| 504 | |
| 505 You may use the ``roundup-admin`` "``security``" command to display the | |
| 506 current Role and Permission configuration in your tracker. | |
| 507 | |
| 508 Adding a new Permission | |
| 509 ~~~~~~~~~~~~~~~~~~~~~~~ | |
| 510 | |
| 511 When adding a new Permission, you will need to: | |
| 512 | |
| 513 1. add it to your tracker's dbinit so it is created | |
| 514 2. enable it for the Roles that should have it (verify with | |
| 515 "``roundup-admin security``") | |
| 516 3. add it to the relevant HTML interface templates | |
| 517 4. add it to the appropriate xxxPermission methods on in your tracker | |
| 518 interfaces module | |
| 519 | |
| 520 Example Scenarios | |
| 521 ~~~~~~~~~~~~~~~~~ | |
| 522 | |
| 523 **automatic registration of users in the e-mail gateway** | |
| 524 By giving the "anonymous" user the "Email Registration" Role, any | |
| 525 unidentified user will automatically be registered with the tracker (with | |
| 526 no password, so they won't be able to log in through the web until an admin | |
| 527 sets them a password). Note: this is the default behaviour in the tracker | |
| 528 templates that ship with Roundup. | |
| 529 | |
| 530 **anonymous access through the e-mail gateway** | |
| 531 Give the "anonymous" user the "Email Access" and ("Edit", "issue") Roles | |
| 532 but not giving them the "Email Registration" Role. This means that when an | |
| 533 unknown user sends email into the tracker, they're automatically logged in | |
| 534 as "anonymous". Since they don't have the "Email Registration" Role, they | |
| 535 won't be automatically registered, but since "anonymous" has permission | |
| 536 to use the gateway, they'll still be able to submit issues. Note that the | |
| 537 Sender information - their email address - will not be available - they're | |
| 538 *anonymous*. | |
| 539 | |
| 540 **only developers may be assigned issues** | |
| 541 Create a new Permission called "Fixer" for the "issue" class. Create a new | |
| 542 Role "Developer" which has that Permission, and assign that to the | |
| 543 appropriate users. Filter the list of users available in the assignedto | |
| 544 list to include only those users. Enforce the Permission with an auditor. See | |
| 545 the example `restricting the list of users that are assignable to a task`_. | |
| 546 | |
| 547 **only managers may sign off issues as complete** | |
| 548 Create a new Permission called "Closer" for the "issue" class. Create a new | |
| 549 Role "Manager" which has that Permission, and assign that to the appropriate | |
| 550 users. In your web interface, only display the "resolved" issue state option | |
| 551 when the user has the "Closer" Permissions. Enforce the Permission with | |
| 552 an auditor. This is very similar to the previous example, except that the | |
| 553 web interface check would look like:: | |
| 554 | |
| 555 <option tal:condition="python:request.user.hasPermission('Closer')" | |
| 556 value="resolved">Resolved</option> | |
| 557 | |
| 435 | 558 |
| 436 | 559 |
| 437 Web Interface | 560 Web Interface |
| 438 ============= | 561 ============= |
| 439 | 562 |
| 775 fails, the path expression fails. When we get to the end, the object we're | 898 fails, the path expression fails. When we get to the end, the object we're |
| 776 left with is evaluated to get a string - methods are called, objects are | 899 left with is evaluated to get a string - methods are called, objects are |
| 777 stringified. Path expressions may have an optional ``path:`` prefix, though | 900 stringified. Path expressions may have an optional ``path:`` prefix, though |
| 778 they are the default expression type, so it's not necessary. | 901 they are the default expression type, so it's not necessary. |
| 779 | 902 |
| 780 XXX | components of expressions | 903 If an expression evaluates to ``default`` then the expression is |
| 781 | 904 "cancelled" - whatever HTML already exists in the template will remain |
| 782 XXX "nothing" and "default" | 905 (tag content in the case of tal:content, attributes in the case of |
| 906 tal:attributes). | |
| 907 | |
| 908 If an expression evaluates to ``nothing`` then the target of the expression | |
| 909 is removed (tag content in the case of tal:content, attributes in the case | |
| 910 of tal:attributes and the tag itself in the case of tal:replace). | |
| 911 | |
| 912 If an element in the path may not exist, then you can use the ``|`` | |
| 913 operator in the expression to provide an alternative. So, the expression | |
| 914 ``request/form/foo/value | default`` would simply leave the current HTML | |
| 915 in place if the "foo" form variable doesn't exist. | |
| 783 | 916 |
| 784 **String Expressions** - eg. ``string:hello ${user/name}`` | 917 **String Expressions** - eg. ``string:hello ${user/name}`` |
| 785 These expressions are simple string interpolations (though they can be just | 918 These expressions are simple string interpolations (though they can be just |
| 786 plain strings with no interpolation if you want. The expression in the | 919 plain strings with no interpolation if you want. The expression in the |
| 787 ``${ ... }`` is just a path expression as above. | 920 ``${ ... }`` is just a path expression as above. |
| 1419 *where each journal entry is an HTMLJournalEntry.* | 1552 *where each journal entry is an HTMLJournalEntry.* |
| 1420 | 1553 |
| 1421 Defining new web actions | 1554 Defining new web actions |
| 1422 ------------------------ | 1555 ------------------------ |
| 1423 | 1556 |
| 1424 XXX | 1557 You may define new actions to be triggered by the ``:action`` form variable. |
| 1425 | 1558 These are added to the tracker ``interfaces.py`` as methods on the ``Client`` |
| 1426 | 1559 class. |
| 1427 Access Controls | 1560 |
| 1428 =============== | 1561 Adding action methods takes three steps; first you `define the new action |
| 1429 | 1562 method`_, then you `register the action method`_ with the cgi interface so |
| 1430 A set of Permissions are built in to the security module by default: | 1563 it may be triggered by the ``:action`` form variable. Finally you actually |
| 1431 | 1564 `use the new action`_ in your HTML form. |
| 1432 - Edit (everything) | 1565 |
| 1433 - View (everything) | 1566 See "`setting up a "wizard" (or "druid") for controlled adding of issues`_" |
| 1434 | 1567 for an example. |
| 1435 The default interfaces define: | 1568 |
| 1436 | 1569 Define the new action method |
| 1437 - Web Registration | 1570 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1438 - Web Access | 1571 |
| 1439 - Web Roles | 1572 The action methods have the following interface:: |
| 1440 - Email Registration | 1573 |
| 1441 - Email Access | 1574 def myActionMethod(self): |
| 1442 | 1575 ''' Perform some action. No return value is required. |
| 1443 These are hooked into the default Roles: | 1576 ''' |
| 1444 | 1577 |
| 1445 - Admin (Edit everything, View everything, Web Roles) | 1578 The *self* argument is an instance of your tracker ``instance.Client`` class - |
| 1446 - User (Web Access, Email Access) | 1579 thus it's mostly implemented by ``roundup.cgi.Client``. See the docstring of |
| 1447 - Anonymous (Web Registration, Email Registration) | 1580 that class for details of what it can do. |
| 1448 | 1581 |
| 1449 And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user | 1582 The method will typically check the ``self.form`` variable's contents. It |
| 1450 gets the "Anonymous" assigned when the database is initialised on installation. | 1583 may then: |
| 1451 The two default schemas then define: | 1584 |
| 1452 | 1585 - add information to ``self.ok_message`` or ``self.error_message`` |
| 1453 - Edit issue, View issue (both) | 1586 - change the ``self.template`` variable to alter what the user will see next |
| 1454 - Edit file, View file (both) | 1587 - raise Unauthorised, SendStaticFile, SendFile, NotFound or Redirect |
| 1455 - Edit msg, View msg (both) | 1588 exceptions |
| 1456 - Edit support, View support (extended only) | 1589 |
| 1457 | 1590 |
| 1458 and assign those Permissions to the "User" Role. New users are assigned the | 1591 Register the action method |
| 1459 Roles defined in the config file as: | 1592 ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1460 | 1593 |
| 1461 - NEW_WEB_USER_ROLES | 1594 The method is now written, but isn't available to the user until you add it to |
| 1462 - NEW_EMAIL_USER_ROLES | 1595 the `instance.Client`` class ``actions`` variable, like so:: |
| 1463 | 1596 |
| 1464 You may alter the configuration variables to change the Role that new web or | 1597 actions = client.Class.actions + ( |
| 1465 email users get, for example to not give them access to the web interface if | 1598 ('myaction', 'myActionMethod'), |
| 1466 they register through email. | 1599 ) |
| 1467 | 1600 |
| 1468 You may use the ``roundup-admin`` "``security``" command to display the | 1601 This maps the action name "myaction" to the action method we defined. |
| 1469 current Role and Permission configuration in your tracker. | 1602 |
| 1470 | 1603 |
| 1471 Adding a new Permission | 1604 Use the new action |
| 1472 ----------------------- | 1605 ~~~~~~~~~~~~~~~~~~ |
| 1473 | 1606 |
| 1474 When adding a new Permission, you will need to: | 1607 In your HTML form, add a hidden form element like so:: |
| 1475 | 1608 |
| 1476 1. add it to your tracker's dbinit so it is created | 1609 <input type="hidden" name=":action" value="myaction"> |
| 1477 2. enable it for the Roles that should have it (verify with | 1610 |
| 1478 "``roundup-admin security``") | 1611 where "myaction" is the name you registered in the previous step. |
| 1479 3. add it to the relevant HTML interface templates | |
| 1480 4. add it to the appropriate xxxPermission methods on in your tracker | |
| 1481 interfaces module | |
| 1482 | |
| 1483 Example Scenarios | |
| 1484 ----------------- | |
| 1485 | |
| 1486 **automatic registration of users in the e-mail gateway** | |
| 1487 By giving the "anonymous" user the "Email Registration" Role, any | |
| 1488 unidentified user will automatically be registered with the tracker (with | |
| 1489 no password, so they won't be able to log in through the web until an admin | |
| 1490 sets them a password). Note: this is the default behaviour in the tracker | |
| 1491 templates that ship with Roundup. | |
| 1492 | |
| 1493 **anonymous access through the e-mail gateway** | |
| 1494 Give the "anonymous" user the "Email Access" and ("Edit", "issue") Roles | |
| 1495 but not giving them the "Email Registration" Role. This means that when an | |
| 1496 unknown user sends email into the tracker, they're automatically logged in | |
| 1497 as "anonymous". Since they don't have the "Email Registration" Role, they | |
| 1498 won't be automatically registered, but since "anonymous" has permission | |
| 1499 to use the gateway, they'll still be able to submit issues. Note that the | |
| 1500 Sender information - their email address - will not be available - they're | |
| 1501 *anonymous*. | |
| 1502 | |
| 1503 XXX more examples needed | |
| 1504 | 1612 |
| 1505 | 1613 |
| 1506 Examples | 1614 Examples |
| 1507 ======== | 1615 ======== |
| 1616 | |
| 1617 .. contents:: | |
| 1618 :local: | |
| 1619 :depth: 1 | |
| 1508 | 1620 |
| 1509 Adding a new field to the classic schema | 1621 Adding a new field to the classic schema |
| 1510 ---------------------------------------- | 1622 ---------------------------------------- |
| 1511 | 1623 |
| 1512 This example shows how to add a new constrained property (ie. a selection of | 1624 This example shows how to add a new constrained property (ie. a selection of |
| 2099 usually to validate user choices and determine what page is next. Now | 2211 usually to validate user choices and determine what page is next. Now |
| 2100 encode those actions in methods on the interfaces Client class and insert | 2212 encode those actions in methods on the interfaces Client class and insert |
| 2101 hooks to those actions in the "actions" attribute on that class, like so:: | 2213 hooks to those actions in the "actions" attribute on that class, like so:: |
| 2102 | 2214 |
| 2103 actions = client.Class.actions + ( | 2215 actions = client.Class.actions + ( |
| 2104 ('page1_submit', page1SubmitAction), | 2216 ('page1_submit', 'page1SubmitAction'), |
| 2105 ) | 2217 ) |
| 2106 | 2218 |
| 2107 def page1SubmitAction(self): | 2219 def page1SubmitAction(self): |
| 2108 ''' Verify that the user has selected a category, and then move on | 2220 ''' Verify that the user has selected a category, and then move on |
| 2109 to page 2. | 2221 to page 2. |
| 2155 matches. | 2267 matches. |
| 2156 | 2268 |
| 2157 We also remove the redundant password fields from the ``user.item`` template. | 2269 We also remove the redundant password fields from the ``user.item`` template. |
| 2158 | 2270 |
| 2159 | 2271 |
| 2272 Adding a "vacation" flag to users for stopping nosy messages | |
| 2273 ------------------------------------------------------------ | |
| 2274 | |
| 2275 When users go on vacation and set up vacation email bouncing, you'll start to | |
| 2276 see a lot of messages come back through Roundup "Fred is on vacation". Not | |
| 2277 very useful, and relatively easy to stop. | |
| 2278 | |
| 2279 1. add a "vacation" flag to your users:: | |
| 2280 | |
| 2281 user = Class(db, "user", | |
| 2282 username=String(), password=Password(), | |
| 2283 address=String(), realname=String(), | |
| 2284 phone=String(), organisation=String(), | |
| 2285 alternate_addresses=String(), | |
| 2286 roles=String(), queries=Multilink("query"), | |
| 2287 vacation=Boolean()) | |
| 2288 | |
| 2289 2. edit your detector ``nosyreactor.py`` so that the ``nosyreaction()`` | |
| 2290 consists of:: | |
| 2291 | |
| 2292 def nosyreaction(db, cl, nodeid, oldvalues): | |
| 2293 # send a copy of all new messages to the nosy list | |
| 2294 for msgid in determineNewMessages(cl, nodeid, oldvalues): | |
| 2295 try: | |
| 2296 users = db.user | |
| 2297 messages = db.msg | |
| 2298 | |
| 2299 # figure the recipient ids | |
| 2300 sendto = [] | |
| 2301 r = {} | |
| 2302 recipients = messages.get(msgid, 'recipients') | |
| 2303 for recipid in messages.get(msgid, 'recipients'): | |
| 2304 r[recipid] = 1 | |
| 2305 | |
| 2306 # figure the author's id, and indicate they've received the | |
| 2307 # message | |
| 2308 authid = messages.get(msgid, 'author') | |
| 2309 | |
| 2310 # possibly send the message to the author, as long as they aren't | |
| 2311 # anonymous | |
| 2312 if (db.config.MESSAGES_TO_AUTHOR == 'yes' and | |
| 2313 users.get(authid, 'username') != 'anonymous'): | |
| 2314 sendto.append(authid) | |
| 2315 r[authid] = 1 | |
| 2316 | |
| 2317 # now figure the nosy people who weren't recipients | |
| 2318 nosy = cl.get(nodeid, 'nosy') | |
| 2319 for nosyid in nosy: | |
| 2320 # Don't send nosy mail to the anonymous user (that user | |
| 2321 # shouldn't appear in the nosy list, but just in case they | |
| 2322 # do...) | |
| 2323 if users.get(nosyid, 'username') == 'anonymous': | |
| 2324 continue | |
| 2325 # make sure they haven't seen the message already | |
| 2326 if not r.has_key(nosyid): | |
| 2327 # send it to them | |
| 2328 sendto.append(nosyid) | |
| 2329 recipients.append(nosyid) | |
| 2330 | |
| 2331 # generate a change note | |
| 2332 if oldvalues: | |
| 2333 note = cl.generateChangeNote(nodeid, oldvalues) | |
| 2334 else: | |
| 2335 note = cl.generateCreateNote(nodeid) | |
| 2336 | |
| 2337 # we have new recipients | |
| 2338 if sendto: | |
| 2339 # filter out the people on vacation | |
| 2340 sendto = [i for i in sendto if not users.get(i, 'vacation', 0)] | |
| 2341 | |
| 2342 # map userids to addresses | |
| 2343 sendto = [users.get(i, 'address') for i in sendto] | |
| 2344 | |
| 2345 # update the message's recipients list | |
| 2346 messages.set(msgid, recipients=recipients) | |
| 2347 | |
| 2348 # send the message | |
| 2349 cl.send_message(nodeid, msgid, note, sendto) | |
| 2350 except roundupdb.MessageSendError, message: | |
| 2351 raise roundupdb.DetectorError, message | |
| 2352 | |
| 2353 Note that this is the standard nosy reaction code, with the small addition | |
| 2354 of:: | |
| 2355 | |
| 2356 # filter out the people on vacation | |
| 2357 sendto = [i for i in sendto if not users.get(i, 'vacation', 0)] | |
| 2358 | |
| 2359 which filters out the users that have the vacation flag set to true. | |
| 2360 | |
| 2361 | |
| 2160 ------------------- | 2362 ------------------- |
| 2161 | 2363 |
| 2162 Back to `Table of Contents`_ | 2364 Back to `Table of Contents`_ |
| 2163 | 2365 |
| 2164 .. _`Table of Contents`: index.html | 2366 .. _`Table of Contents`: index.html |
| 2165 | 2367 .. _`design documentation`: design.html |
| 2368 |
