Code

- remember the change note on bad submissions (sf bug 625989)
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 21 Oct 2002 00:42:00 +0000 (00:42 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 21 Oct 2002 00:42:00 +0000 (00:42 +0000)
- highlight required form fields (sf bug 625989)

add a couple of examples to the customisation doc too.
un-reversed the message list so first messages appear first.
fixed the messages header (post introduction of "remove")

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1366 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/customizing.txt
roundup/templates/classic/html/issue.item
roundup/templates/classic/html/style.css

index 849362476844b05badedeff5a4ce0f62000e3110..62653bc7c3f4a64690df7619c99814e1eb62acc4 100644 (file)
@@ -8,6 +8,8 @@ are given with the most recent entry first.
 - added CGI :remove:<propname> and :add:<propname> which specify item ids to
   remove / add in <propname> multilink.
 - bugfix in boolean templating
+- remember the change note on bad submissions (sf bug 625989)
+- highlight required form fields (sf bug 625989)
 
 
 2002-10-16 0.5.1
index d683f2708d8f771a93dfe26759d73f1a1eba82ec..1c410bad03b16c95ef227579a05027f6034c0556 100644 (file)
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.58 $
+:Version: $Revision: 1.59 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -2579,6 +2579,184 @@ able to give a summary of the total time spent on a particular issue.
    example - then you'll need to restart that to pick up the code changes.
    When that's done, you'll be able to use the new time logging interface.
 
+Using a UN*X passwd file as the user database
+---------------------------------------------
+
+On some systems, the primary store of users is the UN*X passwd file. It holds
+information on users such as their username, real name, password and primary
+user group.
+
+Roundup can use this store as its primary source of user information, but it
+needs additional information too - email address(es), roundup Roles, vacation
+flags, roundup hyperdb item ids, etc. Also, "retired" users must still exist
+in the user database, unlike some passwd files in which the users are removed
+when they no longer have access to a system.
+
+To make use of the passwd file, we therefore synchronise between the two user
+stores. We also use the passwd file to validate the user logins, as described
+in the previous example, `using an external password validation source`_. We
+keep the users lists in sync using a fairly simple script that runs once a
+day, or several times an hour if more immediate access is needed. In short, it:
+
+1. parses the passwd file, finding usernames, passwords and real names,
+2. compares that list to the current roundup user list:
+   a. entries no longer in the passwd file are *retired*
+   b. entries with mismatching real names are *updated*
+   a. entries only exist in the passwd file are *created*
+3. send an email to administrators to let them know what's been done.
+
+The retiring and updating are simple operations, requiring only a call to
+``retire()`` or ``set()``. The creation operation requires more information
+though - the user's email address and their roundup Roles. We're going to
+assume that the user's email address is the same as their login name, so we
+just append the domain name to that. The Roles are determined using the
+passwd group identifier - mapping their UN*X group to an appropriate set of
+Roles.
+
+The script to perform all this, broken up into its main components, is as
+follows. Firstly, we import the necessary modules and open the tracker we're
+to work on::
+
+    import sys, os, smtplib
+    from roundup import instance, date
+
+    # open the tracker
+    tracker_home = sys.argv[1]
+    tracker = instance.open(tracker_home)
+
+Next we read in the *passwd* file from the tracker home::
+
+    # read in the users
+    file = os.path.join(tracker_home, 'users.passwd')
+    users = [x.strip().split(':') for x in open(file).readlines()]
+
+Handle special users (those to ignore in the file, and those who don't appear
+in the file)::
+
+    # users to not keep ever, pre-load with the users I know aren't
+    # "real" users
+    ignore = ['ekmmon', 'bfast', 'csrmail']
+
+    # users to keep - pre-load with the roundup-specific users
+    keep = ['comment_pool', 'network_pool', 'admin', 'dev-team', 'cs_pool',
+        'anonymous', 'system_pool', 'automated']
+
+Now we map the UN*X group numbers to the Roles that users should have::
+
+    roles = {
+     '501': 'User,Tech',  # tech
+     '502': 'User',       # finance
+     '503': 'User,CSR',   # customer service reps
+     '504': 'User',       # sales
+     '505': 'User',       # marketing
+    }
+
+Now we do all the work. Note that the body of the script (where we have the
+tracker database open) is wrapped in a ``try`` / ``finally`` clause, so that
+we always close the database cleanly when we're finished. So, we now do all
+the work::
+
+    # open the database
+    db = tracker.open('admin')
+    try:
+        # store away messages to send to the tracker admins
+        msg = []
+
+        # loop over the users list read in from the passwd file
+        for user,passw,uid,gid,real,home,shell in users:
+            if user in ignore:
+                # this user shouldn't appear in our tracker
+                continue
+            keep.append(user)
+            try:
+                # see if the user exists in the tracker
+                uid = db.user.lookup(user)
+
+                # yes, they do - now check the real name for correctness
+                if real != db.user.get(uid, 'realname'):
+                    db.user.set(uid, realname=real)
+                    msg.append('FIX %s - %s'%(user, real))
+            except KeyError:
+                # nope, the user doesn't exist
+                db.user.create(username=user, realname=real,
+                    address='%s@ekit-inc.com'%user, roles=roles[gid])
+                msg.append('ADD %s - %s (%s)'%(user, real, roles[gid]))
+
+        # now check that all the users in the tracker are also in our "keep"
+        # list - retire those who aren't
+        for uid in db.user.list():
+            user = db.user.get(uid, 'username')
+            if user not in keep:
+                db.user.retire(uid)
+                msg.append('RET %s'%user)
+
+        # if we did work, then send email to the tracker admins
+        if msg:
+            # create the email
+            msg = '''Subject: %s user database maintenance
+
+            %s
+            '''%(db.config.TRACKER_NAME, '\n'.join(msg))
+
+            # send the email
+            smtp = smtplib.SMTP(db.config.MAILHOST)
+            addr = db.config.ADMIN_EMAIL
+            smtp.sendmail(addr, addr, msg)
+
+        # now we're done - commit the changes
+        db.commit()
+    finally:
+        # always close the database cleanly
+        db.close()
+
+And that's it!
+
+
+Enabling display of either message summaries or the entire messages
+-------------------------------------------------------------------
+
+This is pretty simple - all we need to do is copy the code from the example
+`displaying entire message contents in the issue display`_ into our template
+alongside the summary display, and then introduce a switch that shows either
+one or the other. We'll use a new form variable, ``:whole_messages`` to
+achieve this::
+
+ <table class="messages" tal:condition="context/messages">
+  <tal:block tal:condition="not:request/form/:whole_messages/value | python:0">
+   <tr><th colspan=3 class="header">Messages</th>
+       <th colspan=2 class="header">
+         <a href="?:whole_messages=yes">show entire messages</a>
+       </th>
+   </tr>
+   <tr tal:repeat="msg context/messages">
+    <td><a tal:attributes="href string:msg${msg/id}"
+           tal:content="string:msg${msg/id}"></a></td>
+    <td tal:content="msg/author">author</td>
+    <td nowrap tal:content="msg/date/pretty">date</td>
+    <td tal:content="msg/summary">summary</td>
+    <td>
+     <a tal:attributes="href string:?:remove:messages=${msg/id}&:action=edit">remove</a>
+    </td>
+   </tr>
+  </tal:block>
+
+  <tal:block tal:condition="request/form/:whole_messages/value | python:0">
+   <tr><th colspan=2 class="header">Messages</th>
+       <th class="header"><a href="?:whole_messages=">show only summaries</a></th>
+   </tr>
+   <tal:block tal:repeat="msg context/messages">
+    <tr>
+     <th tal:content="msg/author">author</th>
+     <th nowrap tal:content="msg/date/pretty">date</th>
+     <th style="text-align: right">
+      (<a tal:attributes="href string:?:remove:messages=${msg/id}&:action=edit">remove</a>)
+     </th>
+    </tr>
+    <tr><td colspan=3 tal:content="msg/content"></td></tr>
+   </tal:block>
+  </tal:block>
+ </table>
+
 
 -------------------
 
index 3215703e28111ef2ecddec2dd7b18527f7b75870..20941856cca0856c2a45ea1761141095af9f96e5 100644 (file)
@@ -18,12 +18,12 @@ You are not allowed to view this page.
 
 <table class="form">
 <tr>
- <th nowrap>Title</th>
+ <th class="required" nowrap>Title</th>
  <td colspan=3 tal:content="structure python:context.title.field(size=60)">title</td>
 </tr>
 
 <tr>
- <th nowrap>Priority</th>
+ <th class="required" nowrap>Priority</th>
  <td tal:content="structure context/priority/menu">priority</td>
  <th nowrap>Status</th>
  <td tal:content="structure context/status/menu">status</td>
@@ -60,7 +60,8 @@ python:db.user.classhelp('username,realname,address,phone')" /><br>
 <tr>
  <th nowrap>Change Note</th>
  <td colspan=3>
-  <textarea name=":note" wrap="hard" rows="5" cols="60"></textarea>
+  <textarea tal:content="request/form/:note/value | default"
+            name=":note" wrap="hard" rows="5" cols="60"></textarea>
  </td>
 </tr>
 
@@ -76,9 +77,16 @@ python:db.user.classhelp('username,realname,address,phone')" /><br>
  </td>
 </tr>
 </table>
-
 </form>
 
+<table class="form" tal:condition="not:context/id">
+<tr>
+ <td>Note:&nbsp;</td>
+ <th class="required">highlighted</th>
+ <td>&nbsp;fields are required.</td>
+</tr>
+</table>
+
 <table class="form" tal:condition="context/is_only_view_ok">
 <tr>
  <th nowrap>Title</th><td colspan=3 tal:content="context/title">title</td>
@@ -114,8 +122,8 @@ python:db.user.classhelp('username,realname,address,phone')" /><br>
  </p>
 
  <table class="messages" tal:condition="context/messages">
-  <tr><th colspan=4 class="header">Messages</th></tr>
-  <tr tal:repeat="msg context/messages/reverse">
+  <tr><th colspan=5 class="header">Messages</th></tr>
+  <tr tal:repeat="msg context/messages">
    <td><a tal:attributes="href string:msg${msg/id}"
           tal:content="string:msg${msg/id}"></a></td>
    <td tal:content="msg/author">author</td>
index 8aad4587e09dbc6b301b9378dc465737b8d44200..fc376d445e6a5a40f37b518c6d8929d1570e574d 100644 (file)
@@ -70,19 +70,22 @@ table.form {
 }
 
 table.form th {
-  font-weight: bold;
   color: #333388;
   text-align: right;
   vertical-align: top;
+  font-weight: normal;
 }
 
 table.form th.header {
   font-weight: bold;
-  color: #333388;
   background-color: #eeeeff;
   text-align: left;
 }
 
+table.form th.required {
+  font-weight: bold;
+}
+
 table.form td {
   color: #333333;
   empty-cells: show;