[open-ils-commits] r918 - in servres/trunk/conifer: custom integration libsystems/evergreen libsystems/z3950 static syrup syrup/views templates/components templates/item (gfawcett)

svn at svn.open-ils.org svn at svn.open-ils.org
Wed Jul 14 20:55:27 EDT 2010


Author: gfawcett
Date: 2010-07-14 20:55:26 -0400 (Wed, 14 Jul 2010)
New Revision: 918

Added:
   servres/trunk/conifer/syrup/integration.py
Removed:
   servres/trunk/conifer/custom/lib_integration.py
   servres/trunk/conifer/integration/COURSE_CODES.txt
   servres/trunk/conifer/integration/COURSE_SECTIONS.txt
   servres/trunk/conifer/integration/campus-interface.md
   servres/trunk/conifer/integration/default.py
   servres/trunk/conifer/integration/example.py
Modified:
   servres/trunk/conifer/integration/_hooksystem.py
   servres/trunk/conifer/integration/hooks.py
   servres/trunk/conifer/libsystems/evergreen/item_status.py
   servres/trunk/conifer/libsystems/z3950/marcxml.py
   servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
   servres/trunk/conifer/static/main.css
   servres/trunk/conifer/syrup/models.py
   servres/trunk/conifer/syrup/views/__init__.py
   servres/trunk/conifer/syrup/views/_common.py
   servres/trunk/conifer/syrup/views/items.py
   servres/trunk/conifer/templates/components/site.xhtml
   servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
Log:
moving to new integration system; dummy item_status integration.

Deleted: servres/trunk/conifer/custom/lib_integration.py
===================================================================
--- servres/trunk/conifer/custom/lib_integration.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/custom/lib_integration.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,98 +0,0 @@
-# Our integration-point with back-end library systems.
-
-# This is a work in progress. I'm trying to separate out the actual
-# protocol handlers (in libsystems) from the configuration decicions
-# (in settings.py), and use this as sort of a merge-point between
-# those two decisions. 
-
-# TODO: write some documentation about the lib_integration interface.
-
-# Our example configuration: 
-# Z39.50 for catalogue search, 
-# SIP for patron and item_info, and for item checkout and checkin,
-# OpenSRF for extended item info.
-
-# define a @caching decorator to exploit the Django cache. Fixme, move
-# this somewhere else.
-from django.core.cache import cache
-import cPickle 
-def caching(prefix, timeout=60):
-    def g(func):
-        def f(*args):
-            # wtf! Django encodes string-values as
-            # unicode-strings. That's bad, like stupid-bad! I'm
-            # putting explicit utf8-conversions here to make debugging
-            # easier if this code dies.
-            key = ','.join([prefix] + map(str, args))
-            v = cache.get(key)
-            if v:
-                return cPickle.loads(v.encode('utf-8'))
-            else:
-                v = func(*args)
-                if v:
-                    cache.set(key, unicode(cPickle.dumps(v), 'utf-8'), timeout)
-                    return v
-        return f
-    return g
-
-
-from django.conf import settings
-
-from conifer.libsystems.evergreen.support import initialize
-EG_BASE = 'http://%s/' % settings.EVERGREEN_GATEWAY_SERVER
-try:
-    initialize(EG_BASE)
-except:
-    import warnings
-    warnings.warn('Evergreen inaccessible! Integration will suck eggs!')
-
-from conifer.libsystems.evergreen import item_status as I
-from conifer.libsystems.sip.sipclient import SIP
-#from conifer.libsystems.z3950 import yaz_search
-from conifer.libsystems.z3950 import pyz3950_search
-from conifer.libsystems.z3950.marcxml import marcxml_to_records
-
-
- at caching('patroninfo', timeout=300)
- at SIP
-def patron_info(conn, barcode):
-    return conn.patron_info(barcode)
-
- at caching('itemstatus', timeout=300)
- at SIP
-def item_status(conn, barcode):
-    return conn.item_info(barcode)
-
- at SIP
-def checkout(conn, patron_barcode, item_barcode):
-    return conn.checkout(patron_barcode, item_barcode, '')
-
- at SIP
-def checkin(conn, item_barcode):
-    return conn.checkin(item_barcode, institution='', location='')
-
-
- at caching('bcbi', timeout=3600)
-def barcode_to_bib_id(barcode):
-    return I.barcode_to_bib_id(barcode)
-
- at caching('bccp', timeout=3600)
-def barcode_to_copy(barcode):
-    return I.barcode_to_copy(barcode)
-
- at caching('bimx', timeout=3600)
-def bib_id_to_marcxml(bib_id):
-    return I.bib_id_to_marcxml(bib_id)
-
-
-def cat_search(query, start=1, limit=10):
-    # this is a total hack for conifer. If the query is a Conifer
-    # title-detail URL, then return just that one item.
-    if query.startswith(EG_BASE):
-        results = marcxml_to_records(I.url_to_marcxml(query))
-        numhits = len(results)
-    else:
-        cat_host, cat_port, cat_db = settings.Z3950_CONFIG
-        results, numhits = pyz3950_search.search(cat_host, cat_port, cat_db, query, start, limit)
-        #results, numhits = yaz_search.search(cat_host, cat_db, query, start, limit)
-    return results, numhits

