Skip to content

Instantly share code, notes, and snippets.

@lawlesst
Last active December 12, 2015 05:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lawlesst/4722068 to your computer and use it in GitHub Desktop.
Save lawlesst/4722068 to your computer and use it in GitHub Desktop.
Flask app for stackview.
import os
from flask import Flask, url_for
from flask import request, current_app
from flask import jsonify
from flask import render_template
from functools import wraps
from fetch_data import stackview_data, get_by_bib
app = Flask(__name__)
def jsonp(func):
"""
Taken from: https://gist.github.com/1094140
Wraps JSONified output for JSONP requests.
"""
@wraps(func)
def decorated_function(*args, **kwargs):
callback = request.args.get('callback', False)
if callback:
data = str(func(*args, **kwargs).data)
content = str(callback) + '(' + data + ')'
mimetype = 'application/javascript'
return current_app.response_class(content, mimetype=mimetype)
else:
return func(*args, **kwargs)
return decorated_function
@app.route('/', methods=['GET'])
@app.route('/<bib>', methods=['GET'])
def stack(bib=None):
if bib is not None:
record = get_by_bib(str(bib))
callnumber = record.get('callnumber')
#Add shelf location.
this_title = "%s -- %s" % (record.get('title'), callnumber)
else:
callnumber = str(request.args.get('cn', None))
return render_template(
'stackview.html',
callnumber=callnumber,
title=this_title
)
@app.route('/data', methods=['GET'])
@jsonp
def stack_view():
callnumber = str(request.args.get('query', None))
#import pdb; pdb.set_trace()
out = stackview_data(callnumber)
return jsonify(out)
if __name__ == '__main__':
if os.getenv('DEVBOX') == 'true':
app.run(
host='0.0.0.0',
debug=True
)
else:
app.run()
"""
Run Z3950 searches to get the items sorted by call number.
Example Stackview data objects.
{
"title": "Blankets",
"creator": [
"Craig Thompson"
],
"measurement_page_numeric": 582,
"measurement_height_numeric": 25,
"shelfrank": 13,
"pub_date": "2003",
"link": "http://holliscatalog.harvard.edu/?itemid=|library/m/aleph|009189638"
},
{
"title": "Persepolis",
"creator": [
"Marjane Satrapi"
],
"measurement_page_numeric": 153,
"measurement_height_numeric": 24,
"shelfrank": 64,
"pub_date": "2003",
"link": "http://holliscatalog.harvard.edu/?itemid=|library/m/aleph|009098946"
}
"""
import os
import sys
import re
from PyZ3950 import zoom
from PyZ3950.z3950 import ConnectionError
import pymarc
Z39_SERVER = {
'host': os.getenv('ZHOST'),
'port': int(os.getenv('ZPORT')),
'db': os.getenv('ZDB'),
'qsyntax': 'PQF',
'rsyntax': 'OPAC',
'element_set': 'F',
'charset': 'utf-8',
}
PUB_YEAR = re.compile('(?:2[0-9]0u|\?)?[0-9]{3}u|\?')
class Z39Result(object):
"""
Converts the Z39.50 result to a stackview response.
"""
def __init__(self, result_set):
self.result = result_set
def callnumber(self, record):
"""
Call number routine. Brown specific.
"""
call_number = None
if record['090']:
call_number = record['090'].format_field()
elif record['050']:
call_number = record['050'].format_field()
return {'callnumber': call_number}
def get_publication_date(self, record):
"""
Try to find pub date and convert to Python date object.
"""
from datetime import datetime, date
#=008 100315s2010\\\\it\a\\\\\b\\\\001\0\ita\\
o08 = record['008'].data
date1 = o08[7:11]
if PUB_YEAR.search(date1):
date1 = date1.replace('u', '0')
#Will convert to date object even though we will report
#just the year string. This will return null for 19uu
#fields which we don't want to appear in facets.
try:
date_obj = date(year=int(date1), month=1, day=1)
#Throw out bad data for years greater than two years in
#advance.
if date_obj.year > (date.today().year + 2):
return
except ValueError:
return
return str(date_obj.year)
def physical(self, bib):
"""
Try to assign a numeric page size.
Default to 250 pages.
"""
phys = bib['300']
try:
pages = phys['a']
if pages is not None:
try:
pages = int(re.findall(r'[0-9]+', pages)[0])
except IndexError:
pages = 250
else:
pages = 250
try:
height = int(re.findall(r'[0-9]+', phys['c'])[0])
except IndexError:
height = 10
if phys is None:
return {}
except TypeError:
pages = 250
height = 10
return {
"measurement_page_numeric": pages,
"measurement_height_numeric": height
}
def response(self):
results = {}
holdings_set = [1]
for rec in self.result:
#Read details into pymarc Record object.
try:
bib = pymarc.Record(data=rec.data.bibliographicRecord.encoding[1])
except AttributeError:
continue
results['title'] = bib.title()
#Assuming the record id is in 907 and follows III convention
results['id'] = bib['907']['a'].lstrip('.')[:8]
results['link'] = os.getenv('OPAC') + results['id']
results['pub_date'] = self.get_publication_date(bib)
results['callnumber'] = self.callnumber(bib)
au = bib.author()
if au:
creator = [au]
else:
creator = []
results['creator'] = creator
#Get physical description
results.update(self.physical(bib))
results.update(self.callnumber(bib))
#Get item count for shelfrank.
try:
holdings_set = rec.data.holdingsData
except AttributeError:
pass
results['items'] = len(holdings_set)
#Computer a shelfrank based on the number of items in
#the catalog.
results['shelfrank'] = self.shelfrank(results['items'])
return results
def shelfrank(self, item_count):
"""
Make a shelf rank based off the number
of items/copies held of a given title.
"""
if item_count == 1:
sr = 10
elif (item_count > 1) and (item_count < 3):
sr = 50
elif (item_count >= 3) and (item_count < 4):
sr = 70
else:
sr = 100
return sr
def stackview_data(call_number):
"""
Run a Z39.50 scan for a particular call number.
"""
server = Z39_SERVER
#Open Z39.50 connection
conn = zoom.Connection(
server['host'],
server['port'],
preferredRecordSyntax=server['rsyntax'],
elementSetName=server['element_set'],
databaseName=server['db'],
charset='utf-8',
numberOfEntries=int(os.getenv('STACK_SIZE')),
responsePosition=int(os.getenv('ITEM_POS'))
)
params = '@attr 1=16 "%s"' % call_number
query = zoom.Query(server['qsyntax'], params)
#Do a scan by call number
docs = []
results = conn.scan(query)
for count, bib in enumerate(results):
call = bib.get('term')[1]
csearch = '@attr 1=13 "%s"' % call
lookup = zoom.Query(server['qsyntax'], csearch)
result = conn.search(lookup)
r = Z39Result(result).response()
docs.append(r)
conn.close()
out = {"start": "-1", "limit": 50, "num_found": count}
out["docs"] = docs
return out
def get_by_bib(bib_number):
"""
Get the bibliographic data for a given id number.
"""
server = Z39_SERVER
#Open Z39.50 connection
conn = zoom.Connection(server['host'],
server['port'],
preferredRecordSyntax=server['rsyntax'],
elementSetName=server['element_set'],
databaseName=server['db'],
charset='utf-8',
)
params = '@attr 1=12 "%s"' % bib_number
query = zoom.Query(server['qsyntax'], params)
result = conn.search(query)
r = Z39Result(result).response()
conn.close()
return r
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0068)http://librarylab.law.harvard.edu/projects/stackview/demo/basic.html -->
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>StackView Basic</title>
<link rel="icon" href="http://librarylab.law.harvard.edu/projects/stackview/demo/favicon.ico" type="image/x-icon">
<!-- stackview.css to style the stack -->
<link rel="stylesheet" href="http://librarylab.law.harvard.edu/projects/stackview/demo/lib/jquery.stackview.css" type="text/css">
<!-- stackview.js and all js dependencies -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript" src="http://librarylab.law.harvard.edu/projects/stackview/demo/lib/jquery.stackview.min.js"></script>
</head>
<body>
<div id="stackview"></div>
<script type="text/javascript">
$(function () {
$('#stackview').stackView(
{
'url': './data',
'books_per_page': "1",
'start': "30",
'ribbon': "Stackview -- {{title}}",
'jsonp': true,
'search_type': 'cn',
'query': "{{callnumber}}",
//'search_type': 'loc_sort_order'
}
);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment