[open-ils-commits] r621 - in servres/trunk/conifer: . libsystems/sip static templates/item (artunit)

svn at svn.open-ils.org svn at svn.open-ils.org
Mon Aug 17 10:23:09 EDT 2009


Author: artunit
Date: 2009-08-17 10:23:07 -0400 (Mon, 17 Aug 2009)
New Revision: 621

Modified:
   servres/trunk/conifer/libsystems/sip/sipclient.py
   servres/trunk/conifer/settings.py
   servres/trunk/conifer/static/main.css
   servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
Log:
some help text and css changes

Modified: servres/trunk/conifer/libsystems/sip/sipclient.py
===================================================================
--- servres/trunk/conifer/libsystems/sip/sipclient.py	2009-08-17 05:05:04 UTC (rev 620)
+++ servres/trunk/conifer/libsystems/sip/sipclient.py	2009-08-17 14:23:07 UTC (rev 621)
@@ -1,567 +1,569 @@
-# Small portions are borrowed from David Fiander's acstest.py, in the
-# openncip project. David's license is below:
-
-# Copyright (C) 2006-2008  Georgia Public Library Service
-# 
-# Author: David J. Fiander
-# 
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of version 2 of the GNU General Public
-# License as published by the Free Software Foundation.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public
-# License along with this program; if not, write to the Free
-# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-# MA 02111-1307 USA
-
-
-from sipconstants import *
-import socket
-import sys
-from datetime import datetime
-import re
-
-DEBUG = True
-
-# ------------------------------------------------------------
-# helper functions
-
-def split_n(n):
-    """Return a function that splits a string into two parts at index N."""
-    return lambda s: (s[:n], s[n:])
-
-split2 = split_n(2)
-
-
-
-
-# ------------------------------------------------------------
-# Messages
-
-# First we build up a little language for defining SIP messages, so
-# that we can define the protocol in a declarative style.
-
-
-class basefield(object): 
-
-    def encode(self, dct):
-        """Take a dict, and return the wire representation of this field."""
-        raise NotImplementedError, repr(self)
-
-    def decode(self, bytes):
-        """
-        Take a wire representation and return a pair (V,R) where V is
-        the translated value of the current field, and R is the
-        remaining bytes after the field has been read. If this is an
-        optional field, then decode should return None for V, and
-        return the input bytes for R.
-        """
-        raise NotImplementedError, repr(self)
-
-
-class field(basefield):
-
-    def __init__(self, name, code, width=None):
-        self.name = name 
-        self.code = code
-        self.width = None       # don't use this yet.
-
-    def encode(self, dct):
-        return '%s%s|' % (self.code, dct.get(self.name, ''))
-
-    def decode(self, bytes):
-        bcode, rest = split2(bytes)
-        if bcode != self.code:
-            raise 'BadDecode', \
-                'Wrong field! Expected %r (%s) got %r (%s), in %r.' % (
-                    self.code, lookup_constant(self.code),
-                    bcode, lookup_constant(bcode),
-                    bytes)
-        data, rest = rest.split('|', 1)
-        return data, rest
-
-
-class optfield(field):          # an optional field
-
-    def decode(self, bytes):
-        tmp = bytes + '  '
-        bcode, rest = split2(tmp)
-        if bcode == self.code:
-            return field.decode(self, bytes)
-        else:
-            return None, bytes
-
-        
-class charfield(basefield):
-
-    def __init__(self, name, width=None, default=None):
-        self.name = name
-        self.dflt = str(default)
-        self.width = width or len(self.dflt) # give at least one
-        self.pad = ' ' * self.width
-
-        self.decode = split_n(self.width)
-
-    def encode(self, dct):
-        v = dct.get(self.name, self.dflt)
-        assert v is not None
-        return ('%s%s' % (self.pad, v))[-self.width:]
-
-
-class yn(basefield):
-    def __init__(self, name):
-        self.name = name
-
-    def encode(self, dct):
-        return 'NY'[bool(dct.get(self.name))]
-
-    def decode(self, bytes):
-        return (bytes[0] == 'Y'), bytes[1:]
-
-
-class localtime(charfield):
-    def __init__(self, name):
-        self.name = name
-        self.width = 18
-
-    def encode(self, dct):
-        return datetime.now().strftime('%Y%m%d    %H%M%S')
-
-    def decode(self, bytes):
-        return split_n(self.width)(bytes)
-
-RAW = -55
-class raw(basefield):
-    name = 'raw'
-    # for debugging.
-    def decode(self, bytes):
-        return bytes, '\r'
-
-# We define a protocol Message as a list of fields. For now,
-# message(A, B, C) is equivalent to the tuple (A,B,C).
-
-message = lambda *args: args
-
-# Encoding a message on to the wire. Args is a dict of field-values.
-
-def encode_msg(msg, args):
-    out = []
-    add = out.append
-    for thing in msg:
-        if isinstance(thing, basefield):
-            add(thing.encode(args))
-        else:
-            add(str(thing))
-    return ''.join(out)
-
-# Decoding from the wire:
-
-def decode_msg(msg, bytes):
-    out = {}
-    add = out.__setitem__
-    rest = bytes
-    
-    # Proper 'fields' have variable position in the tail of the
-    # message. So we treat them differently.
-    varposn = set([p for p in msg if isinstance(p, field)])
-    varlookup = dict((x.code, x) for x in varposn)
-    fixedposn = [p for p in msg if not p in varposn]
-    
-    for part in fixedposn:
-        if isinstance(part, basefield):
-            good, rest = part.decode(rest)
-            if good is not None:
-                add(part.name, good)
-        else:
-            v = str(part)
-            good, rest = rest[:len(v)], rest[len(v):]
-            assert v == good
-        if DEBUG: print '%s == %r\n==== %r' % (getattr(part, 'name',''), good, rest)
-
-    # Now we take what's left, chunk it, and try to resolve each one
-    # against a variable-position field.
-    segments = re.findall(r'(.*?\|)', rest)
-    
-    if DEBUG: print segments
-
-    for segment in segments:
-        fld = varlookup.get(segment[:2])
-        if fld:
-            good, rest = fld.decode(segment)
-            add(fld.name, good)
-            varposn.remove(fld)
-        else:
-            raise 'FieldNotProcessed: %s, %s' % (segment, lookup_constant(segment[:2]))
-
-    # Let's make sure that any "required" fields were not missing.
-    notpresent = set(f for f in varposn if not isinstance(f, optfield))
-    if notpresent:
-        for f in notpresent:
-            print 'MISSING: %-12s %s %s' % (f.name, f.code, lookup_constant(f.code))
-        raise 'MandatoryFieldsNotPresent'
-
-    return out
-
-# The SIP checksum. Borrowed from djfiander.        
-
-def checksum(msg):
-    return '%04X' % ((0 - sum(map(ord, msg))) & 0xFFFF)
-
-
-#------------------------------------------------------------
-# SIP Message Definitions
-
-# some common fields
-
-
-fld_localtime     = localtime('localtime')
-fld_INST_ID       = field('inst', FID_INST_ID)
-fld_ITEM_ID       = field('item', FID_ITEM_ID)
-fld_PATRON_ID     = field('patron', FID_PATRON_ID)
-ofld_TERMINAL_PWD = optfield('termpwd', FID_TERMINAL_PWD)
-fld_proto_version = charfield('version', default='2.00')
-ofld_print_line    = optfield('print_line', FID_PRINT_LINE)
-ofld_screen_msg    = optfield('screenmsg', FID_SCREEN_MSG)
-
-MESSAGES = {
-    LOGIN : message(
-            LOGIN, 
-            '00',
-            field('uid', FID_LOGIN_UID),
-            field('pwd', FID_LOGIN_PWD),
-            field('locn', FID_LOCATION_CODE)),
-
-    LOGIN_RESP : message(
-            LOGIN_RESP, 
-            charfield('ok', width=1)),
-
-    SC_STATUS : message(
-            SC_STATUS, 
-            charfield('online', default='1'),
-            charfield('width', default='040'),
-            fld_proto_version),
-
-    ACS_STATUS : message(
-            ACS_STATUS,
-            yn('online'),
-            yn('checkin_OK'),
-            yn('checkout_OK'),
-            yn('renewal_OK'),
-            yn('status_update_OK'),
-            yn('offline_OK'),
-            charfield('timeout', default='01'),
-            charfield('retries', default='9999'),
-            fld_localtime,
-            charfield('protocol', default='2.00'),
-            fld_INST_ID,
-            optfield('patron_id', FID_PATRON_ID),
-            optfield('item_id', FID_ITEM_ID),
-            optfield('terminal_pwd', FID_TERMINAL_PWD),
-            
-            optfield('instname', FID_LIBRARY_NAME),
-            field('supported', FID_SUPPORTED_MSGS),
-            optfield('ttylocn', FID_TERMINAL_LOCN),
-            ofld_screen_msg,
-            ofld_print_line),
-    PATRON_INFO : message(
-            PATRON_INFO,
-            charfield('lang', width=3, default=1),
-            fld_localtime,
-            charfield('holditemsreq', default='Y         '),
-            fld_INST_ID,
-            fld_PATRON_ID,
-            ofld_TERMINAL_PWD,
-            optfield('patronpwd', FID_PATRON_PWD),
-            optfield('startitem', FID_START_ITEM, width=5),
-            optfield('enditem', FID_END_ITEM, width=5)),
-            
-    PATRON_INFO_RESP : message(
-            PATRON_INFO_RESP,
-            charfield('hmmm', width=14),
-            charfield('lang', width=3, default=1),
-            fld_localtime,
-            charfield('onhold', width=4),
-            charfield('overdue', width=4),
-            charfield('charged', width=4),
-            charfield('fine', width=4),
-            charfield('recall', width=4),
-            charfield('unavail_holds', width=4),
-            fld_INST_ID,
-            ofld_screen_msg,
-            ofld_print_line,
-            optfield('instname', FID_LIBRARY_NAME),
-            fld_PATRON_ID,
-            field('personal', FID_PERSONAL_NAME),
-
-            optfield('hold_limit', FID_HOLD_ITEMS_LMT, width=4),
-            optfield('overdue_limit', FID_OVERDUE_ITEMS_LMT, width=4),
-            optfield('charged_limit', FID_OVERDUE_ITEMS_LMT, width=4),
-
-            optfield('hold_items', FID_HOLD_ITEMS),
-            optfield('valid_patron_pwd', FID_VALID_PATRON_PWD),
-            
-            optfield('valid_patron', FID_VALID_PATRON),
-            optfield('currency', FID_CURRENCY),
-            optfield('fee_amt', FID_FEE_AMT),
-            optfield('fee_limit', FID_FEE_LMT),
-            optfield('home_addr', FID_HOME_ADDR),
-            optfield('email', FID_EMAIL),
-            optfield('home_phone', FID_HOME_PHONE),
-            optfield('patron_birthdate', FID_PATRON_BIRTHDATE),
-            optfield('patron_class', FID_PATRON_CLASS),
-            optfield('inet_profile', FID_INET_PROFILE),
-            optfield('home_library', FID_HOME_LIBRARY)),
-
-    END_PATRON_SESSION : message(
-            END_PATRON_SESSION,
-            fld_localtime,
-            field('inst', FID_INST_ID),
-            field('patron', FID_PATRON_ID)),
-
-    END_SESSION_RESP : message(
-            END_SESSION_RESP,
-            yn('session_ended'),
-            fld_localtime,
-            fld_INST_ID,
-            fld_PATRON_ID,
-            ofld_print_line,
-            ofld_screen_msg),
-
-    CHECKOUT: message(
-        CHECKOUT,
-        yn('renewals_OK'),
-        yn('no_block'),
-        fld_localtime,
-        fld_localtime,
-        field('inst', FID_INST_ID),
-        field('patron', FID_PATRON_ID),
-        field('item', FID_ITEM_ID),
-        ),
-
-    CHECKOUT_RESP: message(
-        CHECKOUT_RESP,
-        charfield('ok', width=1),
-        yn('is_renewal'),
-        yn('is_magnetic'),
-        yn('desensitize'),
-        fld_localtime,
-        field('inst', FID_INST_ID),
-        field('patron', FID_PATRON_ID),
-        field('item', FID_ITEM_ID),
-        field('due', FID_DUE_DATE),
-        field('title', FID_TITLE_ID),
-        optfield('media_type_code', FID_MEDIA_TYPE),
-        optfield('is_valid_patron', FID_VALID_PATRON),
-        ofld_print_line,
-        ofld_screen_msg),
-
-    CHECKIN: message(
-        CHECKIN,
-        yn('is_retry'),
-        fld_localtime,
-        fld_localtime,
-        field('item', FID_ITEM_ID),
-        field('location', FID_CURRENT_LOCN),
-        field('inst', FID_INST_ID),
-        ofld_TERMINAL_PWD,
-        ),
-
-    CHECKIN_RESP: message(
-        CHECKIN_RESP,
-        charfield('ok', width=1),
-        yn('resensitize'),
-        yn('is_magnetic'),
-        yn('alert'),
-        fld_localtime,
-        fld_INST_ID,
-        optfield('patron', FID_PATRON_ID),
-        field('item', FID_ITEM_ID),
-        field('title', FID_TITLE_ID),
-        optfield('media_type_code', FID_MEDIA_TYPE),
-        optfield('perm_locn', FID_PERM_LOCN),
-        optfield('due', FID_DUE_DATE),
-        ofld_print_line,
-        ofld_screen_msg,
-        ),
-#         yn('is_retry'),
-#         fld_localtime,
-#         fld_localtime,
-#         field('item', FID_ITEM_ID),
-#         field('location', FID_CURRENT_LOCN),
-#         ofld_TERMINAL_PWD,
-#        ),
-
-    ITEM_INFORMATION : message(
-            ITEM_INFORMATION,
-            fld_localtime,
-            fld_INST_ID,
-            fld_ITEM_ID,
-            ofld_TERMINAL_PWD),
-
-    ITEM_INFO_RESP : message(
-            ITEM_INFO_RESP,
-            charfield('circstat', width=2),
-            charfield('security', width=2),
-            charfield('feetype', width=2),
-            fld_localtime,
-            fld_ITEM_ID,
-            field('title', FID_TITLE_ID),
-            optfield('mediatype', FID_MEDIA_TYPE),
-            optfield('perm_locn', FID_PERM_LOCN),
-            optfield('current_locn', FID_CURRENT_LOCN),
-            optfield('item_props', FID_ITEM_PROPS),
-            optfield('currency', FID_CURRENCY),
-            optfield('fee', FID_FEE_AMT),
-            optfield('owner', FID_OWNER),
-            optfield('hold_queue_len', FID_HOLD_QUEUE_LEN),
-            optfield('due_date', FID_DUE_DATE),
-
-            optfield('recall_date', FID_RECALL_DATE),
-            optfield('hold_pickup_date', FID_HOLD_PICKUP_DATE),
-            ofld_screen_msg,
-            ofld_print_line),
-            
-    RAW : message(raw()),
-}
-
-
-class SipClient(object):
-    def __init__(self, host, port, error_detect=False):
-        self.hostport = (host, port)
-        self.error_detect = error_detect
-        self.connect()
-
-    def connect(self):
-        so = socket.socket()
-        so.connect(self.hostport)
-        self.socket = so
-        self.seqno = self.error_detect and 1 or 0
-
-    def close(self):
-        # fixme, do SIP close first.
-        self.socket.close()
-
-    def send(self, outmsg, inmsg, args=None):
-        msg_template = MESSAGES[outmsg]
-        resp_template = MESSAGES[inmsg]
-        msg = encode_msg(msg_template, args or {})
-        if self.error_detect:
-            # add the checksum
-            msg += 'AY%dAZ' % (self.seqno % 10)
-            self.seqno += 1
-            msg += checksum(msg)
-        msg += '\r'
-        if DEBUG: print '>>> %r' % msg
-        self.socket.send(msg)
-        resp = self.socket.recv(1000)
-        if DEBUG: print '<<< %r' % resp
-        return decode_msg(resp_template, resp)
-        
-
-    # --------------------------------------------------
-    # Common protocol methods
-
-    def login(self, uid, pwd, locn):
-        msg = self.send(LOGIN, LOGIN_RESP, 
-                        dict(uid=uid, pwd=pwd, locn=locn))
-        return msg.get('ok') == '1'
-
-    def status(self):
-        return self.send(SC_STATUS, ACS_STATUS)
-
-    def patron_info(self, barcode):
-        msg = self.send(PATRON_INFO,PATRON_INFO_RESP,
-                        {'patron':barcode,
-                         'startitem':1, 'enditem':2})
-        # fixme, this may not be the best test of okayness
-        msg['success'] = msg.get('valid_patron') == 'Y'
-        return msg
-
-    def checkout(self, patron, item, inst=''):
-        msg = self.send(CHECKOUT, CHECKOUT_RESP,
-                        {'patron':patron,
-                         'inst': inst,
-                         'item':item})
-        msg['media_type'] = MEDIA_TYPE_TABLE.get(msg.get('media_type_code'))
-        msg['success'] = msg.get('ok') == '1'
-        return msg
-
-    def checkin(self, item, institution='', location=''):
-        msg = self.send(CHECKIN, CHECKIN_RESP,
-                        {'inst': institution,
-                         'location':location,
-                         'is_retry':False,
-                         'item':item})
-        msg['success'] = msg.get('ok') == '1'
-        return msg
-
-    def item_info(self, barcode):
-        msg = self.send(ITEM_INFORMATION, ITEM_INFO_RESP,
-                        {'item':barcode})
-        msg['available'] = msg['circstat'] == '03'
-        msg['status'] = ITEM_STATUS_TABLE[msg['circstat']]
-        return msg
-
-
-# ------------------------------------------------------------
-# Django stuff. Optional.
-
-try:
-    from django.conf import settings
-    def sip_connection():
-        sip = SipClient(*settings.SIP_HOST)
-        if not sip.login(*settings.SIP_CREDENTIALS):
-            raise 'SipLoginError'
-        return sip
-
-    # decorator
-    def SIP(fn):
-        def f(*args, **kwargs):
-            conn = sip_connection()
-            resp = fn(conn, *args, **kwargs)
-            conn.close()
-            return resp
-        return f
-
-except ImportError:
-    pass
-
-
-# ------------------------------------------------------------
-# Test code.
-
-if __name__ == '__main__':
-    from pprint import pprint
-
-    sip = SipClient('home', 6001)
-    resp = sip.login(uid='scclient',
-                     pwd='clientpwd', locn='The basement')
-    pprint(resp)
-    pprint(sip.status())
-
-    pprint(sip.send(PATRON_INFO, PATRON_INFO_RESP,
-                   {'patron':'scclient',
-                    'startitem':1, 'enditem':2}))
-
-    # these are items from openncip's test database.
-    item_ids = ['1565921879', '0440242746', '660']
-    bad_ids = ['xx' + i for i in item_ids]
-    for item in (item_ids + bad_ids):
-        result = sip.send(ITEM_INFORMATION, ITEM_INFO_RESP,
-                          {'item':item})
-        print '%-12s: %s' % (item, result['title'] or '????')
-        print sip.send(CHECKOUT, RAW,
-                       {'patron':'scclient-2',
-                        'inst': 'UWOLS',
-                        'item':item})
-        print '\n' * 5
-    pprint(sip.send(END_PATRON_SESSION, END_SESSION_RESP,
-                   {'patron':'scclient',
-                    'inst':'UWOLS'}))
-
-
+# Small portions are borrowed from David Fiander's acstest.py, in the
+# openncip project. David's license is below:
+
+# Copyright (C) 2006-2008  Georgia Public Library Service
+# 
+# Author: David J. Fiander
+# 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free
+# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+
+
+from sipconstants import *
+import socket
+import sys
+from datetime import datetime
+import re
+
+DEBUG = True
+
+# ------------------------------------------------------------
+# helper functions
+
+def split_n(n):
+    """Return a function that splits a string into two parts at index N."""
+    return lambda s: (s[:n], s[n:])
+
+split2 = split_n(2)
+
+
+
+
+# ------------------------------------------------------------
+# Messages
+
+# First we build up a little language for defining SIP messages, so
+# that we can define the protocol in a declarative style.
+
+
+class basefield(object): 
+
+    def encode(self, dct):
+        """Take a dict, and return the wire representation of this field."""
+        raise NotImplementedError, repr(self)
+
+    def decode(self, bytes):
+        """
+        Take a wire representation and return a pair (V,R) where V is
+        the translated value of the current field, and R is the
+        remaining bytes after the field has been read. If this is an
+        optional field, then decode should return None for V, and
+        return the input bytes for R.
+        """
+        raise NotImplementedError, repr(self)
+
+
+class field(basefield):
+
+    def __init__(self, name, code, width=None):
+        self.name = name 
+        self.code = code
+        self.width = None       # don't use this yet.
+
+    def encode(self, dct):
+        return '%s%s|' % (self.code, dct.get(self.name, ''))
+
+    def decode(self, bytes):
+        bcode, rest = split2(bytes)
+        if bcode != self.code:
+            raise 'BadDecode', \
+                'Wrong field! Expected %r (%s) got %r (%s), in %r.' % (
+                    self.code, lookup_constant(self.code),
+                    bcode, lookup_constant(bcode),
+                    bytes)
+        data, rest = rest.split('|', 1)
+        return data, rest
+
+
+class optfield(field):          # an optional field
+
+    def decode(self, bytes):
+        tmp = bytes + '  '
+        bcode, rest = split2(tmp)
+        if bcode == self.code:
+            return field.decode(self, bytes)
+        else:
+            return None, bytes
+
+        
+class charfield(basefield):
+
+    def __init__(self, name, width=None, default=None):
+        self.name = name
+        self.dflt = str(default)
+        self.width = width or len(self.dflt) # give at least one
+        self.pad = ' ' * self.width
+
+        self.decode = split_n(self.width)
+
+    def encode(self, dct):
+        v = dct.get(self.name, self.dflt)
+        assert v is not None
+        return ('%s%s' % (self.pad, v))[-self.width:]
+
+
+class yn(basefield):
+    def __init__(self, name):
+        self.name = name
+
+    def encode(self, dct):
+        return 'NY'[bool(dct.get(self.name))]
+
+    def decode(self, bytes):
+        return (bytes[0] == 'Y'), bytes[1:]
+
+
+class localtime(charfield):
+    def __init__(self, name):
+        self.name = name
+        self.width = 18
+
+    def encode(self, dct):
+        return datetime.now().strftime('%Y%m%d    %H%M%S')
+
+    def decode(self, bytes):
+        return split_n(self.width)(bytes)
+
+RAW = -55
+class raw(basefield):
+    name = 'raw'
+    # for debugging.
+    def decode(self, bytes):
+        return bytes, '\r'
+
+# We define a protocol Message as a list of fields. For now,
+# message(A, B, C) is equivalent to the tuple (A,B,C).
+
+message = lambda *args: args
+
+# Encoding a message on to the wire. Args is a dict of field-values.
+
+def encode_msg(msg, args):
+    out = []
+    add = out.append
+    for thing in msg:
+        if isinstance(thing, basefield):
+            add(thing.encode(args))
+        else:
+            add(str(thing))
+    return ''.join(out)
+
+# Decoding from the wire:
+
+def decode_msg(msg, bytes):
+    out = {}
+    add = out.__setitem__
+    rest = bytes
+    
+    # Proper 'fields' have variable position in the tail of the
+    # message. So we treat them differently.
+    varposn = set([p for p in msg if isinstance(p, field)])
+    varlookup = dict((x.code, x) for x in varposn)
+    fixedposn = [p for p in msg if not p in varposn]
+    
+    for part in fixedposn:
+        if isinstance(part, basefield):
+            good, rest = part.decode(rest)
+            if good is not None:
+                add(part.name, good)
+        else:
+            v = str(part)
+            good, rest = rest[:len(v)], rest[len(v):]
+            assert v == good
+        if DEBUG: print '%s == %r\n==== %r' % (getattr(part, 'name',''), good, rest)
+
+    # Now we take what's left, chunk it, and try to resolve each one
+    # against a variable-position field.
+    segments = re.findall(r'(.*?\|)', rest)
+    
+    if DEBUG: print segments
+
+    for segment in segments:
+        fld = varlookup.get(segment[:2])
+        if fld:
+            good, rest = fld.decode(segment)
+            add(fld.name, good)
+            varposn.remove(fld)
+        else:
+            raise 'FieldNotProcessed: %s, %s' % (segment, lookup_constant(segment[:2]))
+
+    # Let's make sure that any "required" fields were not missing.
+    notpresent = set(f for f in varposn if not isinstance(f, optfield))
+    if notpresent:
+        for f in notpresent:
+            print 'MISSING: %-12s %s %s' % (f.name, f.code, lookup_constant(f.code))
+        raise 'MandatoryFieldsNotPresent'
+
+    return out
+
+# The SIP checksum. Borrowed from djfiander.        
+
+def checksum(msg):
+    return '%04X' % ((0 - sum(map(ord, msg))) & 0xFFFF)
+
+
+#------------------------------------------------------------
+# SIP Message Definitions
+
+# some common fields
+
+
+fld_localtime     = localtime('localtime')
+fld_INST_ID       = field('inst', FID_INST_ID)
+fld_ITEM_ID       = field('item', FID_ITEM_ID)
+fld_PATRON_ID     = field('patron', FID_PATRON_ID)
+ofld_TERMINAL_PWD = optfield('termpwd', FID_TERMINAL_PWD)
+fld_proto_version = charfield('version', default='2.00')
+ofld_print_line    = optfield('print_line', FID_PRINT_LINE)
+ofld_screen_msg    = optfield('screenmsg', FID_SCREEN_MSG)
+
+MESSAGES = {
+    LOGIN : message(
+            LOGIN, 
+            '00',
+            field('uid', FID_LOGIN_UID),
+            field('pwd', FID_LOGIN_PWD),
+            field('locn', FID_LOCATION_CODE)),
+
+    LOGIN_RESP : message(
+            LOGIN_RESP, 
+            charfield('ok', width=1)),
+
+    SC_STATUS : message(
+            SC_STATUS, 
+            charfield('online', default='1'),
+            charfield('width', default='040'),
+            fld_proto_version),
+
+    ACS_STATUS : message(
+            ACS_STATUS,
+            yn('online'),
+            yn('checkin_OK'),
+            yn('checkout_OK'),
+            yn('renewal_OK'),
+            yn('status_update_OK'),
+            yn('offline_OK'),
+            charfield('timeout', default='01'),
+            charfield('retries', default='9999'),
+            fld_localtime,
+            charfield('protocol', default='2.00'),
+            fld_INST_ID,
+            optfield('patron_id', FID_PATRON_ID),
+            optfield('item_id', FID_ITEM_ID),
+            optfield('terminal_pwd', FID_TERMINAL_PWD),
+            
+            optfield('instname', FID_LIBRARY_NAME),
+            field('supported', FID_SUPPORTED_MSGS),
+            optfield('ttylocn', FID_TERMINAL_LOCN),
+            ofld_screen_msg,
+            ofld_print_line),
+    PATRON_INFO : message(
+            PATRON_INFO,
+            charfield('lang', width=3, default=1),
+            fld_localtime,
+            charfield('holditemsreq', default='Y         '),
+            fld_INST_ID,
+            fld_PATRON_ID,
+            ofld_TERMINAL_PWD,
+            optfield('patronpwd', FID_PATRON_PWD),
+            optfield('startitem', FID_START_ITEM, width=5),
+            optfield('enditem', FID_END_ITEM, width=5)),
+            
+    PATRON_INFO_RESP : message(
+            PATRON_INFO_RESP,
+            charfield('hmmm', width=14),
+            charfield('lang', width=3, default=1),
+            fld_localtime,
+            charfield('onhold', width=4),
+            charfield('overdue', width=4),
+            charfield('charged', width=4),
+            charfield('fine', width=4),
+            charfield('recall', width=4),
+            charfield('unavail_holds', width=4),
+            fld_INST_ID,
+            ofld_screen_msg,
+            ofld_print_line,
+            optfield('instname', FID_LIBRARY_NAME),
+            fld_PATRON_ID,
+            field('personal', FID_PERSONAL_NAME),
+
+            optfield('hold_limit', FID_HOLD_ITEMS_LMT, width=4),
+            optfield('overdue_limit', FID_OVERDUE_ITEMS_LMT, width=4),
+            optfield('charged_limit', FID_OVERDUE_ITEMS_LMT, width=4),
+
+            optfield('hold_items', FID_HOLD_ITEMS),
+            optfield('valid_patron_pwd', FID_VALID_PATRON_PWD),
+            
+            optfield('valid_patron', FID_VALID_PATRON),
+            optfield('currency', FID_CURRENCY),
+            optfield('fee_amt', FID_FEE_AMT),
+            optfield('fee_limit', FID_FEE_LMT),
+            optfield('home_addr', FID_HOME_ADDR),
+            optfield('email', FID_EMAIL),
+            optfield('home_phone', FID_HOME_PHONE),
+            optfield('patron_birthdate', FID_PATRON_BIRTHDATE),
+            optfield('patron_class', FID_PATRON_CLASS),
+            optfield('inet_profile', FID_INET_PROFILE),
+            optfield('home_library', FID_HOME_LIBRARY)),
+
+    END_PATRON_SESSION : message(
+            END_PATRON_SESSION,
+            fld_localtime,
+            field('inst', FID_INST_ID),
+            field('patron', FID_PATRON_ID)),
+
+    END_SESSION_RESP : message(
+            END_SESSION_RESP,
+            yn('session_ended'),
+            fld_localtime,
+            fld_INST_ID,
+            fld_PATRON_ID,
+            ofld_print_line,
+            ofld_screen_msg),
+
+    CHECKOUT: message(
+        CHECKOUT,
+        yn('renewals_OK'),
+        yn('no_block'),
+        fld_localtime,
+        fld_localtime,
+        field('inst', FID_INST_ID),
+        field('patron', FID_PATRON_ID),
+        field('item', FID_ITEM_ID),
+        ),
+
+    CHECKOUT_RESP: message(
+        CHECKOUT_RESP,
+        charfield('ok', width=1),
+        yn('is_renewal'),
+        yn('is_magnetic'),
+        yn('desensitize'),
+        fld_localtime,
+        field('inst', FID_INST_ID),
+        field('patron', FID_PATRON_ID),
+        field('item', FID_ITEM_ID),
+        field('due', FID_DUE_DATE),
+        field('title', FID_TITLE_ID),
+        optfield('media_type_code', FID_MEDIA_TYPE),
+        optfield('is_valid_patron', FID_VALID_PATRON),
+        ofld_print_line,
+        ofld_screen_msg),
+
+    CHECKIN: message(
+        CHECKIN,
+        yn('is_retry'),
+        fld_localtime,
+        fld_localtime,
+        field('item', FID_ITEM_ID),
+        field('location', FID_CURRENT_LOCN),
+        field('inst', FID_INST_ID),
+        ofld_TERMINAL_PWD,
+        ),
+
+    CHECKIN_RESP: message(
+        CHECKIN_RESP,
+        charfield('ok', width=1),
+        yn('resensitize'),
+        yn('is_magnetic'),
+        yn('alert'),
+        fld_localtime,
+        fld_INST_ID,
+        optfield('patron', FID_PATRON_ID),
+        field('item', FID_ITEM_ID),
+        field('title', FID_TITLE_ID),
+        optfield('media_type_code', FID_MEDIA_TYPE),
+        optfield('perm_locn', FID_PERM_LOCN),
+        optfield('due', FID_DUE_DATE),
+        ofld_print_line,
+        ofld_screen_msg,
+        ),
+#         yn('is_retry'),
+#         fld_localtime,
+#         fld_localtime,
+#         field('item', FID_ITEM_ID),
+#         field('location', FID_CURRENT_LOCN),
+#         ofld_TERMINAL_PWD,
+#        ),
+
+    ITEM_INFORMATION : message(
+            ITEM_INFORMATION,
+            fld_localtime,
+            fld_INST_ID,
+            fld_ITEM_ID,
+            ofld_TERMINAL_PWD),
+
+    ITEM_INFO_RESP : message(
+            ITEM_INFO_RESP,
+            charfield('circstat', width=2),
+            charfield('security', width=2),
+            charfield('feetype', width=2),
+            fld_localtime,
+            fld_ITEM_ID,
+            field('title', FID_TITLE_ID),
+            optfield('mediatype', FID_MEDIA_TYPE),
+            optfield('perm_locn', FID_PERM_LOCN),
+            optfield('current_locn', FID_CURRENT_LOCN),
+            optfield('item_props', FID_ITEM_PROPS),
+            optfield('currency', FID_CURRENCY),
+            optfield('fee', FID_FEE_AMT),
+            optfield('owner', FID_OWNER),
+            optfield('hold_queue_len', FID_HOLD_QUEUE_LEN),
+            optfield('due_date', FID_DUE_DATE),
+
+            optfield('recall_date', FID_RECALL_DATE),
+            optfield('hold_pickup_date', FID_HOLD_PICKUP_DATE),
+            ofld_screen_msg,
+            ofld_print_line),
+            
+    RAW : message(raw()),
+}
+
+
+class SipClient(object):
+    def __init__(self, host, port, error_detect=False):
+        self.hostport = (host, port)
+        self.error_detect = error_detect
+        self.connect()
+
+    def connect(self):
+        so = socket.socket()
+        so.connect(self.hostport)
+        self.socket = so
+        self.seqno = self.error_detect and 1 or 0
+
+    def close(self):
+        # fixme, do SIP close first.
+        self.socket.close()
+
+    def send(self, outmsg, inmsg, args=None):
+        msg_template = MESSAGES[outmsg]
+        resp_template = MESSAGES[inmsg]
+        msg = encode_msg(msg_template, args or {})
+        if self.error_detect:
+            # add the checksum
+            msg += 'AY%dAZ' % (self.seqno % 10)
+            self.seqno += 1
+            msg += checksum(msg)
+        msg += '\r'
+        if DEBUG: print '>>> %r' % msg
+        self.socket.send(msg)
+        resp = self.socket.recv(1000)
+        if DEBUG: print '<<< %r' % resp
+        return decode_msg(resp_template, resp)
+        
+
+    # --------------------------------------------------
+    # Common protocol methods
+
+    def login(self, uid, pwd, locn):
+        msg = self.send(LOGIN, LOGIN_RESP, 
+                        dict(uid=uid, pwd=pwd, locn=locn))
+        return msg.get('ok') == '1'
+
+    def status(self):
+        return self.send(SC_STATUS, ACS_STATUS)
+
+    def patron_info(self, barcode):
+        msg = self.send(PATRON_INFO,PATRON_INFO_RESP,
+                        {'patron':barcode,
+                         'startitem':1, 'enditem':2})
+        # fixme, this may not be the best test of okayness
+        msg['success'] = msg.get('valid_patron') == 'Y'
+        return msg
+
+    def checkout(self, patron, item, inst=''):
+        msg = self.send(CHECKOUT, CHECKOUT_RESP,
+                        {'patron':patron,
+                         'inst': inst,
+                         'item':item})
+        msg['media_type'] = MEDIA_TYPE_TABLE.get(msg.get('media_type_code'))
+        msg['success'] = msg.get('ok') == '1'
+        return msg
+
+    def checkin(self, item, institution='', location=''):
+        msg = self.send(CHECKIN, CHECKIN_RESP,
+                        {'inst': institution,
+                         'location':location,
+                         'is_retry':False,
+                         'item':item})
+        msg['success'] = msg.get('ok') == '1'
+        return msg
+
+    def item_info(self, barcode):
+        print("starting")
+        msg = self.send(ITEM_INFORMATION, ITEM_INFO_RESP,
+                        {'item':barcode})
+        print(msg['circstat'])
+        msg['available'] = msg['circstat'] == '03'
+        msg['status'] = ITEM_STATUS_TABLE[msg['circstat']]
+        return msg
+
+
+# ------------------------------------------------------------
+# Django stuff. Optional.
+
+try:
+    from django.conf import settings
+    def sip_connection():
+        sip = SipClient(*settings.SIP_HOST)
+        if not sip.login(*settings.SIP_CREDENTIALS):
+            raise 'SipLoginError'
+        return sip
+
+    # decorator
+    def SIP(fn):
+        def f(*args, **kwargs):
+            conn = sip_connection()
+            resp = fn(conn, *args, **kwargs)
+            conn.close()
+            return resp
+        return f
+
+except ImportError:
+    pass
+
+
+# ------------------------------------------------------------
+# Test code.
+
+if __name__ == '__main__':
+    from pprint import pprint
+
+    sip = SipClient('comet.cs.uoguelph.ca', 8080)
+    resp = sip.login(uid='test',
+                     pwd='test', locn='test')
+    pprint(resp)
+    pprint(sip.status())
+
+    pprint(sip.send(PATRON_INFO, PATRON_INFO_RESP,
+                   {'patron':'scclient',
+                    'startitem':1, 'enditem':2}))
+
+    # these are items from openncip's test database.
+    item_ids = ['1565921879', '0440242746', '660']
+    bad_ids = ['xx' + i for i in item_ids]
+    for item in (item_ids + bad_ids):
+        result = sip.send(ITEM_INFORMATION, ITEM_INFO_RESP,
+                          {'item':item})
+        print '%-12s: %s' % (item, result['title'] or '????')
+        print sip.send(CHECKOUT, RAW,
+                       {'patron':'scclient-2',
+                        'inst': 'UWOLS',
+                        'item':item})
+        print '\n' * 5
+    pprint(sip.send(END_PATRON_SESSION, END_SESSION_RESP,
+                   {'patron':'scclient',
+                    'inst':'UWOLS'}))
+
+