Deleted: servres/trunk/conifer/integration/COURSE_CODES.txt
===================================================================
--- servres/trunk/conifer/integration/COURSE_CODES.txt	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/COURSE_CODES.txt	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,140 +0,0 @@
-# Validation and lookup of course codes.
-
-# This modules specifies an "course-code interface" and a null
-# implementation of that interface. If your local system has rules for
-# valid course codes, and a mechanism for looking up details of these
-# codes, you can implement the interface according to your local
-# rules.
-
-
-# ------------------------------------------------------------
-# Overview and definitions
-
-# A course code identifies a specific course offering. Course codes
-# map 1:N onto formal course titles: by looking up a code, we can
-# derive a formal title (in theory, though it may not be possible for
-# external reasons).
-
-# A course code is insufficient to specify a class list: we need a
-# course section for that. A section ties a course code and term to an
-# instructor(s) and a list of students.
-
-# Course codes may have cross-listings, i.e., other codes which refer
-# to the same course, but which appear under a different department
-# for various academic purposes. In our system, we make no attempt to
-# subordinate cross-listings to a "primary" course code.
-
-
-#------------------------------------------------------------
-# Notes on the interface
-#
-# The `course_code_is_valid` function will be used ONLY if
-# course_code_list() returns None (it is a null implementation). If a
-# course-list is available, the system will use a membership test for
-# course-code validity.
-#
-# `course_code_lookup_title` will be used ONLY if `course_code_list`
-# is implemented.
-#
-#
-# "types" of the interface members
-#
-# course_code_is_valid       (string) --> boolean.
-# course_code_example        : a string constant.
-# course_code_list           () --> list of strings
-# course_code_lookup_title   (string) --> string, or None.
-# course_code_cross_listings (string) --> list of strings
-#
-# For each member, you MUST provide either a valid implementation, or
-# set the member to None. See the null implementation below.
-
-#------------------------------------------------------------
-# Implementations
-
-# ------------------------------------------------------------ 
-# Here is a 'null implementation' of the course-code interface. No
-# validation is done, nor are lookups.
-#
-#    course_code_is_valid       = None  # anything is OK;
-#    course_code_example        = None  # no examples;
-#    course_code_lookup_title   = None  # no codes to list;
-#    course_code_cross_listings = None  # no cross lists.
-
-# ------------------------------------------------------------
-# This one specifies a valid course-code format using a regular
-# expression, and offers some example codes, but does not have a
-# lookup system.
-#
-#    import re
-#
-#    def course_code_is_valid(course_code):
-#        pattern = re.compile(r'^\d{2}-\d{3}$')
-#        return bool(pattern.match(course_code))
-#
-#    course_code_example        = '55-203; 99-105'
-#
-#    course_code_list           = None
-#    course_code_lookup_title   = None
-#    course_code_cross_listings = None
-
-
-
-# ------------------------------------------------------------
-# This is a complete implementation, based on a hard-coded list of
-# course codes and titles, and two cross-listed course codes.
-#
-#    _codes = [('ENG100', 'Introduction to English'),
-#              ('ART108', 'English: An Introduction'),
-#              ('FRE238', 'Modern French Literature'),
-#              ('WEB203', 'Advanced Web Design'),]
-#
-#    _crosslists = set(['ENG100', 'ART108'])
-#
-#    course_code_is_valid = None
-#    course_code_example = 'ENG100; FRE238'
-#
-#    def course_code_list():
-#        return [a for (a,b) in _codes]
-#
-#    def course_code_lookup_title(course_code):
-#        return dict(_codes).get(course_code)
-#
-#    def course_code_cross_listings(course_code):
-#        if course_code in _crosslists:
-#            return list(_crosslists - set([course_code]))
-
-
-# ------------------------------------------------------------
-# Provide your own implementation below.
-
-
-#_codes = [('ENG100', 'Introduction to English'),
-#          ('ART108', 'English: An Introduction'),
-#          ('FRE238', 'Modern French Literature'),
-#          ('LIB201', 'Intro to Library Science'),
-#          ('WEB203', 'Advanced Web Design'),]
-
-_codes = [('ART99-100', 'Art History'),
-          ('BIOL55-350', 'Molecular Cell Biology'),
-          ('CRIM48-567', 'Current Issues in Criminology'),
-          ('ENGL26-280', 'Contemporary Literary Theory'),
-          ('ENGL26-420', 'Word and Image: The Contemporary Graphic Novel'),
-          ('SOCWK47-457', 'Advanced Social Work Research'),]
-
-_crosslists = set(['ENGL26-280', 'ENGL26-420'])
-
-
-course_code_is_valid = None
-
-course_code_example = 'BIOL55-350; SOCWK47-457'
-
-def course_code_list():
-    return [a for (a,b) in _codes]
-
-def course_code_lookup_title(course_code):
-    return dict(_codes).get(course_code)
-
-def course_code_cross_listings(course_code):
-    if course_code in _crosslists:
-        return list(_crosslists - set([course_code]))
-

Deleted: servres/trunk/conifer/integration/COURSE_SECTIONS.txt
===================================================================
--- servres/trunk/conifer/integration/COURSE_SECTIONS.txt	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/COURSE_SECTIONS.txt	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,153 +0,0 @@
-# Operations on course-section identifiers
-
-# A course section is an instance of a course offered in a term. 
-
-# A section is specified by a 'section-id', a 3-tuple (course-code,
-# term, section-code), where section-code is usually a short
-# identifier (e.g., "1" representing "section 1 in this term"). Note
-# that multiple sections of the same course are possible in a given
-# term.
-
-# Within the reserves system, a course-site can be associated with
-# zero or more sections, granting access to students in those
-# sections. We need two representations of a section-id.
-
-# The section_tuple_delimiter must be a string which will never appear
-# in a course-code, term, or section-code in your database. It may be
-# a nonprintable character (e.g. NUL or CR). It is used to delimit
-# parts of the tuples in a course's database record.
-
-#------------------------------------------------------------
-# Notes on the interface
-#
-# 'sections_taught_by(username)' returns a set of sections for which
-# username is an instructor. It is acceptable if 'sections_taught_by'
-# only returns current and future sections: historical information is
-# not required by the reserves system.
-#
-# It is expected that the reserves system will be able to resolve any
-# usernames into user records. If there are students on a section-list
-# which do not resolve into user accounts, they will probably be
-# ignored and will not get access to their course sites. So if you're
-# updating your users and sections in a batch-run, you might want to
-# update your users first.
-#
-#------------------------------------------------------------
-# Implementations
-
-# The reserves system will work with a null-implementation of the
-# course-section interface, but tasks related to course-sections will
-# be unavailable. 
-
-# ------------------------------------------------------------ 
-# The null implementation:
-#
-#    sections_tuple_delimiter   = None
-#    sections_taught_by         = None
-#    students_in                = None
-#    instructors_in             = None
-#    sections_for_code_and_term = None
-
-# ------------------------------------------------------------ 
-#
-# The minimal non-null implementation. At the least you must provide
-# sections_tuple_delimiter and students_in. Lookups for instructors
-# may be skipped. Note that sections passed to students_in are
-# (term, course-code, section-code) tuples (string, string, string).
-#
-#    sections_tuple_delimiter   = '|'
-#
-#    def students_in(*sections):
-#        ...
-#        return set_of_usernames
-#
-#    instructors_in             = None
-#    sections_for_code_and_term = None
-
-# ------------------------------------------------------------
-# A complete implementation, with a static database.
-
-#    sections_tuple_delimiter = '|'
-#    
-#    _db = [
-#        ('fred', ('2009W', 'ENG203', '1'), 'jim joe jack ellen ed'),
-#        ('fred', ('2009W', 'ENG327', '1'), 'ed paul bill'),
-#        ('bill', ('2009S', 'BIO323', '1'), 'alan june jack'),
-#        ('bill', ('2009S', 'BIO323', '2'), 'emmet'),
-#    ]
-#    
-#    def sections_taught_by(username):
-#        return set([s[1] for s in _db if s[0] == username])
-#    
-#    def students_in(*sections):
-#        def inner():
-#            for instr, sec, studs in _db:
-#                if sec in sections:
-#                    for s in studs.split(' '):
-#                        yield s
-#        return set(inner())
-#    
-#    def instructors_in(*sections):
-#        def inner():
-#            for instr, sec, studs in _db:
-#                if sec in sections:
-#                    yield instr
-#        return set(inner())
-#    
-#    def sections_for_code_and_term(code, term):
-#        return [(t, c, s) for (instr, (t, c, s), ss) in _db \
-#                    if c == code and t == term]
-#
-
-
-# ------------------------------------------------------------
-# Provide your own implementation below.
-
-sections_tuple_delimiter   = None
-sections_taught_by         = None
-students_in                = None
-instructors_in             = None
-sections_for_code_and_term = None
-
-
-
-# ------------------------------------------------------------
-# a temporary implementation, while I write up the UI.
-
-sections_tuple_delimiter = '|'
-
-# For any of the students to actually appear in a course site, they
-# must also exist as Django users (or be in an authentication backend
-# that supports 'maybe_initialize_user'; see auth_evergreen.py).
-
-_db = [
-    #(instructor, (term, code, sec-code), 'student1 student2 ... studentN'),
-    ('fred', ('2009W', 'ENG203', '1'), 'jim joe jack ellen ed'),
-    ('fred', ('2009W', 'ENG327', '1'), 'ed paul bill'),
-    ('art',  ('2009W', 'LIB201', '1'), 'graham bill ed'),
-    ('graham', ('2009S', 'ART108', '1'), 'alan june jack'),
-    ('graham', ('2009S', 'ART108', '2'), 'emmet'),
-    ('graham', ('2009S', 'ART108', '3'), 'freda hugo bill'),
-]
-
-def sections_taught_by(username):
-    return set([s[1] for s in _db if s[0] == username])
-
-def students_in(*sections):
-    def inner():
-        for instr, sec, studs in _db:
-            if sec in sections:
-                for s in studs.split(' '):
-                    yield s
-    return set(inner())
-
-def instructors_in(*sections):
-    def inner():
-        for instr, sec, studs in _db:
-            if sec in sections:
-                yield instr
-    return set(inner())
-
-def sections_for_code_and_term(code, term):
-    return [(t, c, s) for (instr, (t, c, s), ss) in _db \
-                if c == code and t == term]

