[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) — %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 — %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 > 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.">
+ ∅
+ </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