Modified: servres/trunk/conifer/settings.py
===================================================================
--- servres/trunk/conifer/settings.py	2009-08-17 05:05:04 UTC (rev 620)
+++ servres/trunk/conifer/settings.py	2009-08-17 14:23:07 UTC (rev 621)
@@ -106,13 +106,13 @@
 
 EVERGREEN_GATEWAY_SERVER = 'www.concat.ca'
 Z3950_CONFIG = ('zed.concat.ca', 210, 'OWA')  #OWA,OSUL,CONIFER
-SIP_HOST = ('comet.cs.uoguelph.ca', 8080)
+SIP_HOST = ('localhost', 8080)
 
 try:
     from private_local_settings import SIP_CREDENTIALS
 except:
     # stuff that I really ought not check into svn...
-    #SIP_CREDENTIALS = ('userid', 'password', 'location')
+    SIP_CREDENTIALS = ('test', 'test', 'test')
     pass
 
 

Modified: servres/trunk/conifer/static/main.css
===================================================================
--- servres/trunk/conifer/static/main.css	2009-08-17 05:05:04 UTC (rev 620)
+++ servres/trunk/conifer/static/main.css	2009-08-17 14:23:07 UTC (rev 621)
@@ -1,335 +1,342 @@
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, font, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td {
-	margin: 0;
-	padding: 0;
-	border: 0;
-	outline: 0;
-	font-weight: inherit;
-	font-style: inherit;
-	font-size: 100%;
-	font-family: inherit;
-	vertical-align: baseline;
-}
-/* remember to define focus styles! */
-:focus {
-	outline: 0;
-}
-body {
-	line-height: 1;
-	color: black;
-	background: white;
-}
-ol, ul {
-	list-style: none;
-}
-/* tables still need 'cellspacing="0"' in the markup */
-table {
-	border-collapse: separate;
-	border-spacing: 0;
-}
-caption, th, td {
-	text-align: left;
-	font-weight: normal;
-}
-blockquote:before, blockquote:after,
-q:before, q:after {
-	content: "";
-}
-blockquote, q {
-	quotes: "" "";
-}
-
-/* General look and feel */
-
-body * {  font-family: Verdana, sans-serif; }
-pre, code { font-family: monospace; }
-
-body, html { margin: 0; padding: 0; }
-
-body {  
-    background-color: #005; 
-    font-size: normal; 
-}
-
-div#outer {
-    background-color: white; 
-    width: 960px; margin-bottom: 50px;
-    margin-left: auto; margin-right: auto;
-}
-#mainpanel {  background-color: white; padding: 0 12px 24px 12px; 
-min-height: 300px; 
-}
-
-/* General headers and footers */
-
-#header, #footer {
-    color: white; padding: 8px 4px 12px 8px;
-    background-color: #448;
-}
-
-#header div#search {
-    float: right;
-    margin: 0;
-    color: #ccc;
-}
-
-#header #welcome {
-    margin-top: 4px;
-}
-
-#brandheader { background-color: white; padding: 8px; }
-
-#header a { color: #fff; font-weight: bold;  padding: 10px 12px 10px 12px; }
-#header a.loginbutton { background-color: #a44; }
-#header a:hover { background-color: #fb7; color: black; text-decoration: none; }
-
-tbody td, tbody th { vertical-align: top; }
-
-#footer {  
-    margin: 12px;
-    padding-bottom: 12px;
-    background-color: #ddf; 
-    color: black;
-    border-bottom: white 10px solid;
-}
-
-/* heading sizes and colours. */
-
-h1 { font-size: 150%; font-weight: bold; }
-h2 { font-size: 125%; font-weight: bold; }
-h3 { font-size: 120%; font-weight: bold; }
-p { margin: 24px 0px; }
-h1 { color: navy; margin: 36px 0 18px 0; }
-h2 { color: #336; margin: 24px 0 12px 0; }
-h3, h4 { color: darkgreen; }
-h1 a, h2 a { color: navy; }
-
-a { color: blue; text-decoration: none; }
-a:hover {  text-decoration: underline;  }
-
-/* error panel on the person-edit form */
-
-.errors {  margin: 1em; padding: 1em; background-color: #fdd; }
-.errors h2 { font-size: 120%; }
-
-/* actions (e.g. "edit user" link) */
-.action a { font-weight: bold; }
-
-#tabbar { margin: 18px 0; padding: 0; clear: both; }
-#tabbar li { display: inline; }
-#tabbar li a { padding: 15px 18px 5px 18px; background-color: #ddf; color: black; text-decoration: none; }
-#tabbar li a:hover { background-color: #fc8; }
-
-/* 
-#tabbar li.active a { background-color: #fa6; font-weight: bold; }
-*/
-
-.pagination_controls {
-    text-align: center; margin: 12px 0;
-}
-
-.pagination_controls .nums {
-    padding: 0 12px; 
-}
-
-.pagetable td { border: #ddd 1px solid; padding: 8px; }
-.pagetable .odd {
-    background-color: #F8F8F8;
-}
-.pagetable thead th { font-size: smaller; text-align: left; padding: 2px 8px; }
-
-
-/* nested titles: like breadcrumbs when drilling into an itemtree */
-
-.nestedtitle h2 { margin-top: 8px; }
-.nestedtitle a { color: navy; }
-
-span.final_item { font-weight: bold; font-size: 110%; }
-
-/* item trees (tree of headings and items in a course */
-
-#sidepanel { width: 183px; float: right; text-align: right;}
-#sidepanel div { margin: 6px 0; }
-
-#treepanel { width: 740px; }
-
-.itemtree { 
-    margin-left: 20px;
-    padding-left: 20px;
-    list-style-type: none; 
-}
-
-.itemtree .itemtree { margin-left: 30px; padding-left: 0; }
-
-.itemtree li { padding-left: 0; margin-left: 0; } 
-
-.itemtree li { margin: 12px 8px; }
-.itemtree li .mainline { padding-left: 8px; }
-
-.itemtree .metalink { padding-left: 8px; color: gray; }
-.itemtree .metalink a {
-    color: gray; 
-}
-
-.itemtree .editlinks   { padding-left: 12px; color: gray; 
-			 font-size: small;
-		     }
-.itemtree .editlinks a { color: navy; }
-
-.itemadd { 
-    margin-top: 30px; font-size: 90%;
-    padding: 10px; 
-    background-color: #eef; 
-    clear: both;
-}
-.itemadd li {     
-    margin: 10px; 
-}
-
-.itemadd a { color: navy; }
-
-/* specialized display of items in tree, by type */
-
-.itemtree li.item_HEADING { 
-    list-style-image: url(tango/folder.png);
-}
-.itemtree li.item_HEADING > a { 
-    color: navy; 
-}
-
-li.item_HEADING .headingmainline {
-    margin-bottom: 12px;
-}
-
-li.item_HEADING .headingmainline  a.mainlink {
-    border-bottom: #aaa 1px solid; 
-}
-
-li.item_HEADING  .headingmainline a.mainlink:hover {
-    border-bottom: none;
-}
-
-.itemtree li.item_ELEC { 
-    list-style-image: url(tango/document.png);
-}
-
-.itemtree li.item_URL { 
-    list-style-image: url(tango/applications-internet.png);
-}
-
-.itemtree li.item_PHYS { 
-    /* fixme: need a better icon */
-    list-style-image: url(tango/x-office-address-book.png);
-}
-
-
-.instructors {
-  border: 1px solid #ccc;
-  float: left;
-  width: 50%;
-  padding: 2px 2px 2px 2px;
-  font-size: 1em;
-  line-height: 1em;
-  text-align: left;
-  margin-right: 5px;
-}
-
-.topbox {
-  border: 1px solid #ccc;
-  width: 50%;
-  line-height: 1em;
-  text-align: left;
-}
-
-table.topheading { width: 100%; }
-.topheading th {
-    background-color: #ddf;
-}
-
-.topheading th, .topheading td {
-padding: 8px;
-}
-
-p.todo, div.todo { background-color: #fdd; padding: 6px; margin: 12px; border-left: #d99 6px solid; }
-
-.newsitem p { margin: 12px 0; }
-.newsitem ul { list-style: circle; margin-left: 30px; }
-.newsitem ul li { margin: 8px; }
-
-.newsitem { 
-    max-width: 600px;
-    line-height: 125%;
-}
-.newsitem .newsdate { 
-    margin: 4px 0 8px 0; text-align: right; 
-    font-size: 80%; color: navy;
-}
-
-.menublockopener { margin-left: 0.25em; color: #bbb !important; font-weight: normal !important; }
-.menublock { background-color: #f2e4cc; font-size: 95%; padding: 1px 4px; }
-
-#coursebanner { background-color: #f2e4cc; margin: -12px -12px 12px -12px; padding: 8px; }
-#coursesearch { float: right; }
-#coursebanner h1 { margin: 12px 0; font-size: 125%; }
-
-#edit_course_link { margin: 8px 0 8px 0; font-size: 95%; }
-
-
-#breadcrumbs { margin: 8px 0px 16px 0; width: 716px;  
-	     text-indent: -24px; padding-left: 24px; }
-
-.errorlist { float: right; }
-.errorlist li { color: red; font-size: 90%; }
-
-
-/* a nice table-style for forms. */
-.formtable tbody th { 
-    padding: 0 8px 16px 0;
-    width: 200px; 
-    text-align: left; 
-    font-size: 90%;
-    font-weight: normal; 
-}
-
-thead th { padding: 8px; font-weight: bold; font-size: 90%; }
-.metadata_table tbody th,
-.metadata_table tbody td {
-    padding: 8px; border: #ddd 1px solid;
-}
-.metadata_table input { width: 600px; }
-.metadata_table .meta3 input { width: 10px; }
-
-.metadata_table tbody th {
-    background-color: #eee;
-}
-   
-.metadata_table a.bigdownload { padding: 8px 58px; font-weight: bold; font-size: 105%; }
-.metadata_table a.bigdownload:hover { background-color: #dfd; color: black; }
-
-h2.metadata_subhead {font-size: 105%; padding: 0; margin: 18px 0 9px 0;}
-
-.metadata_table tbody th {
-    text-align: left; width: 120px;
-}
-.gap { height: 24px; }
-.metadata_table td { max-width: 800px; overflow: hidden; }
-
-/* panels that appear when specific OPTIONs or radio-buttons are selected. */
-.specific { padding: 8px; margin: 0 16px; background-color: #eef; }
-
-
-li.sort_item { margin-top: 20px !important;
-	     border: gray 1px dotted; width: 400px; }
-
-li.sort_item:hover { background-color: #eee; }
-
-ul.heading_tree li  { list-style: none; }
-ul.heading_tree { margin: 0; padding-left: 0; }
-ul.heading_tree ul { margin: 0; padding-left: 25px; }
-
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	outline: 0;
+	font-weight: inherit;
+	font-style: inherit;
+	font-size: 100%;
+	font-family: inherit;
+	vertical-align: baseline;
+}
+/* remember to define focus styles! */
+:focus {
+	outline: 0;
+}
+body {
+	line-height: 1;
+	color: black;
+	background: white;
+}
+ol, ul {
+	list-style: none;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+	border-collapse: separate;
+	border-spacing: 0;
+}
+caption, th, td {
+	text-align: left;
+	font-weight: normal;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: "";
+}
+blockquote, q {
+	quotes: "" "";
+}
+
+/* General look and feel */
+
+body * {  font-family: Verdana, sans-serif; }
+pre, code { font-family: monospace; }
+
+body, html { margin: 0; padding: 0; }
+
+body {  
+    background-color: #005; 
+    font-size: normal; 
+}
+
+div#outer {
+    background-color: white; 
+    width: 960px; margin-bottom: 50px;
+    margin-left: auto; margin-right: auto;
+}
+#mainpanel {  background-color: white; padding: 0 12px 24px 12px; 
+min-height: 300px; 
+}
+
+/* General headers and footers */
+
+#header, #footer {
+    color: white; padding: 8px 4px 12px 8px;
+    background-color: #448;
+}
+
+#header div#search {
+    float: right;
+    margin: 0;
+    color: #ccc;
+}
+
+#header #welcome {
+    margin-top: 4px;
+}
+
+#brandheader { background-color: white; padding: 8px; }
+
+#header a { color: #fff; font-weight: bold;  padding: 10px 12px 10px 12px; }
+#header a.loginbutton { background-color: #a44; }
+#header a:hover { background-color: #fb7; color: black; text-decoration: none; }
+
+tbody td, tbody th { vertical-align: top; }
+
+#footer {  
+    margin: 12px;
+    padding-bottom: 12px;
+    background-color: #ddf; 
+    color: black;
+    border-bottom: white 10px solid;
+}
+
+/* heading sizes and colours. */
+
+h1 { font-size: 150%; font-weight: bold; }
+h2 { font-size: 125%; font-weight: bold; }
+h3 { font-size: 120%; font-weight: bold; }
+p { margin: 24px 0px; }
+h1 { color: navy; margin: 36px 0 18px 0; }
+h2 { color: #336; margin: 24px 0 12px 0; }
+h3, h4 { color: darkgreen; }
+h1 a, h2 a { color: navy; }
+
+a { color: blue; text-decoration: none; }
+a:hover {  text-decoration: underline;  }
+
+/* error panel on the person-edit form */
+
+.errors {  margin: 1em; padding: 1em; background-color: #fdd; }
+.errors h2 { font-size: 120%; }
+
+/* actions (e.g. "edit user" link) */
+.action a { font-weight: bold; }
+
+#tabbar { margin: 18px 0; padding: 0; clear: both; }
+#tabbar li { display: inline; }
+#tabbar li a { padding: 15px 18px 5px 18px; background-color: #ddf; color: black; text-decoration: none; }
+#tabbar li a:hover { background-color: #fc8; }
+
+/* 
+#tabbar li.active a { background-color: #fa6; font-weight: bold; }
+*/
+
+.pagination_controls {
+    text-align: center; margin: 12px 0;
+}
+
+.pagination_controls .nums {
+    padding: 0 12px; 
+}
+
+.pagetable td { border: #ddd 1px solid; padding: 8px; }
+.pagetable .odd {
+    background-color: #F8F8F8;
+}
+.pagetable thead th { font-size: smaller; text-align: left; padding: 2px 8px; }
+
+
+/* nested titles: like breadcrumbs when drilling into an itemtree */
+
+.nestedtitle h2 { margin-top: 8px; }
+.nestedtitle a { color: navy; }
+
+span.final_item { font-weight: bold; font-size: 110%; }
+
+/* item trees (tree of headings and items in a course */
+
+#sidepanel { width: 183px; float: right; text-align: right;}
+#sidepanel div { margin: 6px 0; }
+
+#treepanel { width: 740px; }
+
+.helptext { 
+    margin-top: 30px; font-size: 90%;
+    padding: 10px; 
+    background-color: #eef; 
+    clear: both;
+}
+
+.itemtree { 
+    margin-left: 20px;
+    padding-left: 20px;
+    list-style-type: none; 
+}
+
+.itemtree .itemtree { margin-left: 30px; padding-left: 0; }
+
+.itemtree li { padding-left: 0; margin-left: 0; } 
+
+.itemtree li { margin: 12px 8px; }
+.itemtree li .mainline { padding-left: 8px; }
+
+.itemtree .metalink { padding-left: 8px; color: gray; }
+.itemtree .metalink a {
+    color: gray; 
+}
+
+.itemtree .editlinks   { padding-left: 12px; color: gray; 
+			 font-size: small;
+		     }
+.itemtree .editlinks a { color: navy; }
+
+.itemadd { 
+    margin-top: 30px; font-size: 90%;
+    padding: 10px; 
+    background-color: #eef; 
+    clear: both;
+}
+.itemadd li {     
+    margin: 10px; 
+}
+
+.itemadd a { color: navy; }
+
+/* specialized display of items in tree, by type */
+
+.itemtree li.item_HEADING { 
+    list-style-image: url(tango/folder.png);
+}
+.itemtree li.item_HEADING > a { 
+    color: navy; 
+}
+
+li.item_HEADING .headingmainline {
+    margin-bottom: 12px;
+}
+
+li.item_HEADING .headingmainline  a.mainlink {
+    border-bottom: #aaa 1px solid; 
+}
+
+li.item_HEADING  .headingmainline a.mainlink:hover {
+    border-bottom: none;
+}
+
+.itemtree li.item_ELEC { 
+    list-style-image: url(tango/document.png);
+}
+
+.itemtree li.item_URL { 
+    list-style-image: url(tango/applications-internet.png);
+}
+
+.itemtree li.item_PHYS { 
+    /* fixme: need a better icon */
+    list-style-image: url(tango/x-office-address-book.png);
+}
+
+
+.instructors {
+  border: 1px solid #ccc;
+  float: left;
+  width: 50%;
+  padding: 2px 2px 2px 2px;
+  font-size: 1em;
+  line-height: 1em;
+  text-align: left;
+  margin-right: 5px;
+}
+
+.topbox {
+  border: 1px solid #ccc;
+  width: 50%;
+  line-height: 1em;
+  text-align: left;
+}
+
+table.topheading { width: 100%; }
+.topheading th {
+    background-color: #ddf;
+}
+
+.topheading th, .topheading td {
+padding: 8px;
+}
+
+p.todo, div.todo { background-color: #fdd; padding: 6px; margin: 12px; border-left: #d99 6px solid; }
+
+.newsitem p { margin: 12px 0; }
+.newsitem ul { list-style: circle; margin-left: 30px; }
+.newsitem ul li { margin: 8px; }
+
+.newsitem { 
+    max-width: 600px;
+    line-height: 125%;
+}
+.newsitem .newsdate { 
+    margin: 4px 0 8px 0; text-align: right; 
+    font-size: 80%; color: navy;
+}
+
+.menublockopener { margin-left: 0.25em; color: #bbb !important; font-weight: normal !important; }
+.menublock { background-color: #f2e4cc; font-size: 95%; padding: 1px 4px; }
+
+#coursebanner { background-color: #f2e4cc; margin: -12px -12px 12px -12px; padding: 8px; }
+#coursesearch { float: right; }
+#coursebanner h1 { margin: 12px 0; font-size: 125%; }
+
+#edit_course_link { margin: 8px 0 8px 0; font-size: 95%; }
+
+
+#breadcrumbs { margin: 8px 0px 16px 0; width: 716px;  
+	     text-indent: -24px; padding-left: 24px; }
+
+.errorlist { float: right; }
+.errorlist li { color: red; font-size: 90%; }
+
+
+/* a nice table-style for forms. */
+.formtable tbody th { 
+    padding: 0 8px 16px 0;
+    width: 200px; 
+    text-align: left; 
+    font-size: 90%;
+    font-weight: normal; 
+}
+
+thead th { padding: 8px; font-weight: bold; font-size: 90%; }
+.metadata_table tbody th,
+.metadata_table tbody td {
+    padding: 8px; border: #ddd 1px solid;
+}
+.metadata_table input { width: 600px; }
+.metadata_table .meta3 input { width: 10px; }
+
+.metadata_table tbody th {
+    background-color: #eee;
+}
+   
+.metadata_table a.bigdownload { padding: 8px 58px; font-weight: bold; font-size: 105%; }
+.metadata_table a.bigdownload:hover { background-color: #dfd; color: black; }
+
+h2.metadata_subhead {font-size: 105%; padding: 0; margin: 18px 0 9px 0;}
+
+.metadata_table tbody th {
+    text-align: left; width: 120px;
+}
+.gap { height: 24px; }
+.metadata_table td { max-width: 800px; overflow: hidden; }
+
+/* panels that appear when specific OPTIONs or radio-buttons are selected. */
+.specific { padding: 8px; margin: 0 16px; background-color: #eef; }
+
+
+li.sort_item { margin-top: 20px !important;
+	     border: gray 1px dotted; width: 400px; }
+
+li.sort_item:hover { background-color: #eee; }
+
+ul.heading_tree li  { list-style: none; }
+ul.heading_tree { margin: 0; padding-left: 0; }
+ul.heading_tree ul { margin: 0; padding-left: 25px; }
+

Modified: servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_add_cat_search.xhtml	2009-08-17 05:05:04 UTC (rev 620)
+++ servres/trunk/conifer/templates/item/item_add_cat_search.xhtml	2009-08-17 14:23:07 UTC (rev 621)
@@ -2,6 +2,7 @@
 from django.utils.simplejson import dumps
 from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc as to_dublin
 title = _('Add physical or electronic item, by catalogue search')
+helptext = _('Use keywords or CCL syntax for searching, for example: ti="detroit river" and au="wilgus"')
 dc_keys = ['dc:title', 'dc:creator', 'dc:publisher', 'dc:date']
 ?>
 <html xmlns="http://www.w3.org/1999/xhtml"
@@ -24,6 +25,10 @@
     ${course_banner(course)}
     ${nested_title(parent_item)}
     <h2>${title}</h2>
+    <div class="helptext">
+    ${helptext}
+    </div>
+
     <form method="GET" action=".">
       <input type="text" id="query" name="query" value="${query}" 
 	     style="font-size: larger; width: 600px;"/>



More information about the open-ils-commits mailing list