Modified: servres/trunk/conifer/integration/_hooksystem.py
===================================================================
--- servres/trunk/conifer/integration/_hooksystem.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/_hooksystem.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,36 +1,21 @@
 # TODO: decide whether or not to use this!
 
 import warnings
+import conifer.syrup.integration as HOOKS
 
-__all__ = ['hook', 'callhook', 'callhook_required', 'gethook']
+__all__ = ['callhook', 'callhook_required', 'gethook']
 
-__HOOKS = {}
-
-def __register_hook(name, func):
-    assert isinstance(name, basestring)
-    assert callable(func)
-    if name in __HOOKS:
-        warnings.warn('redefining hook %r (%r)' % (name, func))
-    __HOOKS[name] = func
-    return func
-
-def hook(*args):
-    if isinstance(args[0], basestring):
-        return lambda f: __register_hook(args[0], f)
-    else:
-        f = args[0]
-        return __register_hook(f.__name__, f)
-
 def gethook(name, default=None):
-    return __HOOKS.get(name, default)
+    print dir(HOOKS)
+    print (name, getattr(HOOKS, name))
+    return getattr(HOOKS, name) or default
 
 def callhook_required(name, *args, **kwargs):
-    f = __HOOKS.get(name)
+    f = getattr(HOOKS, name)
     assert f, 'implementation for hook %r required but not found' % name
     return f(*args, **kwargs)
 
 def callhook(name, *args, **kwargs):
-    f = __HOOKS.get(name)
+    f = getattr(HOOKS, name)
     if f:
         return f(*args, **kwargs)
-

