def disable(func):
return None
-
-
class EvergreenIntegration(object):
# Either specify EVERGREEN_SERVERin your local_settings, or override
# end of variables dependent on EVERGREEN_SERVER
# ----------------------------------------------------------------------
-
+ # BIB_PART_MERGE: display multiple parts for one bib title
+ # that have been scanned in separately in one section
+ BIB_PART_MERGE = bool(getattr(settings, 'BIB_PART_MERGE', True))
# OPAC_LANG and OPAC_SKIN: localization skinning for your OPAC
ATTACHMENT_EXPRESSION = getattr(settings, 'ATTACHMENT_REGEXP', '\w*DVD\s?|\w*CD\s?|\w[Gg]uide\s?|\w[Bb]ooklet\s?|\w*CD\-ROM\s?')
IS_ATTACHMENT = re.compile(ATTACHMENT_EXPRESSION)
+ # regular expression to volume designations within a
+ # call number
+ EMBEDDEDVOL_EXPRESSION = getattr(settings, 'EMBEDDEDVOL_REGEXP','\w*[Vv]\.\s?(\d+)')
+ IS_EMBEDDEDVOL = re.compile(EMBEDDEDVOL_EXPRESSION)
+
# Used if you're doing updates to Evergreen from Syrup.
UPDATE_CHOICES = [
('Cat', 'Catalogue'),
('Zap', 'Remove from Syrup'),
]
-
# ----------------------------------------------------------------------
-
-
-
def __init__(self):
# establish our OpenSRF connection.
initialize(self)
status_decode = [(str(x['id']), x['name'])
for x in E1('open-ils.search.config.copy_status.retrieve.all')]
+ #for x in E1('open-ils.search.config.copy_status.retrieve.all'):
+ # print "x", x
self.AVAILABLE = [id for id, name in status_decode if name == 'Available'][0]
self.RESHELVING = [id for id, name in status_decode if name == 'Reshelving'][0]
+ self.CHECKEDOUT = [id for id, name in status_decode if name == 'Checked out'][0]
- def item_status(self, item):
+ def item_status(self, item, bcs=[], ids=[]):
"""
Given an Item object, return three numbers: (library, desk,
avail). Library is the total number of copies in the library
"""
if not item.bib_id:
return None
- return self._item_status(item.bib_id, item.barcode)
+ #really silly function to turn list into string for passing
+ def make_obj_string(objs):
+ objlist = ""
+ for obj in objs:
+ if len(objlist) > 0:
+ objlist = objlist + ';'
+
+ for o in obj:
+ if obj.index(o) > 0:
+ objlist = objlist + '/'
+ objlist = objlist + str(o)
+
+ return objlist
+
+ bclist = make_obj_string(bcs)
+ idlist = make_obj_string(ids)
+ return self._item_status(item.bib_id, item.barcode, bclist, idlist)
CACHE_TIME = 300
@memoize(timeout=CACHE_TIME)
- def _item_status(self, bib_id, barcode):
+ def _item_status(self, bib_id, barcode, bclist, idlist):
+
+ #sanity variables for multipart titles
+ DUE = 0
+ READY = 1
+ LOCKED = 2 #lock in an available copy
class copy_obj:
- def __init__(self, circ_modifier, circs, part_label, part_sort):
+ def __init__(self, circ_modifier, circs, part_label, part_sort, syrup_id):
self.circ_modifier = circ_modifier
self.circs = circs
self.part_label = part_label
self.part_sort = part_sort
-
- #if a reserve title is specific to a barcode, there will not be more than one
- def limit_counts(avail,lib,desk):
- limit_avail = 0
- limit_lib = 0
- limit_desk = 0
- if avail >= 1:
- limit_avail = 1
- if lib >= 1:
- limit_lib = 1
- if desk >= 1:
- limit_desk = 1
- return limit_avail, limit_lib, limit_desk
-
-
- def get_copydetails(barcode,copyids,reserves_loc):
+ self.syrup_id = syrup_id
+
+ def make_obj_list(objlist):
+ objset = []
+ objcoll = objlist.split(";")
+ for o in objcoll:
+ objset.append(o.split("/"))
+
+ return objset
+
+ def collect_set(barcode,bcs,ids):
+ bc_dups = []
+ id_dups = []
+ i=0
+ for bc in bcs:
+ if barcode in bc:
+ return bc,ids[i]
+ i = i+1
+
+ return bc_dups, id_dups
+
+ def get_copydetails(barcode,copyids,reserves_loc,bcs,ids):
copy_list = []
+ bcs_set, ids_set = collect_set(barcode,bcs,ids)
+
for copyid in copyids:
circinfo = E1(OPENSRF_FLESHED2_CALL, copyid)
circbarcode = None
+
if barcode is not None:
circbarcode = circinfo.get("barcode")
if thisloc:
thisloc = thisloc.get("name")
- if thisloc in reserves_loc and barcode==circbarcode:
+ #create copy object for supplied barcode - will be all barcodes if none supplied
+ if thisloc in reserves_loc and (barcode==circbarcode or circbarcode in bcs_set):
circ_modifier = circinfo.get("circ_modifier")
circs = circinfo.get("circulations")
parts = circinfo.get("parts")
if parts:
part = parts[0]
if part:
- part_label = ' ' + part.get("label")
+ part_label = part.get("label")
part_sort = part.get("label_sortkey")
- copy_list.append(copy_obj(circ_modifier,circs,part_label,part_sort))
+ id_ind = -1
+ if circbarcode in bcs_set:
+ id_ind = ids_set[bcs_set.index(circbarcode)]
+ copy_list.append(copy_obj(circ_modifier,circs,part_label,part_sort,id_ind))
return sorted(copy_list, key=lambda copy: copy.part_sort)
- def get_dueinfo(callprefix,callsuffix,callno,earliestdue,attachtest,voltest,sort_callno,bringfw,dueinfo):
+ #deal with call numbers that have embedded parts - ugh!
+ def get_dueinfo(callprefix,callsuffix,callno,earliestdue,attachtest,voltest,sort_callno,
+ bringfw,dueinfo):
+
tmpinfo = ''
_callprefix = callprefix
return _dueinfo,_callno,_callprefix,_callsuffix
+ #get due information - lots of pieces passed on for embedded parts
+ def deal_with_dues(duetime,avail,bringfw,copy,callprefix,callsuffix,callno,earliestdue,
+ attachtest,voltest,sort_callno,dueinfo):
+
+ earlydue = earliestdue
+ due = dueinfo
+ due_callprefix = callprefix
+ due_callno = callno
+ due_callsuffix = callsuffix
+
+ if (avail == 0 or bringfw) and copy.circs and len(copy.circs) > 0:
+ if len(dueinfo) == 0 or bringfw:
+ earlydue = duetime
+ due,due_callno,due_callprefix,due_callsuffix = get_dueinfo(callprefix,callsuffix,callno,
+ earliestdue,attachtest,voltest,sort_callno,bringfw,dueinfo)
+
+ if duetime < earlydue and not bringfw:
+ earlydue = duetime
+ due = time.strftime(self.DUE_FORMAT,earliestdue)
+
+ return due, due_callprefix, due_callno,due_callsuffix
+
+ #create initial call no and counts
+ def initialVals(prefix,suffix,callno,lib):
+ initial_callno = callno
+ if prefix:
+ initial_callno = prefix + callno
+ if suffix:
+ initial_callno = callno + suffix
+ initial_avail = stats.get(self.AVAILABLE, 0)
+ initial_avail += stats.get(self.RESHELVING, 0)
+ anystatus_here = sum(stats.values())
+
+ return initial_callno, lib + anystatus_here
+
+ #sometimes part information is in the callno directly, try to combine for display by
+ #shifting to suffix - otherwise treat normally
+ def add_in_embedded_parts(prefix,suffix,callprefix,callsuffix,callno,voltest,attachtest,vol):
+ embed_prefix = callprefix
+ embed_callno = callno
+ embed_suffix = callsuffix
+
+ if (voltest and vol > 0 ):
+ if (int(voltest.group(1)) > vol):
+ embed_suffix = "/" + callno
+ else:
+ embed_prefix = callno + "/"
+ elif attachtest and callno.find(attachtest.group(0)) == -1:
+ if len(callno) > 0:
+ embed_suffix = "/" + callno
+ else:
+ embed_prefix = callno
+ else:
+ embed_callno = prefix + callno + suffix
+
+ return embed_prefix, embed_callno, embed_suffix
+
+ #probably not needed but final sanity check for embedded parts
+ def last_check_embed(callprefix,callno,callsuffix,voltest,vol):
+ last_call = callno
+ last_vol = vol
+ if callno.find(callprefix) == -1:
+ last_call = callprefix + callno
+ if callno.find(callsuffix) == -1:
+ last_call = last_call + callsuffix
+ if voltest:
+ last_vol = int(voltest.group(1))
+
+ return last_call, last_vol
+
+ #use counts from system if not parts
+ def get_desk_counts(counts):
+ desk_count = 0
+ for i, j, k, l, m, n in counts:
+ if m in self.RESERVES_DESK_NAME:
+ desk_count += n.get(self.AVAILABLE, 0)
+ desk_count += n.get(self.RESHELVING, 0)
+ desk_count += n.get(self.CHECKEDOUT, 0)
+ return desk_count
+
+ #pull together status information
def sort_out_status(barcode, sort_vol, counts, version, sort_lib, sort_desk, sort_avail,
- sort_callno, sort_dueinfo, sort_circmod, sort_allcalls, sort_alldues, prefix, suffix):
+ sort_callno, sort_dueinfo, sort_circmod, sort_allcalls, sort_alldues, prefix, suffix,
+ bcs,ids):
vol = sort_vol
lib = sort_lib
callprefix = ''
callsuffix = ''
- if prefix:
- callno = prefix + callno
- if suffix:
- callno = callno + suffix
- avail_here = stats.get(self.AVAILABLE, 0)
- avail_here += stats.get(self.RESHELVING, 0)
- anystatus_here = sum(stats.values())
- lib += anystatus_here
+ # get initial call number and total library count
+ callno, lib = initialVals(prefix,suffix,callno,lib)
# volume check - based on v.1, etc. in call number
- voltest = re.search(r'\w*v\.\s?(\d+)', callno)
+ voltest = re.search(self.IS_EMBEDDEDVOL, callno)
# attachment test
attachtest = re.search(self.IS_ATTACHMENT, callno)
- desk += anystatus_here
- avail += avail_here
dueinfo = ''
- if (voltest and vol > 0 ):
- if (int(voltest.group(1)) > vol):
- callsuffix = "/" + callno
- else:
- callprefix = callno + "/"
- elif attachtest and callno.find(attachtest.group(0)) == -1:
- if len(callno) > 0:
- callsuffix = "/" + callno
- else:
- callprefix = callno
- else:
- callno = prefix + callno + suffix
+ # combine volume designations for embedded values
+ callprefix,callno,callsuffix = add_in_embedded_parts(prefix,suffix,
+ callprefix,callsuffix,callno,voltest,attachtest,vol)
if version >= 2.1:
copyids = E1(OPENSRF_CN_CALL, bib_id, [prefix,sort_callno,suffix], org)
else:
copyids = E1(OPENSRF_CN_CALL, bib_id, sort_callno, org)
- copies = get_copydetails(barcode,copyids,self.RESERVES_DESK_NAME)
- if barcode is not None:
- avail = lib = desk = 1
+ #get copy information
+ copies = get_copydetails(barcode,copyids,self.RESERVES_DESK_NAME,bcs,ids)
+
+ desk = get_desk_counts(counts)
+ avail = desk
+
+ copy_parts = []
+ duetime = None
+ earliestdue = None
- # we want to return the resource that will be returned first if
- # already checked out
+ # we want to identify the copy that will be returned first if
+ # all are checked out
for copy in copies:
if copy.part_label:
#print "callno", callno
#print "sort_callno", sort_callno
callno = sort_callno + " " + copy.part_label
- allcalls.append([callno,1])
+ if copy.part_sort in copy_parts and len(copy_parts) > 0:
+ allcalls[len(allcalls) - 1] = [callno,READY,copy.syrup_id,copy.part_label]
+ else:
+ allcalls.append([callno,READY,copy.syrup_id,copy.part_label])
+ copy_parts.append(copy.part_sort)
bringfw = attachtest
rawdate = rawdate[:-5]
duetime = time.strptime(rawdate, self.TIME_FORMAT)
- if (avail == 0 or bringfw) and copy.circs and len(copy.circs) > 0:
- if len(dueinfo) == 0 or bringfw:
- earliestdue = duetime
- dueinfo,callno,callprefix,callsuffix = get_dueinfo(callprefix,callsuffix,callno,
- earliestdue,attachtest,voltest,sort_callno,bringfw,dueinfo)
-
- # way too wacky to sort out embedded vols for this
- if duetime < earliestdue and not bringfw:
- earliestdue = duetime
- dueinfo = time.strftime(self.DUE_FORMAT,earliestdue)
+ #get due information - lots of extra pieces needed for embedded parts
+ dueinfo,callprefix,callno,callsuffix = deal_with_dues(duetime,avail,bringfw,copy,
+ callprefix,callsuffix,callno,earliestdue,attachtest,voltest,sort_callno,dueinfo)
alldisplay = callno + ' (Available)'
if copy.circs and isinstance(copy.circs, list):
+ if len(allcalls) > 0 and (earliestdue is None or duetime < earliestdue):
+ earliestdue = duetime
alldisplay = '%s (DUE: %s)' % (callno,time.strftime(self.DUE_FORMAT,duetime))
- if barcode is not None:
- avail = 0
- if copy.part_label:
- allcalls[len(allcalls) - 1] = [alldisplay,0]
+ if len(allcalls) > 0:
+ if allcalls[len(allcalls) - 1][1] != LOCKED:
+ allcalls[len(allcalls) - 1] = [alldisplay,DUE,copy.syrup_id,copy.part_label]
+ avail -= 1
+ else:
+ avail -= 1
+
+ elif len(allcalls) > 0:
+ allcalls[len(allcalls) - 1] = [callno,LOCKED,copy.syrup_id,copy.part_label]
alldues.append(alldisplay)
if voltest or attachtest:
- if callno.find(callprefix) == -1:
- callno = callprefix + callno
- if callno.find(callsuffix) == -1:
- callno = callno + callsuffix
- if voltest:
- vol = int(voltest.group(1))
+ callno,vol = last_check_embed(callprefix,callno,callsuffix,voltest,vol)
except:
print "due date/call problem: ", bib_id
print "*** print_exc:"
return (vol, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues)
+ #get lists of barcodes and ids
+ bcs = make_obj_list(bclist)
+ ids = make_obj_list(idlist)
+
# At this point, status information does not require the opensrf
# bindings, I am not sure there is a use case where an evergreen
# site would not have access to these but will leave for now
version = getattr(settings, 'EVERGREEN_VERSION',
2.4)
- #TODO: clean this up, a hackish workaround for now
if version >= 2.1:
+ #this loop is needed in case there are multiple reserves locations
for org, prefix, callno, suffix, loc, stats in counts:
if len(prefix) > 0:
prefix += ' '
if len(suffix) > 0:
suffix = ' ' + suffix
vol, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues = sort_out_status(barcode, vol, counts,
- version, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues, prefix, suffix)
+ version, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues, prefix, suffix, bcs,ids)
else:
for org, callno, loc, stats in counts:
vol, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues = sort_out_status(barcode, vol, counts,
- version, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues)
+ version, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues, bcs,ids)
if len(allcalls) > 0:
- cpname = 'parts'
- if barcode is not None:
- cpname = 'part'
+ cpname = 'volumes'
return (cpname, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues)
mod = __import__(modname, fromlist=[''])
integration_class = getattr(mod, klassname)
+BIB_PART_MERGE = bool(getattr(settings, 'BIB_PART_MERGE', True))
+
#----------------------------------------------------------------------
class BaseModel(m.Model):
"""A list of all items which are headings."""
return self.item_set.filter(item_type='HEADING').order_by('title')
- def item_tree(self, subtree=None):
+ def item_tree_merge(self, edit_status=False):
+ return self.item_tree(None,edit_status)
+
+ def item_tree(self, subtree=None, edit_status=False):
"""
Return a list, representing a tree of the site items, in
display order. Every element of the list is an (Item, [Item])
return " ".join(words)
items = self.items()
+
# make a node-lookup table
dct = {}
for item in items:
# TODO: what's the sort order? - art weighing in on normalized title
lst.sort(key=lambda item: (item.item_type=='HEADING',
sort_title(item.title))) # sort in place
+
+ #has barcode already been dealt with
+ def is_collected(poss,barcodes):
+ for bc in barcodes:
+ if poss.barcode in bc:
+ return True
+ return False
+
+ #only concerned about duplicate physical items
+ def is_dup_candidate(poss):
+ if len(poss.bib_id) > 0:
+ if poss.item_type == 'PHYS':
+ if poss.barcode is not None:
+ return True
+ return False
+
+ #collect barcodes for dups
+ def deal_with_dups(item,items,edit_status,barcodes):
+ dup_barcodes = []
+ dup_ids = []
+ push_thru = True
+
+ if not BIB_PART_MERGE or edit_status:
+ return push_thru, dup_barcodes, dup_ids
+ if not is_dup_candidate(item):
+ return push_thru, dup_barcodes, dup_ids
+
+ if is_collected(item,barcodes):
+ return False, dup_barcodes, dup_ids
+
+ for display_item in items:
+ if is_dup_candidate(display_item):
+ if display_item.barcode != item.barcode:
+ if display_item.bib_id == item.bib_id and display_item.barcode not in dup_barcodes:
+ dup_barcodes.append(display_item.barcode)
+ dup_ids.append(display_item.id)
+
+
+ if len(dup_barcodes) > 0 and not item.barcode in dup_barcodes:
+ dup_barcodes.append(item.barcode)
+ dup_ids.append(item.id)
+
+ return push_thru, dup_barcodes, dup_ids
+
# walk the tree
out = []
+ out_barcodes = []
+ out_ids = []
def walk(parent, accum):
here = dct.get(parent, [])
for item in here:
sub = []
walk(item, sub)
- accum.append((item, sub))
+ push_thru, bib_barcodes, syrup_ids = deal_with_dups(item,items,edit_status,out_barcodes)
+ if len(bib_barcodes) > 0:
+ out_barcodes.append(bib_barcodes)
+ out_ids.append(syrup_ids)
+ if push_thru:
+ accum.append((item, sub, out_barcodes, out_ids))
walk(subtree, out)
return out
def describe_physical_item_status(self):
"""Return a (bool,str) tuple: whether the item is available,
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')
stat = callhook('item_status', self)
if not stat:
return (False, 'Status information not available.')
else:
cpname, lib, desk, avail, callno, dueinfo, circmod, allcalls, alldues = stat
return (avail > 0,
- '%d of %d %s available at reserves desk; '
- '%d total copies in library system'
- % (avail, desk, cpname, lib))
+ '%d of %d %s available at reserves desk; %d total copies in library system' %
+ (avail, desk, cpname, lib))
_video_type_re = re.compile(r'tag="007">v(.)')
_video_types = {'c':'videocartridge',