Deleted: servres/trunk/conifer/integration/campus-interface.md
===================================================================
--- servres/trunk/conifer/integration/campus-interface.md	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/campus-interface.md	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,338 +0,0 @@
-% DRAFT: Interface specification for an OpenSRF campus-information service
-% Graham Fawcett
-% March 26, 2010
-
-# Introduction
-
-This document specifies the interface for an OpenSRF-based service
-which gives OpenSRF applications access to *campus information*, such
-as the names of courses taught at a given university, who is teaching
-them, and which students are enrolled in them.
-
-This service is designed to meet the needs of our reserves
-application, "Syrup." We hope the service will be useful in a wide
-range of library applications that could benefit from access to course-related
-information.
-
-This document specifies the OpenSRF interface of the campus
-information service, but it does not dictate how the service must be
-implemented. Each institution will need to implement the service,
-using the tools of their choice, in a way that makes local sense.
-
-# Design considerations
-
-## Partial implementations
-
-In an ideal world, a library would have unlimited access to all the
-course-related information they wanted; but many libraries do not
-enjoy such access. Not all applications need the same types of
-information, and many applications can adapt to different levels of
-available campus information. Given this, it is acceptable to
-*partially* implement the campus-information service, skipping the
-parts that you cannot (or choose not to) implement.
-
-For example, if you don't have access to any class-list information,
-but you do have a machine-readable version of the academic calendar,
-you could implement the course-lookup and term-lookup parts of the
-interface, but skip the course-offering parts.
-
-An application must be able to determine what parts of the interface
-you have implemented. Therefore, you must implement the
-`methods-supported` method (see [Static informational methods]). Since
-this method-list is essentially static (it will change only if you
-modify your implementation), an application may test it infrequently,
-e.g. just once upon startup.
-
-## Caching
-
-OpenSRF provides a high-performance caching framework. You should
-consider using this framework when designing your
-implementation. 
-
-Applications are discouraged from caching campus information:
-especially information on people and course offerings, which both
-change relatively frequently. It makes more sense to centralize policy
-decisions about the lifespans of campus data at the service layer.  If
-applications must cache campus information (e.g. for demonstrated
-performance reasons), they are encouraged to keep the cache-lifetimes
-as short as possible.
-
-# Data types
-
-All of these data types are needed in a complete implementation of the
-interface. Since you are free to implement only parts of the interface
-(see [Partial implementations]), all of these data types might not
-apply in your case.
-
-## Identifier types
-
-    COURSE-ID   = string        (matching a local COURSE-ID-FORMAT)
-    TERM-ID     = string        (matching a local TERM-ID-FORMAT)
-    OFFERING-ID = string        (matching a local OFFERING-ID-FORMAT)
-    PERSON-ID   = string        (matching a local PERSON-ID-FORMAT)
-
-The four identifier types are used respectively as unique keys for
-courses, terms, course offerings, and people. (`String` is the
-primitive type of strings of Unicode characers.)
-
-Since the PERSON-ID may be exposed in reports and user interfaces, it
-should be a common public identifier (such as a 'single-sign-on ID',
-'email prefix', or 'campus username') that can be displayed beside the
-person's name without violating privacy regulations.
-
-Your institution may use 'section numbers' to differentiate multiple
-offerings of a course in the same term. You may embed them in your
-identifiers: for example, the offering ID `ENG100-2010W-03` might
-represent Section 3 of English 100 being taught in Winter 2010. But it
-isn't required that your offering IDs are so structured.
-
-**Formats:** Each type of identifier complies with a respective,
-locally-defined format. You should define a (private, internal)
-function for each format, that verifies whether a given string matches
-the format. For example, a Java implementation might define a
-function, `boolean isValidCourseID(String)`. You might use regular
-expressions to define your formats, but it's not a requirement. At the
-very least, your local formats should reject empty strings as IDs. You
-may expose these functions for application use: see
-[Format-matching methods].
-
-## Record types
-
-Record types are modelled as associative arrays (sets of key/value
-pairs). \[Is this acceptable in OpenSRF? It's valid JSON, but I'm not clear on OpenSRF conventions.\]
-The following notation is used in the type definitions:
-
-             string            (a string primitive)
-             [string]*         (an unordered set of zero or more strings)
-             (string)?         (an optional string: it may be NULL.)
-
-Strictly, unordered sets *do* have an order, since they are
-implemented as JSON lists. But the specification does not guarantee
-that the order of the list is significant. 
-
-Missing optional values may be indicated in two equivalent ways:
-either include the key, and pair it with a `null` value (`{key: null,
-...}`), or simply omit the key/value pair from the record.
-               
-    COURSE = { id:    COURSE-ID, 
-               title: string }
-
-A COURSE record describes a course in the abstract sense, as it would
-appear in an academic calendar. It must have at least a unique course
-ID and a descriptive (possibly non-unique) title. It may include other
-attributes if you wish, but we specify `id` and `title` as required
-attributes.
-
-    TERM = { id:         TERM-ID, 
-             name:       string, 
-             start-date: date, 
-             end-date:   date }
-
-A TERM record describes a typical period in which a course is offered
-(a 'term' or 'semester'). It must have a unique term-ID, a
-probably-unique name, and start and end dates. (`Date` is a primitive
-type, representing a calendar date.)
-
-    PERSON = { id:         PERSON-ID, 
-               surname:    string, 
-               given-name: string,
-               email:      (string)? }
-
-A PERSON record describes a person! It must include a unique
-person-ID, a surname and given name. A value for `email` is
-optional. You may also add other attributes as you see fit.
-                 
-    OFFERING = { id:            OFFERING-ID,
-                 course:        COURSE-ID,
-                 starting-term: TERM-ID,
-                 ending-term:   TERM-ID,
-                 students:      [PERSON-ID]*,
-                 assistants:    [PERSON-ID]*,
-                 instructors:   [PERSON-ID]* }
-
-An OFFERING record describes a course offering: your institution might
-call this a 'class' or a 'section'.  It has specific start- and and
-end-dates (derived from its starting and ending terms: some
-institutions have courses that span multiple terms). The `course`
-attribute refers to the single course of which it is an instance (our
-specification punts on the issue of cross-listed offerings).  It has
-unordered sets of zero-or-more students, teaching assistants and
-instructors.
-
-Each OFFERING record is a snapshot of a course offering at a given
-time. It is assumed that people may join or leave the course
-offering at any point during its duration.
-
-The set of "assistants" is loosely defined. It might include teaching
-assistants (TAs and GAs) but also technical assistants, departmental
-support staff, and other ancillary support staff.
-
-    OFFERING-FLESHED = { id:            OFFERING-ID,
-                         course:        COURSE,
-                         starting-term: TERM,
-                         ending-term:   TERM,
-                         students:      [PERSON]*,
-                         assistants:    [PERSON]*,
-                         instructors:   [PERSON]* }
-
-A OFFERING-FLESHED record is like an OFFERING record, except that the
-course, term, and people-set attributes have been 'fleshed out', so
-that they contain not codes, but actual copies of the COURSE, TERM and
-PERSON records.
-
-# Method signatures
-
-The following notation is used for method signatures:
-
-    method-name:   arg1-type, ... argN-type -> result-type
-    
-The `void` type is used to express empty argument-lists.
-
-## Static informational methods
-
-    methods-supported:   void      -> [string]*
-
-The `methods-supported` method is the only method that you *must*
-implement (see [Partial implementations]). It returns a list of the
-names of the methods for which you've provided
-implementations. Applications can use this list to determine the
-capabilities of your implementation.
-    
-## Course methods
-
-    course-lookup:       COURSE-ID -> (COURSE)?
-    course-id-list:      void      -> [COURSE-ID]*
-    course-list:         void      -> [COURSE]*
-    course-id-example:   void      -> (COURSE-ID)?
-
-Given a COURSE-ID string, `course-lookup` will return the matching
-COURSE record, or `null` if no such course exists. 
-
-The methods `course-id-list` and `course-list` return a list of the
-IDs (or records, respectively) of *all* known courses in the campus
-system. An application might use these to populate option-lists or
-report headings. The lists may be limited to the courses which are
-defined in the current academic calendar (that is, your implementation
-may omit obsolete course descriptions).
-
-The `course-id-example` method returns a course ID *example*. In
-user-interfaces where a course ID must be typed in, this example can
-be used to offer some guidance to the user. If the method returns
-`null`, or if the method is not implemented, an application should
-simply omit any example from the user interface.
-
-## Term methods
-
-    term-lookup:         TERM-ID   -> (TERM)?
-    term-list:           void      -> [TERM]*
-    terms-at-date:       date      -> [TERM]*
-
-The `term-lookup` and `term-list` are analogous to the `course-lookup`
-and `course-list` methods. The `terms-at-date` method takes a date
-argument, and returns a list of all TERM records such that `term.start
-<= date <= term.finish`. (We do not specify that terms are
-non-overlapping.)
-
-## Person methods
-
-    person-lookup:       PERSON-ID   -> (PERSON)?
-
-## Offering methods
-
-To describe the return-values of some of the Offering methods, we
-introduce the notation `MBR(X)` as an abbreviation for the type
-`([X]*, [X]*, [X]*)`, that is, a trio of sets representing the three
-membership groups associated with a course offering: teachers,
-assistants, and students. The types of elements contained in the sets
-is specified by the specializing type, `X`: so, `MBR(PERSON)` is a
-trio of sets of PERSON records.
-
-    MBR(TYPE) = ([TYPE]*,  # memberships as a teacher,
-                 [TYPE]*,  # as an assistant,
-                 [TYPE]*)  # as a student.
-
-
-    course-term-offerings:         (COURSE-ID, TERM-ID) -> [OFFERING]*
-    course-term-offerings-fleshed: (COURSE-ID, TERM-ID) -> [OFFERING-FLESHED]*
-
-Given a COURSE-ID and a TERM-ID, these methods will return records for
-all course offerings for the course represented by COURSE-ID, whose
-`starting-term` *or* `ending-term` is equal to TERM-ID.
-
-    memberships:         PERSON-ID    -> MBR(OFFERING)
-    membership-ids:      PERSON-ID    -> MBR(OFFERING-ID)
-    memberships-fleshed: PERSON-ID    -> MBR(OFFERING-FLESHED)
-    
-These methods take a PERSON-ID and return a trio of sets whose
-elements represent the course-offerings in which the person is
-(respectively) a teacher, assistant, or student. 
-
-Within a given course-offering, a person must belong to no more than
-one of the three sets. For example, it is not permitted to be both a
-teacher and student for the same offering. 
-
-If the PERSON-ID is invalid, or if the person is not a member of any
-offerings, the return value should be a trio of three empty sets --
-`[[], [], []]` -- *not* a `null` value or an error.
-
-    member-ids:          OFFERING-ID -> MBR(PERSON-ID)
-    members:             OFFERING-ID -> MBR(PERSON)
-    
-These methods take an OFFERING-ID and return a trio of sets whose
-elements represent (respectively) the teachers, assistants, and
-students in the offering. 
-
-If the OFFERING-ID is invalid, or if the offering is "empty", the
-return value should be a trio of three empty sets -- `[[], [], []]` --
-*not* a `null` value or an error.
-
-    teacher-ids:         OFFERING-ID -> ([PERSON-ID]*, [PERSON-ID]*)
-    teachers:            OFFERING-ID -> ([PERSON]*, [PERSON]*)
-
-
-The `teacher` methods are identical to the `member` methods, except
-that the student set is omitted: the return-value is a *pair* of sets
-representing teachers and assistants. These are essentially optimized
-versions of the `members` methods for cases when you only need to know
-about the teaching and support teams (typically, very small groups)
-and can avoid the cost of calculating and transmitting the student list
-(typically, 10-100 times larger).
-
-## Format-matching methods
-
-    resembles-course-id:   string    -> boolean
-    resembles-offering-id: string    -> boolean
-    resembles-term-id:     string    -> boolean
-    resembles-person-id:   string    -> boolean
-    
-Applications can use these to implement data-input validation tests in
-user interfaces, primarily where lookups are not possible. They
-determine whether a given string falls within the general guidelines
-of what your IDs are supposed to look like. At some institutions, this
-might be the best you can offer: you might not have access to
-databases in which you can look records up, but at least you can offer
-a means to avoid basic typographic errors.
-
-You could implement these methods by exposing the functions you
-defined for your COURSE-ID-FORMAT, TERM-ID-FORMAT, OFFERING-ID-FORMAT
-and PERSON-ID-FORMAT tests (see [Identifier types]). At the least,
-these formats should ensure that empty strings are rejected.
-
-You might choose to use implement these as lookup functions, returning
-`true` only if a matching record was found. For example, if your
-school offers only two courses (say, `ENG100` and `ENG200`), you could
-choose to implement a `resembles-course-id` method that only returned
-`true` if the argument was exactly one of those two course codes.  No
-matter how you implement it, the intent of the `resembles` methods is
-to help avoid typographic errors, not to act as a membership test.
-
-[Partial implementations]: #partial-implementations
-[Static informational methods]: #static-informational-methods
-[Identifier types]: #identifier-types
-[Format-matching methods]: #format-matching-methods
-
-<!-- 
-Local Variables:
-mode: markdown
-End: 
--->

Deleted: servres/trunk/conifer/integration/default.py
===================================================================
--- servres/trunk/conifer/integration/default.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/default.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,18 +0,0 @@
-# Do not edit this file: make your own, instead.
-
-# See COURSE_CODES.txt for information.
-
-course_code_is_valid       = None
-course_code_example        = None
-course_code_list           = None
-course_code_lookup_title   = None
-course_code_cross_listings = None
-
-# See COURSE_SECTIONS.txt for information.
-
-sections_tuple_delimiter   = '|'
-sections_taught_by         = lambda u: []
-students_in                = lambda *sections: set()
-instructors_in             = lambda *sections: set()
-sections_for_code_and_term = lambda code, term: []
-

Deleted: servres/trunk/conifer/integration/example.py
===================================================================
--- servres/trunk/conifer/integration/example.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/example.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,72 +0,0 @@
-from default import *
-
-#----------------------------------------------------------------------
-# Course Codes
-
-_codes = [('ART99-100', 'Art History'),
-          ('BIOL55-350', 'Molecular Cell Biology'),
-          ('CRIM48-567', 'Current Issues in Criminology'),
-          ('ENGL26-280', 'Contemporary Literary Theory'),
-          ('ENGL26-420', 'Word and Image: The Contemporary Graphic Novel'),
-          ('SOCWK47-457', 'Advanced Social Work Research'),]
-
-_crosslists = set(['ENGL26-280', 'ENGL26-420'])
-
-
-course_code_is_valid = None
-
-course_code_example = 'BIOL55-350; SOCWK47-457'
-
-def course_code_list():
-    return [a for (a,b) in _codes]
-
-def course_code_lookup_title(course_code):
-    return dict(_codes).get(course_code)
-
-def course_code_cross_listings(course_code):
-    if course_code in _crosslists:
-        return list(_crosslists - set([course_code]))
-
-
-#----------------------------------------------------------------------
-# Course Sections
-
-sections_tuple_delimiter = '|'
-
-# For any of the students to actually appear in a course site, they
-# must also exist as Django users (or be in an authentication backend
-# that supports 'maybe_initialize_user'; see auth_evergreen.py).
-
-_db = [
-    #(instructor, (term, code, sec-code), 'student1 student2 ... studentN'),
-    ('fred', ('2009W', 'ENG203', '1'), 'jim joe jack ellen ed'),
-    ('fred', ('2009W', 'ENG327', '1'), 'ed paul bill'),
-    ('art',  ('2009W', 'LIB201', '1'), 'graham bill ed'),
-    ('graham', ('2009S', 'ART108', '1'), 'alan june jack'),
-    ('graham', ('2009S', 'ART108', '2'), 'emmet'),
-    ('graham', ('2009S', 'ART108', '3'), 'freda hugo bill'),
-]
-
-def sections_taught_by(username):
-    return set([s[1] for s in _db if s[0] == username])
-
-def students_in(*sections):
-    def inner():
-        for instr, sec, studs in _db:
-            if sec in sections:
-                for s in studs.split(' '):
-                    yield s
-    return set(inner())
-
-def instructors_in(*sections):
-    def inner():
-        for instr, sec, studs in _db:
-            if sec in sections:
-                yield instr
-    return set(inner())
-
-def sections_for_code_and_term(code, term):
-    return [(t, c, s) for (instr, (t, c, s), ss) in _db \
-                if c == code and t == term]
-
-

Modified: servres/trunk/conifer/integration/hooks.py
===================================================================
--- servres/trunk/conifer/integration/hooks.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/integration/hooks.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,16 +1,10 @@
-from conifer.integration._hooksystem import *
 from datetime import date
+from django.conf import settings
+from conifer.libsystems.evergreen.support import initialize
+from conifer.libsystems.z3950 import marcxml as M
+from conifer.libsystems.evergreen import item_status as I
+from conifer.libsystems.z3950 import pyz3950_search as PZ
 
-#----------------------------------------------------------------------
-# Your hooks go here.
-
-# @hook
-# def can_create_sites(user):
-#     ...
-
-#TODO: this is for testing purposes only! Remove.
-
- at hook
 def department_course_catalogue():
     """
     Return a list of rows representing all known, active courses and
@@ -28,7 +22,6 @@
         ('Social Work','02-47-456','Social Work and Health'),
         ]
 
- at hook
 def term_catalogue():
     """
     Return a list of rows representing all known terms. Each row
@@ -40,3 +33,27 @@
         ('2011S', '2011 Summer', date(2011,5,1), date(2011,9,1)),
         ('2011F', '2011 Fall', date(2011,9,1), date(2011,12,31)),
         ]
+
+
+#--------------------------------------------------
+# ILS integration
+
+EG_BASE = 'http://%s/' % settings.EVERGREEN_GATEWAY_SERVER
+initialize(EG_BASE)
+
+
+def item_status(item):
+    if 'psychology' in item.title.lower():
+        return (8, 4, 2)
+    else:
+        return (2, 0, 0)
+
+
+def cat_search(query, start=1, limit=10):
+    if query.startswith(EG_BASE):
+        results = M.marcxml_to_records(I.url_to_marcxml(query))
+        numhits = len(results)
+    else:
+        cat_host, cat_port, cat_db = settings.Z3950_CONFIG
+        results, numhits = PZ.search(cat_host, cat_port, cat_db, query, start, limit)
+    return results, numhits

Modified: servres/trunk/conifer/libsystems/evergreen/item_status.py
===================================================================
--- servres/trunk/conifer/libsystems/evergreen/item_status.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/libsystems/evergreen/item_status.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -27,7 +27,7 @@
             if item_id:
                 marc_url = ("%s/opac/extras/supercat/"
                             "retrieve/marcxml/record/%s" % (support.BASE, item_id))
-            xml = urllib2.urlopen(marc_url).read()
+            xml = unicode(urllib2.urlopen(marc_url).read(), 'utf-8')
         return xml
 
 if __name__ == '__main__':

Modified: servres/trunk/conifer/libsystems/z3950/marcxml.py
===================================================================
--- servres/trunk/conifer/libsystems/z3950/marcxml.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/libsystems/z3950/marcxml.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,11 +1,19 @@
 from xml.etree import ElementTree
 
 # Note: the 'record' parameters passed to these functions must be
-# Unicode strings, not plain Python strings.
+# Unicode strings, not plain Python strings; or ElementTree instances.
 
+def _to_tree(unicode_or_etree):
+    if isinstance(unicode_or_etree, unicode):
+        tree = ElementTree.fromstring(unicode_or_etree.encode('utf-8'))
+    elif isinstance(unicode_or_etree, ElementTree._ElementInterface):
+        tree = unicode_or_etree
+    else:
+        raise Exception('Bad parameter', unicode_or_etree)
+    return tree
+
 def marcxml_to_records(rec):
-    assert isinstance(rec, unicode)
-    tree = ElementTree.fromstring(rec.encode('utf-8'))
+    tree = _to_tree(rec)
     if tree.tag == '{http://www.loc.gov/MARC21/slim}collection':
         # then we may have multiple records
         records = tree.findall('{http://www.loc.gov/MARC21/slim}record')
@@ -15,9 +23,8 @@
         return []
     return records
     
-def record_to_dictionary(record, multiples=True):
-    assert isinstance(record, unicode)
-    tree = ElementTree.fromstring(record.encode('utf-8'))
+def record_to_dictionary(rec, multiples=True):
+    tree = _to_tree(rec)
     dct = {}
     for df in tree.findall('{http://www.loc.gov/MARC21/slim}datafield'):
         t = df.attrib['tag']
@@ -29,8 +36,7 @@
     return dct
 
 def marcxml_to_dictionary(rec, multiples=False):
-    assert isinstance(rec, unicode)
-    tree = ElementTree.fromstring(rec.encode('utf-8'))
+    tree = _to_tree(rec)
     if tree.tag == '{http://www.loc.gov/MARC21/slim}collection':
         # then we may have multiple records
         records = tree.findall('{http://www.loc.gov/MARC21/slim}record')

Modified: servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
===================================================================
--- servres/trunk/conifer/libsystems/z3950/pyz3950_search.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/libsystems/z3950/pyz3950_search.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -8,6 +8,7 @@
 import re
 import sys
 from marcxml import marcxml_to_dictionary
+from xml.etree import ElementTree as ET
 
 try:
 
@@ -77,7 +78,7 @@
         rec = unicode(rec, 'ascii', 'replace')
 
         assert isinstance(rec, unicode) # this must be true.
-        parsed.append(rec)
+        parsed.append(ET.fromstring(rec.encode('utf-8')))
     return parsed, len(res)
 
 

Modified: servres/trunk/conifer/static/main.css
===================================================================
--- servres/trunk/conifer/static/main.css	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/static/main.css	2010-07-15 00:55:26 UTC (rev 918)
@@ -343,3 +343,7 @@
 ul.heading_tree { margin: 0; padding-left: 0; }
 ul.heading_tree ul { margin: 0; padding-left: 25px; }
 
+
+.availability { float: right; color: darkred; background-color: #eee; padding: 4px; min-width: 24px; }
+.availability .available { color: green; }
+.avail_nonphys { background-color: white; }
\ No newline at end of file

Added: servres/trunk/conifer/syrup/integration.py
===================================================================
--- servres/trunk/conifer/syrup/integration.py	                        (rev 0)
+++ servres/trunk/conifer/syrup/integration.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -0,0 +1,71 @@
+# this is a placeholder module, for the definitions in the
+# INTEGRATION_MODULE defined in local_settings.py. 
+
+# Please do not define anything in this file. It will be automatically
+# populated once confier.syrup.models has been evaluated.
+
+def disable(func):
+    return None
+
+
+ at disable
+def can_create_sites(user):
+    """
+    Return True if this User object represents a person who should be
+    allowed to create new course-reserve sites. Note that users marked
+    as 'staff' are always allowed to create new sites.
+    """
+    pass
+
+
+ at disable
+def department_course_catalogue():
+    """
+    Return a list of rows representing all known, active courses and
+    the departments to which they belong. Each row should be a tuple
+    in the form: ('Department name', 'course-code', 'Course name').
+    """
+    pass
+
+
+ at disable
+def term_catalogue():
+    """
+    Return a list of rows representing all known terms. Each row
+    should be a tuple in the form: ('term-code', 'term-name',
+    'start-date', 'end-date'), where the dates are instances of the
+    datetime.date class.
+    """
+    pass
+
+
+ at disable
+def cat_search(query, start=1, limit=10):
+    """
+    Given a query, and optional start/limit values, return a tuple
+    (results, numhits). Results is a list of
+    xml.etree.ElementTree.Element instances. Each instance is a
+    MARCXML '<{http://www.loc.gov/MARC21/slim}record>'
+    element. Numhits is the total number of hits found against the
+    search, not simply the size of the results lists.
+    """
+
+
+ at disable
+def item_status(item):
+    """
+    Given an Item object, return three numbers: (library, desk,
+    avail). Library is the total number of copies in the library
+    system; Desk is the number of copies at the designated reserves
+    desk; and Avail is the number of copies available for checkout at
+    the given moment. Note that 'library' includes 'desk' which
+    includes 'avail'. You may also return None if the item is
+    nonsensical (e.g. it is not a physical object, or it has no bib
+    ID).
+    
+    Note, 'item.bib_id' is the item's bib_id, or None;
+    'item.item_type' will equal 'PHYS' for physical items;
+    'item.site.service_desk' is the ServiceDesk object associated with
+    the item. The ServiceDesk object has an 'external_id' attribute
+    which should represent the desk in the ILS.
+    """

Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/syrup/models.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -13,9 +13,7 @@
 from django.conf import settings
 campus = settings.CAMPUS_INTEGRATION
 # TODO: fixme, not sure if conifer.custom is a good parent.
-from conifer.custom import lib_integration
-from conifer.libsystems.z3950.marcxml import record_to_dictionary
-from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc
+import conifer.libsystems.z3950.marcxml as MX
 from django.utils import simplejson as json
 
 #----------------------------------------------------------------------
@@ -470,7 +468,7 @@
     #--------------------------------------------------
     # MARC
     def marc_as_dict(self):
-        return record_to_dictionary(self.marcxml)
+        return MX.record_to_dictionary(self.marcxml)
         
     def marc_dc_subset(self):
         return json.dumps(self.marc_as_dict())
@@ -524,7 +522,15 @@
         and a friendly description of the physical item's status"""
         # TODO: this needs to be reimplemented, based on copy detail
         # lookup in the ILS. It also may not belong here!
-        return (True, 'NOT-IMPLEMENTED')
+        #return (True, 'NOT-IMPLEMENTED')
+        stat = callhook('item_status', self)
+        if not stat:
+            return (False, 'Status information not available.')
+        else:
+            lib, desk, avail = stat
+            return (avail > 0,
+                    '%d of %d copies available at reserves desk; %d total copies in library system' % (
+                    avail, desk, lib))
 
     # TODO: stuff I'm not sure about yet. I don't think it belongs here.
 
@@ -560,3 +566,13 @@
         return literal(highlight_re.sub(highlighter, text))
     else:
         return highlight_re.sub(highlighter, text)
+
+
+
+if hasattr(settings, 'INTEGRATION_MODULE'):
+    import conifer.syrup.integration
+    hooks = __import__(settings.INTEGRATION_MODULE, fromlist=[''])
+    for k,v in hooks.__dict__.items():
+        if callable(v):
+            setattr(conifer.syrup.integration, k, v)
+

Modified: servres/trunk/conifer/syrup/views/__init__.py
===================================================================
--- servres/trunk/conifer/syrup/views/__init__.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/syrup/views/__init__.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,4 +1,3 @@
-from conifer.integration import hooks
 from general import *
 from sites import *
 from items import *

Modified: servres/trunk/conifer/syrup/views/_common.py
===================================================================
--- servres/trunk/conifer/syrup/views/_common.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/syrup/views/_common.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -19,7 +19,6 @@
 import re
 import sys
 from django.forms.models import modelformset_factory
-from conifer.custom import lib_integration
 from conifer.libsystems.z3950.marcxml import (marcxml_to_dictionary,
                                               marcxml_dictionary_to_dc)
 from conifer.syrup.fuzzy_match import rank_pending_items

Modified: servres/trunk/conifer/syrup/views/items.py
===================================================================
--- servres/trunk/conifer/syrup/views/items.py	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/syrup/views/items.py	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,7 +1,9 @@
 from _common import *
 from django.utils.translation import ugettext as _
 from xml.etree import ElementTree as E
+from conifer.syrup import integration
 
+
 @members_only
 def item_detail(request, site_id, item_id):
     """Display an item (however that makes sense).""" 
@@ -150,7 +152,7 @@
                             site=site, parent_item=parent_item)
         query = request.GET.get('query','').strip()
         start, limit = (int(request.GET.get(k,v)) for k,v in (('start',1),('limit',10)))
-        results, numhits = lib_integration.cat_search(query, start, limit)
+        results, numhits = integration.cat_search(query, start, limit)
         return g.render('item/item_add_cat_search.xhtml', 
                         results=results, query=query, 
                         start=start, limit=limit, numhits=numhits,
@@ -316,172 +318,3 @@
             return HttpResponseRedirect(new_parent.item_url('meta'))
         else:
             return HttpResponseRedirect(site.site_url())
-        
-        
-
-#-----------------------------------------------------------------------------
-# Physical item processing
-
- at admin_only                     # fixme, is this the right permission?
-def phys_index(request):
-    return g.render('phys/index.xhtml')
-
- at admin_only                     # fixme, is this the right permission?
-def phys_checkout(request):
-    if request.method != 'POST':
-        return g.render('phys/checkout.xhtml', step=1)
-    else:
-        post = lambda k: request.POST.get(k, '').strip()
-        # dispatch based on what 'step' we are at.
-        step = post('step')     
-        func = {'1': _phys_checkout_get_patron,
-                '2':_phys_checkout_do_checkout,
-                '3':_phys_checkout_do_another,
-                }[step]
-        return func(request)
-
-def _phys_checkout_get_patron(request):
-    post           = lambda k: request.POST.get(k, '').strip()
-    patron, item   = post('patron'), post('item')
-    msg            = lib_integration.patron_info(patron)
-    if not msg['success']:
-        return simple_message(_('Invalid patron barcode'),
-                              _('No such patron could be found.'))
-    else:
-        patron_descrip = '%s (%s) &mdash; %s' % (
-            msg['personal'], msg['home_library'], msg['screenmsg'])
-        return g.render('phys/checkout.xhtml', step=2, 
-                        patron=patron, patron_descrip=patron_descrip)
-
-def _phys_checkout_do_checkout(request):
-    post           = lambda k: request.POST.get(k, '').strip()
-    patron, item   = post('patron'), post('item')
-    patron_descrip = post('patron_descrip')
-
-    # make sure the barcode actually matches with a known barcode in
-    # Syrup. We only checkout what we know about.
-    matches = models.Item.with_barcode(item)
-    if not matches:
-        is_successful = False
-        item_descrip  = None
-    else:
-        msg_status   = lib_integration.item_status(item)
-        msg_checkout = lib_integration.checkout(patron, item)
-        is_successful = msg_checkout['success']
-        item_descrip = '%s &mdash; %s' % (
-            msg_status['title'], msg_status['status'])
-
-    # log the checkout attempt.
-    log_entry = models.CheckInOut.objects.create(
-        is_checkout = True,
-        is_successful = is_successful,
-        staff = request.user,
-        patron = patron,
-        patron_descrip = patron_descrip,
-        item = item,
-        item_descrip = item_descrip)
-    log_entry.save()
-
-    if not matches:
-        return simple_message(
-            _('Item not found in Reserves'),
-            _('This item does not exist in the Reserves database! '
-              'Cannot check it out.'))
-    else:
-        return g.render('phys/checkout.xhtml', step=3, 
-                        patron=patron, item=item,
-                        patron_descrip=patron_descrip,
-                        checkout_result=msg_checkout,
-                        item_descrip=item_descrip)
-
-def _phys_checkout_do_another(request):
-    post           = lambda k: request.POST.get(k, '').strip()
-    patron         = post('patron')
-    patron_descrip = post('patron_descrip')
-    return g.render('phys/checkout.xhtml', step=2, 
-                    patron=patron,
-                    patron_descrip=patron_descrip)
-
-#------------------------------------------------------------
-
- at admin_only        
-def phys_mark_arrived(request):
-    if request.method != 'POST':
-        return g.render('phys/mark_arrived.xhtml')
-    else:
-        barcode = request.POST.get('item', '').strip()
-        already = models.PhysicalObject.by_barcode(barcode)
-        if already:
-            msg = _('This item has already been marked as received. Date received: %s')
-            msg = msg % str(already.received)
-            return simple_message(_('Item already marked as received'), msg)
-        bib_id  = lib_integration.barcode_to_bib_id(barcode)
-        if not bib_id:
-            return simple_message(_('Item not found'), 
-                                  _('No item matching this barcode could be found.'))
-
-        marcxml = lib_integration.bib_id_to_marcxml(bib_id)
-        dct     = marcxml_to_dictionary(marcxml)
-        dublin  = marcxml_dictionary_to_dc(dct)
-        # merge them
-        dct.update(dublin)
-        ranked = rank_pending_items(dct)
-        return g.render('phys/mark_arrived_choose.xhtml', 
-                        barcode=barcode,
-                        bib_id=bib_id,
-                        ranked=ranked,
-                        metadata=dct)
-
- at admin_only        
-def phys_mark_arrived_match(request):
-    choices = [int(k.split('_')[1]) for k in request.POST if k.startswith('choose_')]
-    if not choices:
-        return simple_message(_('No matching items selected!'),
-                              _('You must select one or more matching items from the list.'))
-    else:
-        barcode = request.POST.get('barcode', '').strip()
-        assert barcode
-        smallint = request.POST.get('smallint', '').strip() or None
-        try:
-            phys = models.PhysicalObject(barcode=barcode,
-                                         receiver = request.user,
-                                         smallint = smallint)
-            phys.save()
-        except Exception, e:
-            return simple_message(_('Error'), repr(e), go_back=True)
-
-        for c in choices:
-            item = models.Item.objects.get(pk=c)
-            current_bc = item.barcode()
-            if current_bc:
-                item.metadata_set.filter(name='syrup:barcode').delete()
-            item.metadata_set.create(name='syrup:barcode', value=barcode)
-            item.save()
-    return g.render('phys/mark_arrived_outcome.xhtml')
-
- at admin_only
-def phys_circlist(request):
-    term_code = request.GET.get('term')
-    if not term_code:
-        terms = models.Term.objects.order_by('code')
-        return g.render('phys/circlist_index.xhtml', terms=terms)
-
-    term = get_object_or_404(models.Term, code=term_code)
-
-    # gather the list of wanted items for this term.
-    # Fixme, I need a better way.
-
-    cursor = django.db.connection.cursor()
-    q = "select item_id from syrup_metadata where name='syrup:barcode'"
-    cursor.execute(q)
-    bad_ids = set([r[0] for r in cursor.fetchall()])
-    cursor.close()
-
-    wanted = models.Item.objects.filter(
-        item_type='PHYS', site__term=term).select_related('metadata')
-    wanted = [w for w in wanted if w.id not in bad_ids]
-    return g.render('phys/circlist_for_term.xhtml', 
-                    term=term,
-                    wanted=wanted)
-    
-

Modified: servres/trunk/conifer/templates/components/site.xhtml
===================================================================
--- servres/trunk/conifer/templates/components/site.xhtml	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/templates/components/site.xhtml	2010-07-15 00:55:26 UTC (rev 918)
@@ -32,6 +32,22 @@
       class="itemtree">
     <li py:for="item, subs in tree" class="item_${item.item_type} an_item"
 	id="item_${item.id}">
+      <div class="availability" py:if="item.item_type == 'PHYS'">
+	<?python
+	  stat = callhook('item_status', item) if (item.item_type == 'PHYS') else None
+	  valid = stat is not None
+	?>
+	<div py:if="valid" py:with="(_lib, _desk, _avail) = stat"
+	     class="${_avail &gt; 0 and 'available' or 'unavailable'}"
+	     title="${_avail} of ${_desk} copies available at reserves desk; ${_lib} total copies in library system">
+	  ${_avail}/${_desk}
+	</div>
+	<div py:if="not valid" title="No copies are available at the reserves desk. No further status information is available.">
+	  &#x2205;
+	</div>
+      </div>
+      <div class="availability avail_nonphys" py:if="item.item_type != 'PHYS'">
+      </div>
       <div class="mainline ${item.item_type=='HEADING' and 'headingmainline' or ''}">
 	<a href="${item.item_url()}" class="mainlink">${item}</a> 
 	<span class="menublock" py:if="not (item.item_type=='HEADING' and not edit)">

Modified: servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_add_cat_search.xhtml	2010-07-15 00:55:13 UTC (rev 917)
+++ servres/trunk/conifer/templates/item/item_add_cat_search.xhtml	2010-07-15 00:55:26 UTC (rev 918)
@@ -1,5 +1,6 @@
 <?python
 from django.utils.simplejson import dumps
+from xml.etree import ElementTree as ET
 from conifer.libsystems.z3950.marcxml import record_to_dictionary
 from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc as to_dublin
 title = _('Add physical or electronic item, by catalogue search')
@@ -69,7 +70,7 @@
 	  <td>
 	    <form action="." method="POST">
 	      <!-- !TODO: is utf8 okay here? I shouldn't have to do any decoding here. -->
-	      <input type="hidden" name="pickitem" value="${res}"/>
+	      <input type="hidden" name="pickitem" value="${ET.tostring(res)}"/>
 	      <input type="submit" value="Pick this item"/>
 	    </form>
 	  </td>



More information about the open-ils-commits mailing list