[open-ils-commits] r762 - in servres/trunk/conifer: . custom libsystems/sip libsystems/z3950 static static/jquery/js static/xslt syrup syrup/views templates templates/item (gfawcett)
svn at svn.open-ils.org
svn at svn.open-ils.org
Thu Jan 21 21:48:04 EST 2010
Author: gfawcett
Date: 2010-01-21 21:48:02 -0500 (Thu, 21 Jan 2010)
New Revision: 762
Modified:
servres/trunk/conifer/README
servres/trunk/conifer/custom/course_codes.py
servres/trunk/conifer/custom/lib_integration.py
servres/trunk/conifer/libsystems/sip/sipclient.py
servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
servres/trunk/conifer/settings.py
servres/trunk/conifer/static/edit_course.js
servres/trunk/conifer/static/jquery/js/jquery-ui-1.7.1.custom.min.js
servres/trunk/conifer/static/jquery/js/jquery.js
servres/trunk/conifer/static/main.css
servres/trunk/conifer/static/xslt/test.xsl
servres/trunk/conifer/syrup/urls.py
servres/trunk/conifer/syrup/views/general.py
servres/trunk/conifer/syrup/views/items.py
servres/trunk/conifer/templates/departments.xhtml
servres/trunk/conifer/templates/edit_course.xhtml
servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
servres/trunk/conifer/templates/master.xhtml
Log:
stripped out pesky Windows carriage returns
Modified: servres/trunk/conifer/README
===================================================================
--- servres/trunk/conifer/README 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/README 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,99 +1,99 @@
-Syrup: A Reserves application
-------------------------------
-
-For more information, see
-http://open-ils.org/dokuwiki/doku.php?id=scratchpad:reserves
-
-or contact
-Art Rhyno <artrhyno at uwindsor.ca>
-Graham Fawcett <graham.fawcett at gmail.com>
-
-
-State of the application
-------------------------------
-
-Coming along nicely, thank you! With a bit of patience, you ought to
-be able to get a basic Syrup system running in no time. Integrating it
-with your backend library and other systems will take longer, of
-course.
-
-Required components
-------------------------------
-
-You need Python. Probably Python 2.5, I haven't tested with other
-versions. You also need sqlite3 or another Django-compatible
-database. Sqlite3 is recommended for kicking the tires, PostgreSQL for
-production.
-
-Third-party Python dependencies:
-
- sudo easy_install Django Genshi Babel BabelDjango
-
-(You'll need 'setuptools' in order to have 'easy_install'.)
-
-Windows is very similar, see:
-
-http://groups.google.com/group/syrup-reserves-discuss/web/installing-syrup-in-windows
-
-Graham has the following versions installed. Not saying you need these
-exact ones, just that they are known to work.
-
- Django-1.0.1_final-py2.5
- Babel-0.9.4-py2.5
- BabelDjango-0.2.2-py2.5
- Genshi-0.5.1-py2.5
-
-Getting this thing to run
-------------------------------
-
-This might work:
-
-* Review settings.py. Maybe you need to edit stuff. Probably not for a
- quick test.
-
-* ./manage.py syncdb
-
-Note: don't use the "./" syntax in windows for the commands, e.g.:
-
-C:\src\syrup\trunk\conifer>manage.py syncdb
-
-* During syncdb, create yourself a superuser account.
-
-* ./pybabel-extract (currently, this is optional)
-
-* ./manage.py runserver
-
-* visit http://localhost:8000/ and log in.
-
-* create at least one Term and one Department under Admin Options.
-
-* make yourself a course.
-
-* click on all of the links and see what they do.
-
-
-Contents [out of date -- Ed.]
-------------------------------
-
-syrup/ -- the reserves app
-middleware/ -- middleware component to integrate Genshi
-locale/ -- the gettext files
-templates/ -- the Genshi templates
-static/ -- static JS, CSS, image files
-doc/ -- documentation on the app
-
-local_settings.py.in -- a template for local_settings.py
-genshi_support.py -- Genshi template integration
-pybabel-extract -- a "make all" for the i18n files
-babel.cfg -- Babel (i18n) configuration file
-
-The rest is straightforward Django stuff.
-
-
-Customization
-------------------------------
-
-The 'custom' directory contains (or should contain!) all of the bits
-that you really need to customize for your institution. More
-documentation is needed here, but the source code is mostly
+Syrup: A Reserves application
+------------------------------
+
+For more information, see
+http://open-ils.org/dokuwiki/doku.php?id=scratchpad:reserves
+
+or contact
+Art Rhyno <artrhyno at uwindsor.ca>
+Graham Fawcett <graham.fawcett at gmail.com>
+
+
+State of the application
+------------------------------
+
+Coming along nicely, thank you! With a bit of patience, you ought to
+be able to get a basic Syrup system running in no time. Integrating it
+with your backend library and other systems will take longer, of
+course.
+
+Required components
+------------------------------
+
+You need Python. Probably Python 2.5, I haven't tested with other
+versions. You also need sqlite3 or another Django-compatible
+database. Sqlite3 is recommended for kicking the tires, PostgreSQL for
+production.
+
+Third-party Python dependencies:
+
+ sudo easy_install Django Genshi Babel BabelDjango
+
+(You'll need 'setuptools' in order to have 'easy_install'.)
+
+Windows is very similar, see:
+
+http://groups.google.com/group/syrup-reserves-discuss/web/installing-syrup-in-windows
+
+Graham has the following versions installed. Not saying you need these
+exact ones, just that they are known to work.
+
+ Django-1.0.1_final-py2.5
+ Babel-0.9.4-py2.5
+ BabelDjango-0.2.2-py2.5
+ Genshi-0.5.1-py2.5
+
+Getting this thing to run
+------------------------------
+
+This might work:
+
+* Review settings.py. Maybe you need to edit stuff. Probably not for a
+ quick test.
+
+* ./manage.py syncdb
+
+Note: don't use the "./" syntax in windows for the commands, e.g.:
+
+C:\src\syrup\trunk\conifer>manage.py syncdb
+
+* During syncdb, create yourself a superuser account.
+
+* ./pybabel-extract (currently, this is optional)
+
+* ./manage.py runserver
+
+* visit http://localhost:8000/ and log in.
+
+* create at least one Term and one Department under Admin Options.
+
+* make yourself a course.
+
+* click on all of the links and see what they do.
+
+
+Contents [out of date -- Ed.]
+------------------------------
+
+syrup/ -- the reserves app
+middleware/ -- middleware component to integrate Genshi
+locale/ -- the gettext files
+templates/ -- the Genshi templates
+static/ -- static JS, CSS, image files
+doc/ -- documentation on the app
+
+local_settings.py.in -- a template for local_settings.py
+genshi_support.py -- Genshi template integration
+pybabel-extract -- a "make all" for the i18n files
+babel.cfg -- Babel (i18n) configuration file
+
+The rest is straightforward Django stuff.
+
+
+Customization
+------------------------------
+
+The 'custom' directory contains (or should contain!) all of the bits
+that you really need to customize for your institution. More
+documentation is needed here, but the source code is mostly
well-documented.
\ No newline at end of file
Modified: servres/trunk/conifer/custom/course_codes.py
===================================================================
--- servres/trunk/conifer/custom/course_codes.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/custom/course_codes.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,140 +1,140 @@
-# 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]))
-
+# 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]))
+
Modified: servres/trunk/conifer/custom/lib_integration.py
===================================================================
--- servres/trunk/conifer/custom/lib_integration.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/custom/lib_integration.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,98 +1,98 @@
-# 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_dictionary
-
-
- 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_dictionary(I.url_to_marcxml(query), multiples=True)
- 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
+# 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_dictionary
+
+
+ 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_dictionary(I.url_to_marcxml(query), multiples=True)
+ 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
Modified: servres/trunk/conifer/libsystems/sip/sipclient.py
===================================================================
--- servres/trunk/conifer/libsystems/sip/sipclient.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/libsystems/sip/sipclient.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,569 +1,569 @@
-# Small portions are borrowed from David Fiander's acstest.py, in the
-# openncip project. David's license is below:
-
-# Copyright (C) 2006-2008 Georgia Public Library Service
-#
-# Author: David J. Fiander
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of version 2 of the GNU General Public
-# License as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public
-# License along with this program; if not, write to the Free
-# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-# MA 02111-1307 USA
-
-
-from sipconstants import *
-import socket
-import sys
-from datetime import datetime
-import re
-
-DEBUG = True
-
-# ------------------------------------------------------------
-# helper functions
-
-def split_n(n):
- """Return a function that splits a string into two parts at index N."""
- return lambda s: (s[:n], s[n:])
-
-split2 = split_n(2)
-
-
-
-
-# ------------------------------------------------------------
-# Messages
-
-# First we build up a little language for defining SIP messages, so
-# that we can define the protocol in a declarative style.
-
-
-class basefield(object):
-
- def encode(self, dct):
- """Take a dict, and return the wire representation of this field."""
- raise NotImplementedError, repr(self)
-
- def decode(self, bytes):
- """
- Take a wire representation and return a pair (V,R) where V is
- the translated value of the current field, and R is the
- remaining bytes after the field has been read. If this is an
- optional field, then decode should return None for V, and
- return the input bytes for R.
- """
- raise NotImplementedError, repr(self)
-
-
-class field(basefield):
-
- def __init__(self, name, code, width=None):
- self.name = name
- self.code = code
- self.width = None # don't use this yet.
-
- def encode(self, dct):
- return '%s%s|' % (self.code, dct.get(self.name, ''))
-
- def decode(self, bytes):
- bcode, rest = split2(bytes)
- if bcode != self.code:
- raise 'BadDecode', \
- 'Wrong field! Expected %r (%s) got %r (%s), in %r.' % (
- self.code, lookup_constant(self.code),
- bcode, lookup_constant(bcode),
- bytes)
- data, rest = rest.split('|', 1)
- return data, rest
-
-
-class optfield(field): # an optional field
-
- def decode(self, bytes):
- tmp = bytes + ' '
- bcode, rest = split2(tmp)
- if bcode == self.code:
- return field.decode(self, bytes)
- else:
- return None, bytes
-
-
-class charfield(basefield):
-
- def __init__(self, name, width=None, default=None):
- self.name = name
- self.dflt = str(default)
- self.width = width or len(self.dflt) # give at least one
- self.pad = ' ' * self.width
-
- self.decode = split_n(self.width)
-
- def encode(self, dct):
- v = dct.get(self.name, self.dflt)
- assert v is not None
- return ('%s%s' % (self.pad, v))[-self.width:]
-
-
-class yn(basefield):
- def __init__(self, name):
- self.name = name
-
- def encode(self, dct):
- return 'NY'[bool(dct.get(self.name))]
-
- def decode(self, bytes):
- return (bytes[0] == 'Y'), bytes[1:]
-
-
-class localtime(charfield):
- def __init__(self, name):
- self.name = name
- self.width = 18
-
- def encode(self, dct):
- return datetime.now().strftime('%Y%m%d %H%M%S')
-
- def decode(self, bytes):
- return split_n(self.width)(bytes)
-
-RAW = -55
-class raw(basefield):
- name = 'raw'
- # for debugging.
- def decode(self, bytes):
- return bytes, '\r'
-
-# We define a protocol Message as a list of fields. For now,
-# message(A, B, C) is equivalent to the tuple (A,B,C).
-
-message = lambda *args: args
-
-# Encoding a message on to the wire. Args is a dict of field-values.
-
-def encode_msg(msg, args):
- out = []
- add = out.append
- for thing in msg:
- if isinstance(thing, basefield):
- add(thing.encode(args))
- else:
- add(str(thing))
- return ''.join(out)
-
-# Decoding from the wire:
-
-def decode_msg(msg, bytes):
- out = {}
- add = out.__setitem__
- rest = bytes
-
- # Proper 'fields' have variable position in the tail of the
- # message. So we treat them differently.
- varposn = set([p for p in msg if isinstance(p, field)])
- varlookup = dict((x.code, x) for x in varposn)
- fixedposn = [p for p in msg if not p in varposn]
-
- for part in fixedposn:
- if isinstance(part, basefield):
- good, rest = part.decode(rest)
- if good is not None:
- add(part.name, good)
- else:
- v = str(part)
- good, rest = rest[:len(v)], rest[len(v):]
- assert v == good
- if DEBUG: print '%s == %r\n==== %r' % (getattr(part, 'name',''), good, rest)
-
- # Now we take what's left, chunk it, and try to resolve each one
- # against a variable-position field.
- segments = re.findall(r'(.*?\|)', rest)
-
- if DEBUG: print segments
-
- for segment in segments:
- fld = varlookup.get(segment[:2])
- if fld:
- good, rest = fld.decode(segment)
- add(fld.name, good)
- varposn.remove(fld)
- else:
- raise 'FieldNotProcessed: %s, %s' % (segment, lookup_constant(segment[:2]))
-
- # Let's make sure that any "required" fields were not missing.
- notpresent = set(f for f in varposn if not isinstance(f, optfield))
- if notpresent:
- for f in notpresent:
- print 'MISSING: %-12s %s %s' % (f.name, f.code, lookup_constant(f.code))
- raise 'MandatoryFieldsNotPresent'
-
- return out
-
-# The SIP checksum. Borrowed from djfiander.
-
-def checksum(msg):
- return '%04X' % ((0 - sum(map(ord, msg))) & 0xFFFF)
-
-
-#------------------------------------------------------------
-# SIP Message Definitions
-
-# some common fields
-
-
-fld_localtime = localtime('localtime')
-fld_INST_ID = field('inst', FID_INST_ID)
-fld_ITEM_ID = field('item', FID_ITEM_ID)
-fld_PATRON_ID = field('patron', FID_PATRON_ID)
-ofld_TERMINAL_PWD = optfield('termpwd', FID_TERMINAL_PWD)
-fld_proto_version = charfield('version', default='2.00')
-ofld_print_line = optfield('print_line', FID_PRINT_LINE)
-ofld_screen_msg = optfield('screenmsg', FID_SCREEN_MSG)
-
-MESSAGES = {
- LOGIN : message(
- LOGIN,
- '00',
- field('uid', FID_LOGIN_UID),
- field('pwd', FID_LOGIN_PWD),
- field('locn', FID_LOCATION_CODE)),
-
- LOGIN_RESP : message(
- LOGIN_RESP,
- charfield('ok', width=1)),
-
- SC_STATUS : message(
- SC_STATUS,
- charfield('online', default='1'),
- charfield('width', default='040'),
- fld_proto_version),
-
- ACS_STATUS : message(
- ACS_STATUS,
- yn('online'),
- yn('checkin_OK'),
- yn('checkout_OK'),
- yn('renewal_OK'),
- yn('status_update_OK'),
- yn('offline_OK'),
- charfield('timeout', default='01'),
- charfield('retries', default='9999'),
- fld_localtime,
- charfield('protocol', default='2.00'),
- fld_INST_ID,
- optfield('patron_id', FID_PATRON_ID),
- optfield('item_id', FID_ITEM_ID),
- optfield('terminal_pwd', FID_TERMINAL_PWD),
-
- optfield('instname', FID_LIBRARY_NAME),
- field('supported', FID_SUPPORTED_MSGS),
- optfield('ttylocn', FID_TERMINAL_LOCN),
- ofld_screen_msg,
- ofld_print_line),
- PATRON_INFO : message(
- PATRON_INFO,
- charfield('lang', width=3, default=1),
- fld_localtime,
- charfield('holditemsreq', default='Y '),
- fld_INST_ID,
- fld_PATRON_ID,
- ofld_TERMINAL_PWD,
- optfield('patronpwd', FID_PATRON_PWD),
- optfield('startitem', FID_START_ITEM, width=5),
- optfield('enditem', FID_END_ITEM, width=5)),
-
- PATRON_INFO_RESP : message(
- PATRON_INFO_RESP,
- charfield('hmmm', width=14),
- charfield('lang', width=3, default=1),
- fld_localtime,
- charfield('onhold', width=4),
- charfield('overdue', width=4),
- charfield('charged', width=4),
- charfield('fine', width=4),
- charfield('recall', width=4),
- charfield('unavail_holds', width=4),
- fld_INST_ID,
- ofld_screen_msg,
- ofld_print_line,
- optfield('instname', FID_LIBRARY_NAME),
- fld_PATRON_ID,
- field('personal', FID_PERSONAL_NAME),
-
- optfield('hold_limit', FID_HOLD_ITEMS_LMT, width=4),
- optfield('overdue_limit', FID_OVERDUE_ITEMS_LMT, width=4),
- optfield('charged_limit', FID_OVERDUE_ITEMS_LMT, width=4),
-
- optfield('hold_items', FID_HOLD_ITEMS),
- optfield('valid_patron_pwd', FID_VALID_PATRON_PWD),
-
- optfield('valid_patron', FID_VALID_PATRON),
- optfield('currency', FID_CURRENCY),
- optfield('fee_amt', FID_FEE_AMT),
- optfield('fee_limit', FID_FEE_LMT),
- optfield('home_addr', FID_HOME_ADDR),
- optfield('email', FID_EMAIL),
- optfield('home_phone', FID_HOME_PHONE),
- optfield('patron_birthdate', FID_PATRON_BIRTHDATE),
- optfield('patron_class', FID_PATRON_CLASS),
- optfield('inet_profile', FID_INET_PROFILE),
- optfield('home_library', FID_HOME_LIBRARY)),
-
- END_PATRON_SESSION : message(
- END_PATRON_SESSION,
- fld_localtime,
- field('inst', FID_INST_ID),
- field('patron', FID_PATRON_ID)),
-
- END_SESSION_RESP : message(
- END_SESSION_RESP,
- yn('session_ended'),
- fld_localtime,
- fld_INST_ID,
- fld_PATRON_ID,
- ofld_print_line,
- ofld_screen_msg),
-
- CHECKOUT: message(
- CHECKOUT,
- yn('renewals_OK'),
- yn('no_block'),
- fld_localtime,
- fld_localtime,
- field('inst', FID_INST_ID),
- field('patron', FID_PATRON_ID),
- field('item', FID_ITEM_ID),
- ),
-
- CHECKOUT_RESP: message(
- CHECKOUT_RESP,
- charfield('ok', width=1),
- yn('is_renewal'),
- yn('is_magnetic'),
- yn('desensitize'),
- fld_localtime,
- field('inst', FID_INST_ID),
- field('patron', FID_PATRON_ID),
- field('item', FID_ITEM_ID),
- field('due', FID_DUE_DATE),
- field('title', FID_TITLE_ID),
- optfield('media_type_code', FID_MEDIA_TYPE),
- optfield('is_valid_patron', FID_VALID_PATRON),
- ofld_print_line,
- ofld_screen_msg),
-
- CHECKIN: message(
- CHECKIN,
- yn('is_retry'),
- fld_localtime,
- fld_localtime,
- field('item', FID_ITEM_ID),
- field('location', FID_CURRENT_LOCN),
- field('inst', FID_INST_ID),
- ofld_TERMINAL_PWD,
- ),
-
- CHECKIN_RESP: message(
- CHECKIN_RESP,
- charfield('ok', width=1),
- yn('resensitize'),
- yn('is_magnetic'),
- yn('alert'),
- fld_localtime,
- fld_INST_ID,
- optfield('patron', FID_PATRON_ID),
- field('item', FID_ITEM_ID),
- field('title', FID_TITLE_ID),
- optfield('media_type_code', FID_MEDIA_TYPE),
- optfield('perm_locn', FID_PERM_LOCN),
- optfield('due', FID_DUE_DATE),
- ofld_print_line,
- ofld_screen_msg,
- ),
-# yn('is_retry'),
-# fld_localtime,
-# fld_localtime,
-# field('item', FID_ITEM_ID),
-# field('location', FID_CURRENT_LOCN),
-# ofld_TERMINAL_PWD,
-# ),
-
- ITEM_INFORMATION : message(
- ITEM_INFORMATION,
- fld_localtime,
- fld_INST_ID,
- fld_ITEM_ID,
- ofld_TERMINAL_PWD),
-
- ITEM_INFO_RESP : message(
- ITEM_INFO_RESP,
- charfield('circstat', width=2),
- charfield('security', width=2),
- charfield('feetype', width=2),
- fld_localtime,
- fld_ITEM_ID,
- field('title', FID_TITLE_ID),
- optfield('mediatype', FID_MEDIA_TYPE),
- optfield('perm_locn', FID_PERM_LOCN),
- optfield('current_locn', FID_CURRENT_LOCN),
- optfield('item_props', FID_ITEM_PROPS),
- optfield('currency', FID_CURRENCY),
- optfield('fee', FID_FEE_AMT),
- optfield('owner', FID_OWNER),
- optfield('hold_queue_len', FID_HOLD_QUEUE_LEN),
- optfield('due_date', FID_DUE_DATE),
-
- optfield('recall_date', FID_RECALL_DATE),
- optfield('hold_pickup_date', FID_HOLD_PICKUP_DATE),
- ofld_screen_msg,
- ofld_print_line),
-
- RAW : message(raw()),
-}
-
-
-class SipClient(object):
- def __init__(self, host, port, error_detect=False):
- self.hostport = (host, port)
- self.error_detect = error_detect
- self.connect()
-
- def connect(self):
- so = socket.socket()
- so.connect(self.hostport)
- self.socket = so
- self.seqno = self.error_detect and 1 or 0
-
- def close(self):
- # fixme, do SIP close first.
- self.socket.close()
-
- def send(self, outmsg, inmsg, args=None):
- msg_template = MESSAGES[outmsg]
- resp_template = MESSAGES[inmsg]
- msg = encode_msg(msg_template, args or {})
- if self.error_detect:
- # add the checksum
- msg += 'AY%dAZ' % (self.seqno % 10)
- self.seqno += 1
- msg += checksum(msg)
- msg += '\r'
- if DEBUG: print '>>> %r' % msg
- self.socket.send(msg)
- resp = self.socket.recv(1000)
- if DEBUG: print '<<< %r' % resp
- return decode_msg(resp_template, resp)
-
-
- # --------------------------------------------------
- # Common protocol methods
-
- def login(self, uid, pwd, locn):
- msg = self.send(LOGIN, LOGIN_RESP,
- dict(uid=uid, pwd=pwd, locn=locn))
- return msg.get('ok') == '1'
-
- def status(self):
- return self.send(SC_STATUS, ACS_STATUS)
-
- def patron_info(self, barcode):
- msg = self.send(PATRON_INFO,PATRON_INFO_RESP,
- {'patron':barcode,
- 'startitem':1, 'enditem':2})
- # fixme, this may not be the best test of okayness
- msg['success'] = msg.get('valid_patron') == 'Y'
- return msg
-
- def checkout(self, patron, item, inst=''):
- msg = self.send(CHECKOUT, CHECKOUT_RESP,
- {'patron':patron,
- 'inst': inst,
- 'item':item})
- msg['media_type'] = MEDIA_TYPE_TABLE.get(msg.get('media_type_code'))
- msg['success'] = msg.get('ok') == '1'
- return msg
-
- def checkin(self, item, institution='', location=''):
- msg = self.send(CHECKIN, CHECKIN_RESP,
- {'inst': institution,
- 'location':location,
- 'is_retry':False,
- 'item':item})
- msg['success'] = msg.get('ok') == '1'
- return msg
-
- def item_info(self, barcode):
- print("starting")
- msg = self.send(ITEM_INFORMATION, ITEM_INFO_RESP,
- {'item':barcode})
- print(msg['circstat'])
- msg['available'] = msg['circstat'] == '03'
- msg['status'] = ITEM_STATUS_TABLE[msg['circstat']]
- return msg
-
-
-# ------------------------------------------------------------
-# Django stuff. Optional.
-
-try:
- from django.conf import settings
- def sip_connection():
- sip = SipClient(*settings.SIP_HOST)
- if not sip.login(*settings.SIP_CREDENTIALS):
- raise 'SipLoginError'
- return sip
-
- # decorator
- def SIP(fn):
- def f(*args, **kwargs):
- conn = sip_connection()
- resp = fn(conn, *args, **kwargs)
- conn.close()
- return resp
- return f
-
-except ImportError:
- pass
-
-
-# ------------------------------------------------------------
-# Test code.
-
-if __name__ == '__main__':
- from pprint import pprint
-
- sip = SipClient('comet.cs.uoguelph.ca', 8080)
- resp = sip.login(uid='test',
- pwd='test', locn='test')
- pprint(resp)
- pprint(sip.status())
-
- pprint(sip.send(PATRON_INFO, PATRON_INFO_RESP,
- {'patron':'scclient',
- 'startitem':1, 'enditem':2}))
-
- # these are items from openncip's test database.
- item_ids = ['1565921879', '0440242746', '660']
- bad_ids = ['xx' + i for i in item_ids]
- for item in (item_ids + bad_ids):
- result = sip.send(ITEM_INFORMATION, ITEM_INFO_RESP,
- {'item':item})
- print '%-12s: %s' % (item, result['title'] or '????')
- print sip.send(CHECKOUT, RAW,
- {'patron':'scclient-2',
- 'inst': 'UWOLS',
- 'item':item})
- print '\n' * 5
- pprint(sip.send(END_PATRON_SESSION, END_SESSION_RESP,
- {'patron':'scclient',
- 'inst':'UWOLS'}))
-
-
+# Small portions are borrowed from David Fiander's acstest.py, in the
+# openncip project. David's license is below:
+
+# Copyright (C) 2006-2008 Georgia Public Library Service
+#
+# Author: David J. Fiander
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free
+# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+
+
+from sipconstants import *
+import socket
+import sys
+from datetime import datetime
+import re
+
+DEBUG = True
+
+# ------------------------------------------------------------
+# helper functions
+
+def split_n(n):
+ """Return a function that splits a string into two parts at index N."""
+ return lambda s: (s[:n], s[n:])
+
+split2 = split_n(2)
+
+
+
+
+# ------------------------------------------------------------
+# Messages
+
+# First we build up a little language for defining SIP messages, so
+# that we can define the protocol in a declarative style.
+
+
+class basefield(object):
+
+ def encode(self, dct):
+ """Take a dict, and return the wire representation of this field."""
+ raise NotImplementedError, repr(self)
+
+ def decode(self, bytes):
+ """
+ Take a wire representation and return a pair (V,R) where V is
+ the translated value of the current field, and R is the
+ remaining bytes after the field has been read. If this is an
+ optional field, then decode should return None for V, and
+ return the input bytes for R.
+ """
+ raise NotImplementedError, repr(self)
+
+
+class field(basefield):
+
+ def __init__(self, name, code, width=None):
+ self.name = name
+ self.code = code
+ self.width = None # don't use this yet.
+
+ def encode(self, dct):
+ return '%s%s|' % (self.code, dct.get(self.name, ''))
+
+ def decode(self, bytes):
+ bcode, rest = split2(bytes)
+ if bcode != self.code:
+ raise 'BadDecode', \
+ 'Wrong field! Expected %r (%s) got %r (%s), in %r.' % (
+ self.code, lookup_constant(self.code),
+ bcode, lookup_constant(bcode),
+ bytes)
+ data, rest = rest.split('|', 1)
+ return data, rest
+
+
+class optfield(field): # an optional field
+
+ def decode(self, bytes):
+ tmp = bytes + ' '
+ bcode, rest = split2(tmp)
+ if bcode == self.code:
+ return field.decode(self, bytes)
+ else:
+ return None, bytes
+
+
+class charfield(basefield):
+
+ def __init__(self, name, width=None, default=None):
+ self.name = name
+ self.dflt = str(default)
+ self.width = width or len(self.dflt) # give at least one
+ self.pad = ' ' * self.width
+
+ self.decode = split_n(self.width)
+
+ def encode(self, dct):
+ v = dct.get(self.name, self.dflt)
+ assert v is not None
+ return ('%s%s' % (self.pad, v))[-self.width:]
+
+
+class yn(basefield):
+ def __init__(self, name):
+ self.name = name
+
+ def encode(self, dct):
+ return 'NY'[bool(dct.get(self.name))]
+
+ def decode(self, bytes):
+ return (bytes[0] == 'Y'), bytes[1:]
+
+
+class localtime(charfield):
+ def __init__(self, name):
+ self.name = name
+ self.width = 18
+
+ def encode(self, dct):
+ return datetime.now().strftime('%Y%m%d %H%M%S')
+
+ def decode(self, bytes):
+ return split_n(self.width)(bytes)
+
+RAW = -55
+class raw(basefield):
+ name = 'raw'
+ # for debugging.
+ def decode(self, bytes):
+ return bytes, '\r'
+
+# We define a protocol Message as a list of fields. For now,
+# message(A, B, C) is equivalent to the tuple (A,B,C).
+
+message = lambda *args: args
+
+# Encoding a message on to the wire. Args is a dict of field-values.
+
+def encode_msg(msg, args):
+ out = []
+ add = out.append
+ for thing in msg:
+ if isinstance(thing, basefield):
+ add(thing.encode(args))
+ else:
+ add(str(thing))
+ return ''.join(out)
+
+# Decoding from the wire:
+
+def decode_msg(msg, bytes):
+ out = {}
+ add = out.__setitem__
+ rest = bytes
+
+ # Proper 'fields' have variable position in the tail of the
+ # message. So we treat them differently.
+ varposn = set([p for p in msg if isinstance(p, field)])
+ varlookup = dict((x.code, x) for x in varposn)
+ fixedposn = [p for p in msg if not p in varposn]
+
+ for part in fixedposn:
+ if isinstance(part, basefield):
+ good, rest = part.decode(rest)
+ if good is not None:
+ add(part.name, good)
+ else:
+ v = str(part)
+ good, rest = rest[:len(v)], rest[len(v):]
+ assert v == good
+ if DEBUG: print '%s == %r\n==== %r' % (getattr(part, 'name',''), good, rest)
+
+ # Now we take what's left, chunk it, and try to resolve each one
+ # against a variable-position field.
+ segments = re.findall(r'(.*?\|)', rest)
+
+ if DEBUG: print segments
+
+ for segment in segments:
+ fld = varlookup.get(segment[:2])
+ if fld:
+ good, rest = fld.decode(segment)
+ add(fld.name, good)
+ varposn.remove(fld)
+ else:
+ raise 'FieldNotProcessed: %s, %s' % (segment, lookup_constant(segment[:2]))
+
+ # Let's make sure that any "required" fields were not missing.
+ notpresent = set(f for f in varposn if not isinstance(f, optfield))
+ if notpresent:
+ for f in notpresent:
+ print 'MISSING: %-12s %s %s' % (f.name, f.code, lookup_constant(f.code))
+ raise 'MandatoryFieldsNotPresent'
+
+ return out
+
+# The SIP checksum. Borrowed from djfiander.
+
+def checksum(msg):
+ return '%04X' % ((0 - sum(map(ord, msg))) & 0xFFFF)
+
+
+#------------------------------------------------------------
+# SIP Message Definitions
+
+# some common fields
+
+
+fld_localtime = localtime('localtime')
+fld_INST_ID = field('inst', FID_INST_ID)
+fld_ITEM_ID = field('item', FID_ITEM_ID)
+fld_PATRON_ID = field('patron', FID_PATRON_ID)
+ofld_TERMINAL_PWD = optfield('termpwd', FID_TERMINAL_PWD)
+fld_proto_version = charfield('version', default='2.00')
+ofld_print_line = optfield('print_line', FID_PRINT_LINE)
+ofld_screen_msg = optfield('screenmsg', FID_SCREEN_MSG)
+
+MESSAGES = {
+ LOGIN : message(
+ LOGIN,
+ '00',
+ field('uid', FID_LOGIN_UID),
+ field('pwd', FID_LOGIN_PWD),
+ field('locn', FID_LOCATION_CODE)),
+
+ LOGIN_RESP : message(
+ LOGIN_RESP,
+ charfield('ok', width=1)),
+
+ SC_STATUS : message(
+ SC_STATUS,
+ charfield('online', default='1'),
+ charfield('width', default='040'),
+ fld_proto_version),
+
+ ACS_STATUS : message(
+ ACS_STATUS,
+ yn('online'),
+ yn('checkin_OK'),
+ yn('checkout_OK'),
+ yn('renewal_OK'),
+ yn('status_update_OK'),
+ yn('offline_OK'),
+ charfield('timeout', default='01'),
+ charfield('retries', default='9999'),
+ fld_localtime,
+ charfield('protocol', default='2.00'),
+ fld_INST_ID,
+ optfield('patron_id', FID_PATRON_ID),
+ optfield('item_id', FID_ITEM_ID),
+ optfield('terminal_pwd', FID_TERMINAL_PWD),
+
+ optfield('instname', FID_LIBRARY_NAME),
+ field('supported', FID_SUPPORTED_MSGS),
+ optfield('ttylocn', FID_TERMINAL_LOCN),
+ ofld_screen_msg,
+ ofld_print_line),
+ PATRON_INFO : message(
+ PATRON_INFO,
+ charfield('lang', width=3, default=1),
+ fld_localtime,
+ charfield('holditemsreq', default='Y '),
+ fld_INST_ID,
+ fld_PATRON_ID,
+ ofld_TERMINAL_PWD,
+ optfield('patronpwd', FID_PATRON_PWD),
+ optfield('startitem', FID_START_ITEM, width=5),
+ optfield('enditem', FID_END_ITEM, width=5)),
+
+ PATRON_INFO_RESP : message(
+ PATRON_INFO_RESP,
+ charfield('hmmm', width=14),
+ charfield('lang', width=3, default=1),
+ fld_localtime,
+ charfield('onhold', width=4),
+ charfield('overdue', width=4),
+ charfield('charged', width=4),
+ charfield('fine', width=4),
+ charfield('recall', width=4),
+ charfield('unavail_holds', width=4),
+ fld_INST_ID,
+ ofld_screen_msg,
+ ofld_print_line,
+ optfield('instname', FID_LIBRARY_NAME),
+ fld_PATRON_ID,
+ field('personal', FID_PERSONAL_NAME),
+
+ optfield('hold_limit', FID_HOLD_ITEMS_LMT, width=4),
+ optfield('overdue_limit', FID_OVERDUE_ITEMS_LMT, width=4),
+ optfield('charged_limit', FID_OVERDUE_ITEMS_LMT, width=4),
+
+ optfield('hold_items', FID_HOLD_ITEMS),
+ optfield('valid_patron_pwd', FID_VALID_PATRON_PWD),
+
+ optfield('valid_patron', FID_VALID_PATRON),
+ optfield('currency', FID_CURRENCY),
+ optfield('fee_amt', FID_FEE_AMT),
+ optfield('fee_limit', FID_FEE_LMT),
+ optfield('home_addr', FID_HOME_ADDR),
+ optfield('email', FID_EMAIL),
+ optfield('home_phone', FID_HOME_PHONE),
+ optfield('patron_birthdate', FID_PATRON_BIRTHDATE),
+ optfield('patron_class', FID_PATRON_CLASS),
+ optfield('inet_profile', FID_INET_PROFILE),
+ optfield('home_library', FID_HOME_LIBRARY)),
+
+ END_PATRON_SESSION : message(
+ END_PATRON_SESSION,
+ fld_localtime,
+ field('inst', FID_INST_ID),
+ field('patron', FID_PATRON_ID)),
+
+ END_SESSION_RESP : message(
+ END_SESSION_RESP,
+ yn('session_ended'),
+ fld_localtime,
+ fld_INST_ID,
+ fld_PATRON_ID,
+ ofld_print_line,
+ ofld_screen_msg),
+
+ CHECKOUT: message(
+ CHECKOUT,
+ yn('renewals_OK'),
+ yn('no_block'),
+ fld_localtime,
+ fld_localtime,
+ field('inst', FID_INST_ID),
+ field('patron', FID_PATRON_ID),
+ field('item', FID_ITEM_ID),
+ ),
+
+ CHECKOUT_RESP: message(
+ CHECKOUT_RESP,
+ charfield('ok', width=1),
+ yn('is_renewal'),
+ yn('is_magnetic'),
+ yn('desensitize'),
+ fld_localtime,
+ field('inst', FID_INST_ID),
+ field('patron', FID_PATRON_ID),
+ field('item', FID_ITEM_ID),
+ field('due', FID_DUE_DATE),
+ field('title', FID_TITLE_ID),
+ optfield('media_type_code', FID_MEDIA_TYPE),
+ optfield('is_valid_patron', FID_VALID_PATRON),
+ ofld_print_line,
+ ofld_screen_msg),
+
+ CHECKIN: message(
+ CHECKIN,
+ yn('is_retry'),
+ fld_localtime,
+ fld_localtime,
+ field('item', FID_ITEM_ID),
+ field('location', FID_CURRENT_LOCN),
+ field('inst', FID_INST_ID),
+ ofld_TERMINAL_PWD,
+ ),
+
+ CHECKIN_RESP: message(
+ CHECKIN_RESP,
+ charfield('ok', width=1),
+ yn('resensitize'),
+ yn('is_magnetic'),
+ yn('alert'),
+ fld_localtime,
+ fld_INST_ID,
+ optfield('patron', FID_PATRON_ID),
+ field('item', FID_ITEM_ID),
+ field('title', FID_TITLE_ID),
+ optfield('media_type_code', FID_MEDIA_TYPE),
+ optfield('perm_locn', FID_PERM_LOCN),
+ optfield('due', FID_DUE_DATE),
+ ofld_print_line,
+ ofld_screen_msg,
+ ),
+# yn('is_retry'),
+# fld_localtime,
+# fld_localtime,
+# field('item', FID_ITEM_ID),
+# field('location', FID_CURRENT_LOCN),
+# ofld_TERMINAL_PWD,
+# ),
+
+ ITEM_INFORMATION : message(
+ ITEM_INFORMATION,
+ fld_localtime,
+ fld_INST_ID,
+ fld_ITEM_ID,
+ ofld_TERMINAL_PWD),
+
+ ITEM_INFO_RESP : message(
+ ITEM_INFO_RESP,
+ charfield('circstat', width=2),
+ charfield('security', width=2),
+ charfield('feetype', width=2),
+ fld_localtime,
+ fld_ITEM_ID,
+ field('title', FID_TITLE_ID),
+ optfield('mediatype', FID_MEDIA_TYPE),
+ optfield('perm_locn', FID_PERM_LOCN),
+ optfield('current_locn', FID_CURRENT_LOCN),
+ optfield('item_props', FID_ITEM_PROPS),
+ optfield('currency', FID_CURRENCY),
+ optfield('fee', FID_FEE_AMT),
+ optfield('owner', FID_OWNER),
+ optfield('hold_queue_len', FID_HOLD_QUEUE_LEN),
+ optfield('due_date', FID_DUE_DATE),
+
+ optfield('recall_date', FID_RECALL_DATE),
+ optfield('hold_pickup_date', FID_HOLD_PICKUP_DATE),
+ ofld_screen_msg,
+ ofld_print_line),
+
+ RAW : message(raw()),
+}
+
+
+class SipClient(object):
+ def __init__(self, host, port, error_detect=False):
+ self.hostport = (host, port)
+ self.error_detect = error_detect
+ self.connect()
+
+ def connect(self):
+ so = socket.socket()
+ so.connect(self.hostport)
+ self.socket = so
+ self.seqno = self.error_detect and 1 or 0
+
+ def close(self):
+ # fixme, do SIP close first.
+ self.socket.close()
+
+ def send(self, outmsg, inmsg, args=None):
+ msg_template = MESSAGES[outmsg]
+ resp_template = MESSAGES[inmsg]
+ msg = encode_msg(msg_template, args or {})
+ if self.error_detect:
+ # add the checksum
+ msg += 'AY%dAZ' % (self.seqno % 10)
+ self.seqno += 1
+ msg += checksum(msg)
+ msg += '\r'
+ if DEBUG: print '>>> %r' % msg
+ self.socket.send(msg)
+ resp = self.socket.recv(1000)
+ if DEBUG: print '<<< %r' % resp
+ return decode_msg(resp_template, resp)
+
+
+ # --------------------------------------------------
+ # Common protocol methods
+
+ def login(self, uid, pwd, locn):
+ msg = self.send(LOGIN, LOGIN_RESP,
+ dict(uid=uid, pwd=pwd, locn=locn))
+ return msg.get('ok') == '1'
+
+ def status(self):
+ return self.send(SC_STATUS, ACS_STATUS)
+
+ def patron_info(self, barcode):
+ msg = self.send(PATRON_INFO,PATRON_INFO_RESP,
+ {'patron':barcode,
+ 'startitem':1, 'enditem':2})
+ # fixme, this may not be the best test of okayness
+ msg['success'] = msg.get('valid_patron') == 'Y'
+ return msg
+
+ def checkout(self, patron, item, inst=''):
+ msg = self.send(CHECKOUT, CHECKOUT_RESP,
+ {'patron':patron,
+ 'inst': inst,
+ 'item':item})
+ msg['media_type'] = MEDIA_TYPE_TABLE.get(msg.get('media_type_code'))
+ msg['success'] = msg.get('ok') == '1'
+ return msg
+
+ def checkin(self, item, institution='', location=''):
+ msg = self.send(CHECKIN, CHECKIN_RESP,
+ {'inst': institution,
+ 'location':location,
+ 'is_retry':False,
+ 'item':item})
+ msg['success'] = msg.get('ok') == '1'
+ return msg
+
+ def item_info(self, barcode):
+ print("starting")
+ msg = self.send(ITEM_INFORMATION, ITEM_INFO_RESP,
+ {'item':barcode})
+ print(msg['circstat'])
+ msg['available'] = msg['circstat'] == '03'
+ msg['status'] = ITEM_STATUS_TABLE[msg['circstat']]
+ return msg
+
+
+# ------------------------------------------------------------
+# Django stuff. Optional.
+
+try:
+ from django.conf import settings
+ def sip_connection():
+ sip = SipClient(*settings.SIP_HOST)
+ if not sip.login(*settings.SIP_CREDENTIALS):
+ raise 'SipLoginError'
+ return sip
+
+ # decorator
+ def SIP(fn):
+ def f(*args, **kwargs):
+ conn = sip_connection()
+ resp = fn(conn, *args, **kwargs)
+ conn.close()
+ return resp
+ return f
+
+except ImportError:
+ pass
+
+
+# ------------------------------------------------------------
+# Test code.
+
+if __name__ == '__main__':
+ from pprint import pprint
+
+ sip = SipClient('comet.cs.uoguelph.ca', 8080)
+ resp = sip.login(uid='test',
+ pwd='test', locn='test')
+ pprint(resp)
+ pprint(sip.status())
+
+ pprint(sip.send(PATRON_INFO, PATRON_INFO_RESP,
+ {'patron':'scclient',
+ 'startitem':1, 'enditem':2}))
+
+ # these are items from openncip's test database.
+ item_ids = ['1565921879', '0440242746', '660']
+ bad_ids = ['xx' + i for i in item_ids]
+ for item in (item_ids + bad_ids):
+ result = sip.send(ITEM_INFORMATION, ITEM_INFO_RESP,
+ {'item':item})
+ print '%-12s: %s' % (item, result['title'] or '????')
+ print sip.send(CHECKOUT, RAW,
+ {'patron':'scclient-2',
+ 'inst': 'UWOLS',
+ 'item':item})
+ print '\n' * 5
+ pprint(sip.send(END_PATRON_SESSION, END_SESSION_RESP,
+ {'patron':'scclient',
+ 'inst':'UWOLS'}))
+
+
Modified: servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
===================================================================
--- servres/trunk/conifer/libsystems/z3950/pyz3950_search.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/libsystems/z3950/pyz3950_search.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,101 +1,101 @@
-# z39.50 search using yaz-client.
-# dependencies: yaz-client, pexpect
-
-# I found that pyz3950.zoom seemed wonky when testing against conifer
-# z3950, so I whipped up this expect-based version instead.
-
-import warnings
-import re
-import sys
-from marcxml import marcxml_to_dictionary
-
-try:
-
- import profile
- import lex
- import yacc
-except ImportError:
-
- sys.modules['profile'] = sys # just get something called 'profile';
- # it's not actually used.
- import ply.lex
- import ply.yacc # pyz3950 thinks these are toplevel modules.
- sys.modules['lex'] = ply.lex
- sys.modules['yacc'] = ply.yacc
-
-# for Z39.50 support, not sure whether this is the way to go yet but
-# as generic as it gets
-from PyZ3950 import zoom, zmarc
-
-
-LOG = None # for pexpect debugging, try LOG = sys.stderr
-GENERAL_TIMEOUT = 40
-PRESENT_TIMEOUT = 60
-
-def search(host, port, database, query, start=1, limit=10):
-
-
- query = query.encode('utf-8') # is this okay? Is it enough??
-
- conn = zoom.Connection(host, port)
- conn.databaseName = database
- conn.preferredRecordSyntax = 'XML'
-
- query = zoom.Query ('CCL', str(query))
- res = conn.search (query)
- collector = []
- #if we were dealing with marc8 results, would probably need this
- #m = zmarc.MARC8_to_Unicode ()
-
- # how many to present? At most 10 for now.
- to_show = min(len(res)-(start - 1), limit)
- if limit:
- to_show = min(to_show, limit)
-
-
- #this seems to an efficient way of snagging the records
- #would be good to cache the result set for iterative display
- for r in range(start - 1,(start-1) + to_show):
- #would need to translate marc8 records, evergreen doesn't need this
- #collector.append(m.translate(r.data))
- collector.append(str(res.__getitem__(r)).replace('\n',''))
- conn.close ()
-
-
- raw = "" . join(collector)
-
- raw_records = []
- err = None
-
- pat = re.compile('<record .*?</record>', re.M)
- raw_records = pat.findall(raw)
-
- parsed = []
- for rec in raw_records:
- try:
- rec = _marc_utf8_pattern.sub(_decode_marc_utf8, rec)
- dct = marcxml_to_dictionary(rec)
- except 'x':
- raise rec
- parsed.append(dct)
- return parsed, len(res)
-
-
-# decoding MARC \X.. UTF-8 patterns.
-
-_marc_utf8_pattern = re.compile(r'\\X([0-9A-F]{2})')
-
-def _decode_marc_utf8(regex_match):
- return chr(int(regex_match.group(1), 16))
-
-
-#------------------------------------------------------------
-# some tests
-
-if __name__ == '__main__':
- tests = [
- ('zed.concat.ca:210', 'OSUL', 'chanson'),
- ]
- for host, db, query in tests:
- print (host, db, query)
- print len(search(host, db, query, limit=33))
+# z39.50 search using yaz-client.
+# dependencies: yaz-client, pexpect
+
+# I found that pyz3950.zoom seemed wonky when testing against conifer
+# z3950, so I whipped up this expect-based version instead.
+
+import warnings
+import re
+import sys
+from marcxml import marcxml_to_dictionary
+
+try:
+
+ import profile
+ import lex
+ import yacc
+except ImportError:
+
+ sys.modules['profile'] = sys # just get something called 'profile';
+ # it's not actually used.
+ import ply.lex
+ import ply.yacc # pyz3950 thinks these are toplevel modules.
+ sys.modules['lex'] = ply.lex
+ sys.modules['yacc'] = ply.yacc
+
+# for Z39.50 support, not sure whether this is the way to go yet but
+# as generic as it gets
+from PyZ3950 import zoom, zmarc
+
+
+LOG = None # for pexpect debugging, try LOG = sys.stderr
+GENERAL_TIMEOUT = 40
+PRESENT_TIMEOUT = 60
+
+def search(host, port, database, query, start=1, limit=10):
+
+
+ query = query.encode('utf-8') # is this okay? Is it enough??
+
+ conn = zoom.Connection(host, port)
+ conn.databaseName = database
+ conn.preferredRecordSyntax = 'XML'
+
+ query = zoom.Query ('CCL', str(query))
+ res = conn.search (query)
+ collector = []
+ #if we were dealing with marc8 results, would probably need this
+ #m = zmarc.MARC8_to_Unicode ()
+
+ # how many to present? At most 10 for now.
+ to_show = min(len(res)-(start - 1), limit)
+ if limit:
+ to_show = min(to_show, limit)
+
+
+ #this seems to an efficient way of snagging the records
+ #would be good to cache the result set for iterative display
+ for r in range(start - 1,(start-1) + to_show):
+ #would need to translate marc8 records, evergreen doesn't need this
+ #collector.append(m.translate(r.data))
+ collector.append(str(res.__getitem__(r)).replace('\n',''))
+ conn.close ()
+
+
+ raw = "" . join(collector)
+
+ raw_records = []
+ err = None
+
+ pat = re.compile('<record .*?</record>', re.M)
+ raw_records = pat.findall(raw)
+
+ parsed = []
+ for rec in raw_records:
+ try:
+ rec = _marc_utf8_pattern.sub(_decode_marc_utf8, rec)
+ dct = marcxml_to_dictionary(rec)
+ except 'x':
+ raise rec
+ parsed.append(dct)
+ return parsed, len(res)
+
+
+# decoding MARC \X.. UTF-8 patterns.
+
+_marc_utf8_pattern = re.compile(r'\\X([0-9A-F]{2})')
+
+def _decode_marc_utf8(regex_match):
+ return chr(int(regex_match.group(1), 16))
+
+
+#------------------------------------------------------------
+# some tests
+
+if __name__ == '__main__':
+ tests = [
+ ('zed.concat.ca:210', 'OSUL', 'chanson'),
+ ]
+ for host, db, query in tests:
+ print (host, db, query)
+ print len(search(host, db, query, limit=33))
Modified: servres/trunk/conifer/settings.py
===================================================================
--- servres/trunk/conifer/settings.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/settings.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,121 +1,121 @@
-# Django settings for conifer project.
-
-import os
-
-os.environ['PYTHON_EGG_CACHE'] = '/tmp/eggs'
-
-BASE_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
-HERE = lambda s: os.path.join(BASE_DIRECTORY, s)
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
- # ('Your Name', 'your_email at domain.com'),
-)
-
-MANAGERS = ADMINS
-
-DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = HERE('syrup.sqlite') # Or path to database file if using sqlite3.
-DATABASE_USER = '' # Not used with sqlite3.
-DATABASE_PASSWORD = '' # Not used with sqlite3.
-DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
-DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = 'America/Detroit'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en_US'
-
-# Please only include languages here for which we have a locale in our
-# locale/ directory.
-LANGUAGES = [("en-us", "English"),
- ("fr-ca", "Canadian French"),
- ]
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# Absolute path to the directory that holds media.
-# Example: "/home/media/media.lawrence.com/"
-MEDIA_ROOT = HERE('static')
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash if there is a path component (optional in other cases).
-# Examples: "http://media.lawrence.com", "http://example.com/media/"
-MEDIA_URL = ''
-
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = '/syrup/djmedia/'
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = 'j$dnxqbi3iih+(@il3m at vv(tuvt2+yu2r-$dxs$s7=iqjz_s!&'
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'conifer.middleware.genshi_locals.ThreadLocals',
- 'django.middleware.locale.LocaleMiddleware',
- 'babeldjango.middleware.LocaleMiddleware',
- # TransactionMiddleware should be last...
- 'django.middleware.transaction.TransactionMiddleware',
-)
-
-ROOT_URLCONF = 'conifer.urls'
-
-TEMPLATE_DIRS = []
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.admin',
- 'conifer.syrup',
-)
-
-AUTH_PROFILE_MODULE = 'syrup.UserProfile'
-
-
-AUTHENTICATION_BACKENDS = (
- 'django.contrib.auth.backends.ModelBackend',
- # uncomment for EG authentication:
- #'conifer.custom.auth_evergreen.EvergreenAuthBackend',
-)
-
-
-EVERGREEN_GATEWAY_SERVER = 'www.concat.ca'
-Z3950_CONFIG = ('zed.concat.ca', 210, 'OWA') #OWA,OSUL,CONIFER
-SIP_HOST = ('localhost', 8080)
-
-try:
- from private_local_settings import SIP_CREDENTIALS
-except:
- # stuff that I really ought not check into svn...
- SIP_CREDENTIALS = ('test', 'test', 'test')
- pass
-
-
-#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
-#CACHE_BACKEND = 'db://test_cache_table'
-#CACHE_BACKEND = 'locmem:///'
+# Django settings for conifer project.
+
+import os
+
+os.environ['PYTHON_EGG_CACHE'] = '/tmp/eggs'
+
+BASE_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
+HERE = lambda s: os.path.join(BASE_DIRECTORY, s)
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email at domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = HERE('syrup.sqlite') # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Detroit'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en_US'
+
+# Please only include languages here for which we have a locale in our
+# locale/ directory.
+LANGUAGES = [("en-us", "English"),
+ ("fr-ca", "Canadian French"),
+ ]
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = HERE('static')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/syrup/djmedia/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'j$dnxqbi3iih+(@il3m at vv(tuvt2+yu2r-$dxs$s7=iqjz_s!&'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'conifer.middleware.genshi_locals.ThreadLocals',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'babeldjango.middleware.LocaleMiddleware',
+ # TransactionMiddleware should be last...
+ 'django.middleware.transaction.TransactionMiddleware',
+)
+
+ROOT_URLCONF = 'conifer.urls'
+
+TEMPLATE_DIRS = []
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'conifer.syrup',
+)
+
+AUTH_PROFILE_MODULE = 'syrup.UserProfile'
+
+
+AUTHENTICATION_BACKENDS = (
+ 'django.contrib.auth.backends.ModelBackend',
+ # uncomment for EG authentication:
+ #'conifer.custom.auth_evergreen.EvergreenAuthBackend',
+)
+
+
+EVERGREEN_GATEWAY_SERVER = 'www.concat.ca'
+Z3950_CONFIG = ('zed.concat.ca', 210, 'OWA') #OWA,OSUL,CONIFER
+SIP_HOST = ('localhost', 8080)
+
+try:
+ from private_local_settings import SIP_CREDENTIALS
+except:
+ # stuff that I really ought not check into svn...
+ SIP_CREDENTIALS = ('test', 'test', 'test')
+ pass
+
+
+#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
+#CACHE_BACKEND = 'db://test_cache_table'
+#CACHE_BACKEND = 'locmem:///'
Modified: servres/trunk/conifer/static/edit_course.js
===================================================================
--- servres/trunk/conifer/static/edit_course.js 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/static/edit_course.js 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,19 +1,19 @@
-/*
-this seems to be causing a disable when we don't want it
-*/
-function do_init() {
- if ($('#id_code')[0].tagName == 'SELECT') {
- // code is a SELECT, so we add a callback to lookup titles.
- $('#id_code').change(function() {
- $('#id_title')[0].disabled=true;
- $.getJSON('/syrup/course/new/ajax_title', {course_code: $(this).val()},
- function(resp) {
- $('#id_title').val(resp.title)
- $('#id_title')[0].disabled=false;
-
- });
- });
- }
-}
-
-$(do_init);
+/*
+this seems to be causing a disable when we don't want it
+*/
+function do_init() {
+ if ($('#id_code')[0].tagName == 'SELECT') {
+ // code is a SELECT, so we add a callback to lookup titles.
+ $('#id_code').change(function() {
+ $('#id_title')[0].disabled=true;
+ $.getJSON('/syrup/course/new/ajax_title', {course_code: $(this).val()},
+ function(resp) {
+ $('#id_title').val(resp.title)
+ $('#id_title')[0].disabled=false;
+
+ });
+ });
+ }
+}
+
+$(do_init);
Modified: servres/trunk/conifer/static/jquery/js/jquery-ui-1.7.1.custom.min.js
===================================================================
--- servres/trunk/conifer/static/jquery/js/jquery-ui-1.7.1.custom.min.js 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/static/jquery/js/jquery-ui-1.7.1.custom.min.js 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,47 +1,47 @@
-/*
- * jQuery UI 1.7.1
- *
- * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI
+/*
+ * jQuery UI 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
*/
-jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m<n.length;m++){if(j.options[n[m][0]]){n[m][1].apply(j.element,k)}}}},contains:function(k,j){return document.compareDocumentPosition?k.compareDocumentPosition(j)&16:k!==j&&k.contains(j)},hasScroll:function(m,k){if(c(m).css("overflow")=="hidden"){return false}var j=(k&&k=="left")?"scrollLeft":"scrollTop",l=false;if(m[j]>0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
- * jQuery UI Draggable 1.7.1
- *
- * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Draggables
- *
- * Depends:
- * ui.core.js
+jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m<n.length;m++){if(j.options[n[m][0]]){n[m][1].apply(j.element,k)}}}},contains:function(k,j){return document.compareDocumentPosition?k.compareDocumentPosition(j)&16:k!==j&&k.contains(j)},hasScroll:function(m,k){if(c(m).css("overflow")=="hidden"){return false}var j=(k&&k=="left")?"scrollLeft":"scrollTop",l=false;if(m[j]>0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
+ * jQuery UI Draggable 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Draggables
+ *
+ * Depends:
+ * ui.core.js
*/
-(function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");if(this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval){this.helper.remove()}this.helper=null;this.cancelHelperRemoval=false},_trigger:function(b,c,d){d=d||this._uiHash();a.ui.plugin.call(this,b,[c,d]);if(b=="drag"){this.positionAbs=this._convertPositionTo("absolute")}return a.widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(b){return{helper:this.helper,position:this.position,absolutePosition:this.positionAbs,offset:this.positionAbs}}}));a.extend(a.ui.draggable,{version:"1.7.1",eventPrefix:"drag",defaults:{addClasses:true,appendTo:"parent",axis:false,cancel:":input,option",connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false}});a.ui.plugin.add("draggable","connectToSortable",{start:function(c,e){var d=a(this).data("draggable"),f=d.options,b=a.extend({},e,{item:d.element});d.sortables=[];a(f.connectToSortable).each(function(){var g=a.data(this,"sortable");if(g&&!g.options.disabled){d.sortables.push({instance:g,shouldRevert:g.options.revert});g._refreshItems();g._trigger("activate",c,b)}})},stop:function(c,e){var d=a(this).data("draggable"),b=a.extend({},e,{item:d.element});a.each(d.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;d.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert){this.instance.options.revert=true}this.instance._mouseStop(c);this.instance.options.helper=this.instance.options._helper;if(d.options.helper=="original"){this.instance.currentItem.css({top:"auto",left:"auto"})}}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",c,b)}})},drag:function(c,f){var e=a(this).data("draggable"),b=this;var d=function(i){var n=this.offset.click.top,m=this.offset.click.left;var g=this.positionAbs.top,k=this.positionAbs.left;var j=i.height,l=i.width;var p=i.top,h=i.left;return a.ui.isOver(g+n,k+m,p,h,j,l)};a.each(e.sortables,function(g){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(b).clone().appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return f.helper[0]};c.target=this.instance.currentItem[0];this.instance._mouseCapture(c,true);this.instance._mouseStart(c,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;e._trigger("toSortable",c);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}if(this.instance.currentItem){this.instance._mouseDrag(c)}}else{if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",c,this.instance._uiHash(this.instance));this.instance._mouseStop(c,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();if(this.instance.placeholder){this.instance.placeholder.remove()}e._trigger("fromSortable",c);e.dropped=false}}})}});a.ui.plugin.add("draggable","cursor",{start:function(c,d){var b=a("body"),e=a(this).data("draggable").options;if(b.css("cursor")){e._cursor=b.css("cursor")}b.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._cursor){a("body").css("cursor",d._cursor)}}});a.ui.plugin.add("draggable","iframeFix",{start:function(b,c){var d=a(this).data("draggable").options;a(d.iframeFix===true?"iframe":d.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop+f.scrollSpeed}else{if(d.pageY-c.overflowOffset.top<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop-f.scrollSpeed}}}if(!f.axis||f.axis!="y"){if((c.overflowOffset.left+c.scrollParent[0].offsetWidth)-d.pageX<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft+f.scrollSpeed}else{if(d.pageX-c.overflowOffset.left<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft-f.scrollSpeed}}}}else{if(!f.axis||f.axis!="x"){if(d.pageY-a(document).scrollTop()<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-f.scrollSpeed)}else{if(a(window).height()-(d.pageY-a(document).scrollTop())<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+f.scrollSpeed)}}}if(!f.axis||f.axis!="y"){if(d.pageX-a(document).scrollLeft()<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-f.scrollSpeed)}else{if(a(window).width()-(d.pageX-a(document).scrollLeft())<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+f.scrollSpeed)}}}}if(b!==false&&a.ui.ddmanager&&!f.dropBehaviour){a.ui.ddmanager.prepareOffsets(c,d)}}});a.ui.plugin.add("draggable","snap",{start:function(c,d){var b=a(this).data("draggable"),e=b.options;b.snapElements=[];a(e.snap.constructor!=String?(e.snap.items||":data(draggable)"):e.snap).each(function(){var g=a(this);var f=g.offset();if(this!=b.element[0]){b.snapElements.push({item:this,width:g.outerWidth(),height:g.outerHeight(),top:f.top,left:f.left})}})},drag:function(u,p){var g=a(this).data("draggable"),q=g.options;var y=q.snapTolerance;var x=p.offset.left,w=x+g.helperProportions.width,f=p.offset.top,e=f+g.helperProportions.height;for(var v=g.snapElements.length-1;v>=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y<x&&x<n+y&&m-y<f&&f<A+y)||(s-y<x&&x<n+y&&m-y<e&&e<A+y)||(s-y<w&&w<n+y&&m-y<f&&f<A+y)||(s-y<w&&w<n+y&&m-y<e&&e<A+y))){if(g.snapElements[v].snapping){(g.options.snap.release&&g.options.snap.release.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=false;continue}if(q.snapMode!="inner"){var c=Math.abs(m-e)<=y;var z=Math.abs(A-f)<=y;var j=Math.abs(s-w)<=y;var k=Math.abs(n-x)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m-g.helperProportions.height,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s-g.helperProportions.width}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n}).left-g.margins.left}}var h=(c||z||j||k);if(q.snapMode!="outer"){var c=Math.abs(m-f)<=y;var z=Math.abs(A-e)<=y;var j=Math.abs(s-x)<=y;var k=Math.abs(n-w)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A-g.helperProportions.height,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n-g.helperProportions.width}).left-g.margins.left}}if(!g.snapElements[v].snapping&&(c||z||j||k||h)){(g.options.snap.snap&&g.options.snap.snap.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=(c||z||j||k||h)}}});a.ui.plugin.add("draggable","stack",{start:function(b,c){var e=a(this).data("draggable").options;var d=a.makeArray(a(e.stack.group)).sort(function(g,f){return(parseInt(a(g).css("zIndex"),10)||e.stack.min)-(parseInt(a(f).css("zIndex"),10)||e.stack.min)});a(d).each(function(f){this.style.zIndex=e.stack.min+f});this[0].style.zIndex=e.stack.min+d.length}});a.ui.plugin.add("draggable","zIndex",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("zIndex")){e._zIndex=b.css("zIndex")}b.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._zIndex){a(c.helper).css("zIndex",d._zIndex)}}})})(jQuery);;/*
- * jQuery UI Droppable 1.7.1
- *
- * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Droppables
- *
- * Depends:
- * ui.core.js
- * ui.draggable.js
+(function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");if(this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval){this.helper.remove()}this.helper=null;this.cancelHelperRemoval=false},_trigger:function(b,c,d){d=d||this._uiHash();a.ui.plugin.call(this,b,[c,d]);if(b=="drag"){this.positionAbs=this._convertPositionTo("absolute")}return a.widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(b){return{helper:this.helper,position:this.position,absolutePosition:this.positionAbs,offset:this.positionAbs}}}));a.extend(a.ui.draggable,{version:"1.7.1",eventPrefix:"drag",defaults:{addClasses:true,appendTo:"parent",axis:false,cancel:":input,option",connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false}});a.ui.plugin.add("draggable","connectToSortable",{start:function(c,e){var d=a(this).data("draggable"),f=d.options,b=a.extend({},e,{item:d.element});d.sortables=[];a(f.connectToSortable).each(function(){var g=a.data(this,"sortable");if(g&&!g.options.disabled){d.sortables.push({instance:g,shouldRevert:g.options.revert});g._refreshItems();g._trigger("activate",c,b)}})},stop:function(c,e){var d=a(this).data("draggable"),b=a.extend({},e,{item:d.element});a.each(d.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;d.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert){this.instance.options.revert=true}this.instance._mouseStop(c);this.instance.options.helper=this.instance.options._helper;if(d.options.helper=="original"){this.instance.currentItem.css({top:"auto",left:"auto"})}}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",c,b)}})},drag:function(c,f){var e=a(this).data("draggable"),b=this;var d=function(i){var n=this.offset.click.top,m=this.offset.click.left;var g=this.positionAbs.top,k=this.positionAbs.left;var j=i.height,l=i.width;var p=i.top,h=i.left;return a.ui.isOver(g+n,k+m,p,h,j,l)};a.each(e.sortables,function(g){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(b).clone().appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return f.helper[0]};c.target=this.instance.currentItem[0];this.instance._mouseCapture(c,true);this.instance._mouseStart(c,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;e._trigger("toSortable",c);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}if(this.instance.currentItem){this.instance._mouseDrag(c)}}else{if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",c,this.instance._uiHash(this.instance));this.instance._mouseStop(c,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();if(this.instance.placeholder){this.instance.placeholder.remove()}e._trigger("fromSortable",c);e.dropped=false}}})}});a.ui.plugin.add("draggable","cursor",{start:function(c,d){var b=a("body"),e=a(this).data("draggable").options;if(b.css("cursor")){e._cursor=b.css("cursor")}b.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._cursor){a("body").css("cursor",d._cursor)}}});a.ui.plugin.add("draggable","iframeFix",{start:function(b,c){var d=a(this).data("draggable").options;a(d.iframeFix===true?"iframe":d.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop+f.scrollSpeed}else{if(d.pageY-c.overflowOffset.top<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop-f.scrollSpeed}}}if(!f.axis||f.axis!="y"){if((c.overflowOffset.left+c.scrollParent[0].offsetWidth)-d.pageX<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft+f.scrollSpeed}else{if(d.pageX-c.overflowOffset.left<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft-f.scrollSpeed}}}}else{if(!f.axis||f.axis!="x"){if(d.pageY-a(document).scrollTop()<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-f.scrollSpeed)}else{if(a(window).height()-(d.pageY-a(document).scrollTop())<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+f.scrollSpeed)}}}if(!f.axis||f.axis!="y"){if(d.pageX-a(document).scrollLeft()<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-f.scrollSpeed)}else{if(a(window).width()-(d.pageX-a(document).scrollLeft())<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+f.scrollSpeed)}}}}if(b!==false&&a.ui.ddmanager&&!f.dropBehaviour){a.ui.ddmanager.prepareOffsets(c,d)}}});a.ui.plugin.add("draggable","snap",{start:function(c,d){var b=a(this).data("draggable"),e=b.options;b.snapElements=[];a(e.snap.constructor!=String?(e.snap.items||":data(draggable)"):e.snap).each(function(){var g=a(this);var f=g.offset();if(this!=b.element[0]){b.snapElements.push({item:this,width:g.outerWidth(),height:g.outerHeight(),top:f.top,left:f.left})}})},drag:function(u,p){var g=a(this).data("draggable"),q=g.options;var y=q.snapTolerance;var x=p.offset.left,w=x+g.helperProportions.width,f=p.offset.top,e=f+g.helperProportions.height;for(var v=g.snapElements.length-1;v>=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y<x&&x<n+y&&m-y<f&&f<A+y)||(s-y<x&&x<n+y&&m-y<e&&e<A+y)||(s-y<w&&w<n+y&&m-y<f&&f<A+y)||(s-y<w&&w<n+y&&m-y<e&&e<A+y))){if(g.snapElements[v].snapping){(g.options.snap.release&&g.options.snap.release.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=false;continue}if(q.snapMode!="inner"){var c=Math.abs(m-e)<=y;var z=Math.abs(A-f)<=y;var j=Math.abs(s-w)<=y;var k=Math.abs(n-x)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m-g.helperProportions.height,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s-g.helperProportions.width}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n}).left-g.margins.left}}var h=(c||z||j||k);if(q.snapMode!="outer"){var c=Math.abs(m-f)<=y;var z=Math.abs(A-e)<=y;var j=Math.abs(s-x)<=y;var k=Math.abs(n-w)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A-g.helperProportions.height,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n-g.helperProportions.width}).left-g.margins.left}}if(!g.snapElements[v].snapping&&(c||z||j||k||h)){(g.options.snap.snap&&g.options.snap.snap.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=(c||z||j||k||h)}}});a.ui.plugin.add("draggable","stack",{start:function(b,c){var e=a(this).data("draggable").options;var d=a.makeArray(a(e.stack.group)).sort(function(g,f){return(parseInt(a(g).css("zIndex"),10)||e.stack.min)-(parseInt(a(f).css("zIndex"),10)||e.stack.min)});a(d).each(function(f){this.style.zIndex=e.stack.min+f});this[0].style.zIndex=e.stack.min+d.length}});a.ui.plugin.add("draggable","zIndex",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("zIndex")){e._zIndex=b.css("zIndex")}b.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._zIndex){a(c.helper).css("zIndex",d._zIndex)}}})})(jQuery);;/*
+ * jQuery UI Droppable 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Droppables
+ *
+ * Depends:
+ * ui.core.js
+ * ui.draggable.js
*/
-(function(a){a.widget("ui.droppable",{_init:function(){var c=this.options,b=c.accept;this.isover=0;this.isout=1;this.options.accept=this.options.accept&&a.isFunction(this.options.accept)?this.options.accept:function(e){return e.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};a.ui.ddmanager.droppables[this.options.scope]=a.ui.ddmanager.droppables[this.options.scope]||[];a.ui.ddmanager.droppables[this.options.scope].push(this);(this.options.addClasses&&this.element.addClass("ui-droppable"))},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++){if(b[c]==this){b.splice(c,1)}}this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable")},_setData:function(b,c){if(b=="accept"){this.options.accept=c&&a.isFunction(c)?c:function(e){return e.is(c)}}else{a.widget.prototype._setData.apply(this,arguments)}},_activate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.addClass(this.options.activeClass)}(b&&this._trigger("activate",c,this.ui(b)))},_deactivate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}(b&&this._trigger("deactivate",c,this.ui(b)))},_over:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.addClass(this.options.hoverClass)}this._trigger("over",c,this.ui(b))}},_out:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("out",c,this.ui(b))}},_drop:function(c,d){var b=d||a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return false}var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var f=a.data(this,"droppable");if(f.options.greedy&&a.ui.intersect(b,a.extend(f,{offset:f.element.offset()}),f.options.tolerance)){e=true;return false}});if(e){return false}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("drop",c,this.ui(b));return this.element}return false},ui:function(b){return{draggable:(b.currentItem||b.element),helper:b.helper,position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs}}});a.extend(a.ui.droppable,{version:"1.7.1",eventPrefix:"drop",defaults:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"}});a.ui.intersect=function(q,j,o){if(!j.offset){return false}var e=(q.positionAbs||q.position.absolute).left,d=e+q.helperProportions.width,n=(q.positionAbs||q.position.absolute).top,m=n+q.helperProportions.height;var g=j.offset.left,c=g+j.proportions.width,p=j.offset.top,k=p+j.proportions.height;switch(o){case"fit":return(g<e&&d<c&&p<n&&m<k);break;case"intersect":return(g<e+(q.helperProportions.width/2)&&d-(q.helperProportions.width/2)<c&&p<n+(q.helperProportions.height/2)&&m-(q.helperProportions.height/2)<k);break;case"pointer":var h=((q.positionAbs||q.position.absolute).left+(q.clickOffset||q.offset.click).left),i=((q.positionAbs||q.position.absolute).top+(q.clickOffset||q.offset.click).top),f=a.ui.isOver(i,h,p,g,j.proportions.height,j.proportions.width);return f;break;case"touch":return((n>=p&&n<=k)||(m>=p&&m<=k)||(n<p&&m>k))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(e<g&&d>c));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d<b.length;d++){if(b[d].options.disabled||(e&&!b[d].options.accept.call(b[d].element[0],(e.currentItem||e.element)))){continue}for(var c=0;c<h.length;c++){if(h[c]==b[d].element[0]){b[d].proportions.height=0;continue droppablesLoop}}b[d].visible=b[d].element.css("display")!="none";if(!b[d].visible){continue}b[d].offset=b[d].element.offset();b[d].proportions={width:b[d].element[0].offsetWidth,height:b[d].element[0].offsetHeight};if(f=="mousedown"){b[d]._activate.call(b[d],g)}}},drop:function(b,c){var d=false;a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(!this.options){return}if(!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)){d=this._drop.call(this,c)}if(!this.options.disabled&&this.visible&&this.options.accept.call(this.element[0],(b.currentItem||b.element))){this.isout=1;this.isover=0;this._deactivate.call(this,c)}});return d},drag:function(b,c){if(b.options.refreshPositions){a.ui.ddmanager.prepareOffsets(b,c)}a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(this.options.disabled||this.greedyChild||!this.visible){return}var e=a.ui.intersect(b,this,this.options.tolerance);var g=!e&&this.isover==1?"isout":(e&&this.isover==0?"isover":null);if(!g){return}var f;if(this.options.greedy){var d=this.element.parents(":data(droppable):eq(0)");if(d.length){f=a.data(d[0],"droppable");f.greedyChild=(g=="isover"?1:0)}}if(f&&g=="isover"){f.isover=0;f.isout=1;f._out.call(f,c)}this[g]=1;this[g=="isout"?"isover":"isout"]=0;this[g=="isover"?"_over":"_out"].call(this,c);if(f&&g=="isout"){f.isout=0;f.isover=1;f._over.call(f,c)}})}}})(jQuery);;/*
- * jQuery UI Sortable 1.7.1
- *
- * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Sortables
- *
- * Depends:
- * ui.core.js
+(function(a){a.widget("ui.droppable",{_init:function(){var c=this.options,b=c.accept;this.isover=0;this.isout=1;this.options.accept=this.options.accept&&a.isFunction(this.options.accept)?this.options.accept:function(e){return e.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};a.ui.ddmanager.droppables[this.options.scope]=a.ui.ddmanager.droppables[this.options.scope]||[];a.ui.ddmanager.droppables[this.options.scope].push(this);(this.options.addClasses&&this.element.addClass("ui-droppable"))},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++){if(b[c]==this){b.splice(c,1)}}this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable")},_setData:function(b,c){if(b=="accept"){this.options.accept=c&&a.isFunction(c)?c:function(e){return e.is(c)}}else{a.widget.prototype._setData.apply(this,arguments)}},_activate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.addClass(this.options.activeClass)}(b&&this._trigger("activate",c,this.ui(b)))},_deactivate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}(b&&this._trigger("deactivate",c,this.ui(b)))},_over:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.addClass(this.options.hoverClass)}this._trigger("over",c,this.ui(b))}},_out:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("out",c,this.ui(b))}},_drop:function(c,d){var b=d||a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return false}var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var f=a.data(this,"droppable");if(f.options.greedy&&a.ui.intersect(b,a.extend(f,{offset:f.element.offset()}),f.options.tolerance)){e=true;return false}});if(e){return false}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("drop",c,this.ui(b));return this.element}return false},ui:function(b){return{draggable:(b.currentItem||b.element),helper:b.helper,position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs}}});a.extend(a.ui.droppable,{version:"1.7.1",eventPrefix:"drop",defaults:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"}});a.ui.intersect=function(q,j,o){if(!j.offset){return false}var e=(q.positionAbs||q.position.absolute).left,d=e+q.helperProportions.width,n=(q.positionAbs||q.position.absolute).top,m=n+q.helperProportions.height;var g=j.offset.left,c=g+j.proportions.width,p=j.offset.top,k=p+j.proportions.height;switch(o){case"fit":return(g<e&&d<c&&p<n&&m<k);break;case"intersect":return(g<e+(q.helperProportions.width/2)&&d-(q.helperProportions.width/2)<c&&p<n+(q.helperProportions.height/2)&&m-(q.helperProportions.height/2)<k);break;case"pointer":var h=((q.positionAbs||q.position.absolute).left+(q.clickOffset||q.offset.click).left),i=((q.positionAbs||q.position.absolute).top+(q.clickOffset||q.offset.click).top),f=a.ui.isOver(i,h,p,g,j.proportions.height,j.proportions.width);return f;break;case"touch":return((n>=p&&n<=k)||(m>=p&&m<=k)||(n<p&&m>k))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(e<g&&d>c));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d<b.length;d++){if(b[d].options.disabled||(e&&!b[d].options.accept.call(b[d].element[0],(e.currentItem||e.element)))){continue}for(var c=0;c<h.length;c++){if(h[c]==b[d].element[0]){b[d].proportions.height=0;continue droppablesLoop}}b[d].visible=b[d].element.css("display")!="none";if(!b[d].visible){continue}b[d].offset=b[d].element.offset();b[d].proportions={width:b[d].element[0].offsetWidth,height:b[d].element[0].offsetHeight};if(f=="mousedown"){b[d]._activate.call(b[d],g)}}},drop:function(b,c){var d=false;a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(!this.options){return}if(!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)){d=this._drop.call(this,c)}if(!this.options.disabled&&this.visible&&this.options.accept.call(this.element[0],(b.currentItem||b.element))){this.isout=1;this.isover=0;this._deactivate.call(this,c)}});return d},drag:function(b,c){if(b.options.refreshPositions){a.ui.ddmanager.prepareOffsets(b,c)}a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(this.options.disabled||this.greedyChild||!this.visible){return}var e=a.ui.intersect(b,this,this.options.tolerance);var g=!e&&this.isover==1?"isout":(e&&this.isover==0?"isover":null);if(!g){return}var f;if(this.options.greedy){var d=this.element.parents(":data(droppable):eq(0)");if(d.length){f=a.data(d[0],"droppable");f.greedyChild=(g=="isover"?1:0)}}if(f&&g=="isover"){f.isover=0;f.isout=1;f._out.call(f,c)}this[g]=1;this[g=="isout"?"isover":"isout"]=0;this[g=="isover"?"_over":"_out"].call(this,c);if(f&&g=="isout"){f.isout=0;f.isover=1;f._over.call(f,c)}})}}})(jQuery);;/*
+ * jQuery UI Sortable 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Sortables
+ *
+ * Depends:
+ * ui.core.js
*/
(function(a){a.widget("ui.sortable",a.extend({},a.ui.mouse,{_init:function(){var b=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?(/left|right/).test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--){this.items[b].item.removeData("sortable-item")}},_mouseCapture:function(e,f){if(this.reverting){return false}if(this.options.disabled||this.options.type=="static"){return false}this._refreshItems(e);var d=null,c=this,b=a(e.target).parents().each(function(){if(a.data(this,"sortable-item")==c){d=a(this);return false}});if(a.data(e.target,"sortable-item")==c){d=a(e.target)}if(!d){return false}if(this.options.handle&&!f){var g=false;a(this.options.handle,d).find("*").andSelf().each(function(){if(this==e.target){g=true}});if(!g){return false}}this.currentItem=d;this._removeCurrentsFromItems();return true},_mouseStart:function(e,f,b){var g=this.options,c=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(e);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(e);this.originalPageX=e.pageX;this.originalPageY=e.pageY;if(g.cursorAt){this._adjustOffsetFromHelper(g.cursorAt)}this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide()}this._createPlaceholder();if(g.containment){this._setContainment()}if(g.cursor){if(a("body").css("cursor")){this._storedCursor=a("body").css("cursor")}a("body").css("cursor",g.cursor)}if(g.opacity){if(this.helper.css("opacity")){this._storedOpacity=this.helper.css("opacity")}this.helper.css("opacity",g.opacity)}if(g.zIndex){if(this.helper.css("zIndex")){this._storedZIndex=this.helper.css("zIndex")}this.helper.css("zIndex",g.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){this.overflowOffset=this.scrollParent.offset()}this._trigger("start",e,this._uiHash());if(!this._preserveHelperProportions){this._cacheHelperProportions()}if(!b){for(var d=this.containers.length-1;d>=0;d--){this.containers[d]._trigger("activate",e,c._uiHash(this))}}if(a.ui.ddmanager){a.ui.ddmanager.current=this}if(a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,e)}this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(e);return true},_mouseDrag:function(f){this.position=this._generatePosition(f);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}if(this.options.scroll){var g=this.options,b=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-f.pageY<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop+g.scrollSpeed}else{if(f.pageY-this.overflowOffset.top<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop-g.scrollSpeed}}if((this.overflowOffset.left+this.scrollParent[0].offsetWidth)-f.pageX<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft+g.scrollSpeed}else{if(f.pageX-this.overflowOffset.left<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft-g.scrollSpeed}}}else{if(f.pageY-a(document).scrollTop()<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-g.scrollSpeed)}else{if(a(window).height()-(f.pageY-a(document).scrollTop())<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+g.scrollSpeed)}}if(f.pageX-a(document).scrollLeft()<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-g.scrollSpeed)}else{if(a(window).width()-(f.pageX-a(document).scrollLeft())<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+g.scrollSpeed)}}}if(b!==false&&a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,f)}}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d],c=e.item[0],h=this._intersectsWithPointer(e);if(!h){continue}if(c!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=c&&!a.ui.contains(this.placeholder[0],c)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],c):true)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(e)){this._rearrange(f,e)}else{break}this._trigger("change",f,this._uiHash());break}}this._contactContainers(f);if(a.ui.ddmanager){a.ui.ddmanager.drag(this,f)}this._trigger("sort",f,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(c,d){if(!c){return}if(a.ui.ddmanager&&!this.options.dropBehaviour){a.ui.ddmanager.drop(this,c)}if(this.options.revert){var b=this;var e=b.placeholder.offset();b.reverting=true;a(this.helper).animate({left:e.left-this.offset.parent.left-b.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-b.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){b._clear(c)})}else{this._clear(c,d)}return false},cancel:function(){var b=this;if(this.dragging){this._mouseUp();if(this.options.helper=="original"){this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,b._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,b._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder[0].parentNode){this.placeholder[0].parentNode.removeChild(this.placeholder[0])}if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode){this.helper.remove()}a.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){a(this.domPosition.prev).after(this.currentItem)}else{a(this.domPosition.parent).prepend(this.currentItem)}return true},serialize:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};a(b).each(function(){var e=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||(/(.+)[-=_](.+)/));if(e){c.push((d.key||e[1]+"[]")+"="+(d.key&&d.expression?e[1]:e[2]))}});return c.join("&")},toArray:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};b.each(function(){c.push(a(d.item||this).attr(d.attribute||"id")||"")});return c},_intersectsWith:function(m){var e=this.positionAbs.left,d=e+this.helperProportions.width,k=this.positionAbs.top,j=k+this.helperProportions.height;var f=m.left,c=f+m.width,n=m.top,i=n+m.height;var o=this.offset.click.top,h=this.offset.click.left;var g=(k+o)>n&&(k+o)<i&&(e+h)>f&&(e+h)<c;if(this.options.tolerance=="pointer"||this.options.forcePointerForContainers||(this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>m[this.floating?"width":"height"])){return g}else{return(f<e+(this.helperProportions.width/2)&&d-(this.helperProportions.width/2)<c&&n<k+(this.helperProportions.height/2)&&j-(this.helperProportions.height/2)<i)}},_intersectsWithPointer:function(d){var e=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top,d.height),c=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left,d.width),g=e&&c,b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(!g){return false}return this.floating?(((f&&f=="right")||b=="down")?2:1):(b&&(b=="down"?2:1))},_intersectsWithSides:function(e){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+(e.height/2),e.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+(e.width/2),e.width),b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(this.floating&&f){return((f=="right"&&d)||(f=="left"&&!d))}else{return b&&((b=="down"&&c)||(b=="up"&&!c))}},_getDragVerticalDirection:function(){var b=this.positionAbs.top-this.lastPositionAbs.top;return b!=0&&(b>0?"down":"up")},_getDragHorizontalDirection:function(){var b=this.positionAbs.left-this.lastPositionAbs.left;return b!=0&&(b>0?"right":"left")},refresh:function(b){this._refreshItems(b);this.refreshPositions()},_connectWith:function(){var b=this.options;return b.connectWith.constructor==String?[b.connectWith]:b.connectWith},_getItemsAsjQuery:function(b){var l=this;var g=[];var e=[];var h=this._connectWith();if(h&&b){for(var d=h.length-1;d>=0;d--){var k=a(h[d]);for(var c=k.length-1;c>=0;c--){var f=a.data(k[c],"sortable");if(f&&f!=this&&!f.options.disabled){e.push([a.isFunction(f.options.items)?f.options.items.call(f.element):a(f.options.items,f.element).not(".ui-sortable-helper"),f])}}}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper"),this]);for(var d=e.length-1;d>=0;d--){e[d][0].each(function(){g.push(this)})}return a(g)},_removeCurrentsFromItems:function(){var d=this.currentItem.find(":data(sortable-item)");for(var c=0;c<this.items.length;c++){for(var b=0;b<d.length;b++){if(d[b]==this.items[c].item[0]){this.items.splice(c,1)}}}},_refreshItems:function(b){this.items=[];this.containers=[this];var h=this.items;var p=this;var f=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]];var l=this._connectWith();if(l){for(var e=l.length-1;e>=0;e--){var m=a(l[e]);for(var d=m.length-1;d>=0;d--){var g=a.data(m[d],"sortable");if(g&&g!=this&&!g.options.disabled){f.push([a.isFunction(g.options.items)?g.options.items.call(g.element[0],b,{item:this.currentItem}):a(g.options.items,g.element),g]);this.containers.push(g)}}}}for(var e=f.length-1;e>=0;e--){var k=f[e][1];var c=f[e][0];for(var d=0,n=c.length;d<n;d++){var o=a(c[d]);o.data("sortable-item",k);h.push({item:o,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){if(this.offsetParent&&this.helper){this.offset.parent=this._getParentOffset()}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d];if(e.instance!=this.currentContainer&&this.currentContainer&&e.item[0]!=this.currentItem[0]){continue}var c=this.options.toleranceElement?a(this.options.toleranceElement,e.item):e.item;if(!b){e.width=c.outerWidth();e.height=c.outerHeight()}var f=c.offset();e.left=f.left;e.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this)}else{for(var d=this.containers.length-1;d>=0;d--){var f=this.containers[d].element.offset();this.containers[d].containerCache.left=f.left;this.containers[d].containerCache.top=f.top;this.containers[d].containerCache.width=this.containers[d].element.outerWidth();this.containers[d].containerCache.height=this.containers[d].element.outerHeight()}}},_createPlaceholder:function(d){var b=d||this,e=b.options;if(!e.placeholder||e.placeholder.constructor==String){var c=e.placeholder;e.placeholder={element:function(){var f=a(document.createElement(b.currentItem[0].nodeName)).addClass(c||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!c){f.style.visibility="hidden"}return f},update:function(f,g){if(c&&!e.forcePlaceholderSize){return}if(!g.height()){g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10))}if(!g.width()){g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=a(e.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);e.placeholder.update(b,b.placeholder)},_contactContainers:function(d){for(var c=this.containers.length-1;c>=0;c--){if(this._intersectsWith(this.containers[c].containerCache)){if(!this.containers[c].containerCache.over){if(this.currentContainer!=this.containers[c]){var h=10000;var g=null;var e=this.positionAbs[this.containers[c].floating?"left":"top"];for(var b=this.items.length-1;b>=0;b--){if(!a.ui.contains(this.containers[c].element[0],this.items[b].item[0])){continue}var f=this.items[b][this.containers[c].floating?"left":"top"];if(Math.abs(f-e)<h){h=Math.abs(f-e);g=this.items[b]}}if(!g&&!this.options.dropOnEmpty){continue}this.currentContainer=this.containers[c];g?this._rearrange(d,g,null,true):this._rearrange(d,null,this.containers[c].element,true);this._trigger("change",d,this._uiHash());this.containers[c]._trigger("change",d,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder)}this.containers[c]._trigger("over",d,this._uiHash(this));this.containers[c].containerCache.over=1}}else{if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",d,this._uiHash(this));this.containers[c].containerCache.over=0}}}},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c,this.currentItem])):(d.helper=="clone"?this.currentItem.clone():this.currentItem);if(!b.parents("body").length){a(d.appendTo!="parent"?d.appendTo:this.currentItem[0].parentNode)[0].appendChild(b[0])}if(b[0]==this.currentItem[0]){this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}}if(b[0].style.width==""||d.forceHelperSize){b.width(this.currentItem.width())}if(b[0].style.height==""||d.forceHelperSize){b.height(this.currentItem.height())}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.currentItem.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.currentItem.css("marginLeft"),10)||0),top:(parseInt(this.currentItem.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)){var c=a(e.containment)[0];var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_rearrange:function(g,f,c,e){c?c[0].appendChild(this.placeholder[0]):f.item[0].parentNode.insertBefore(this.placeholder[0],(this.direction=="down"?f.item[0]:f.item[0].nextSibling));this.counter=this.counter?++this.counter:1;var d=this,b=this.counter;window.setTimeout(function(){if(b==d.counter){d.refreshPositions(!e)}},0)},_clear:function(d,e){this.reverting=false;var f=[],b=this;if(!this._noFinalSort&&this.currentItem[0].parentNode){this.placeholder.before(this.currentItem)}this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var c in this._storedCSS){if(this._storedCSS[c]=="auto"||this._storedCSS[c]=="static"){this._storedCSS[c]=""}}this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}if(this.fromOutside&&!e){f.push(function(g){this._trigger("receive",g,this._uiHash(this.fromOutside))})}if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!e){f.push(function(g){this._trigger("update",g,this._uiHash())})}if(!a.ui.contains(this.element[0],this.currentItem[0])){if(!e){f.push(function(g){this._trigger("remove",g,this._uiHash())})}for(var c=this.containers.length-1;c>=0;c--){if(a.ui.contains(this.containers[c].element[0],this.currentItem[0])&&!e){f.push((function(g){return function(h){g._trigger("receive",h,this._uiHash(this))}}).call(this,this.containers[c]));f.push((function(g){return function(h){g._trigger("update",h,this._uiHash(this))}}).call(this,this.containers[c]))}}}for(var c=this.containers.length-1;c>=0;c--){if(!e){f.push((function(g){return function(h){g._trigger("deactivate",h,this._uiHash(this))}}).call(this,this.containers[c]))}if(this.containers[c].containerCache.over){f.push((function(g){return function(h){g._trigger("out",h,this._uiHash(this))}}).call(this,this.containers[c]));this.containers[c].containerCache.over=0}}if(this._storedCursor){a("body").css("cursor",this._storedCursor)}if(this._storedOpacity){this.helper.css("opacity",this._storedOpacity)}if(this._storedZIndex){this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex)}this.dragging=false;if(this.cancelHelperRemoval){if(!e){this._trigger("beforeStop",d,this._uiHash());for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}return false}if(!e){this._trigger("beforeStop",d,this._uiHash())}this.placeholder[0].parentNode.removeChild(this.placeholder[0]);if(this.helper[0]!=this.currentItem[0]){this.helper.remove()}this.helper=null;if(!e){for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){if(a.widget.prototype._trigger.apply(this,arguments)===false){this.cancel()}},_uiHash:function(c){var b=c||this;return{helper:b.helper,placeholder:b.placeholder||a([]),position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs,item:b.currentItem,sender:c?c.element:null}}}));a.extend(a.ui.sortable,{getter:"serialize toArray",version:"1.7.1",eventPrefix:"sort",defaults:{appendTo:"parent",axis:false,cancel:":input,option",connectWith:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000}})})(jQuery);;
\ No newline at end of file
Modified: servres/trunk/conifer/static/jquery/js/jquery.js
===================================================================
--- servres/trunk/conifer/static/jquery/js/jquery.js 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/static/jquery/js/jquery.js 2010-01-22 02:48:02 UTC (rev 762)
@@ -536,156 +536,156 @@
function now() {
return (new Date).getTime();
}
-var expando = "jQuery" + now(), uuid = 0, windowData = {};
-
-jQuery.extend({
- cache: {},
-
- data: function( elem, name, data ) {
- elem = elem == window ?
- windowData :
- elem;
-
- var id = elem[ expando ], cache = jQuery.cache;
-
- // Compute a unique ID for the element
- if(!id) id = elem[ expando ] = ++uuid;
-
- // Only generate the data cache if we're
- // trying to access or manipulate it
- if ( name && !cache[ id ] )
- cache[ id ] = {};
-
- var thisCache = cache[ id ];
-
- // Prevent overriding the named cache with undefined values
- if ( data !== undefined ) thisCache[ name ] = data;
-
- if(name === true) return thisCache
- else if(name) return thisCache[name]
- else return id
- },
-
- removeData: function( elem, name ) {
- elem = elem == window ?
- windowData :
- elem;
-
- var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
-
- // If we want to remove a specific section of the element's data
- if ( name ) {
- if ( thisCache ) {
- // Remove the section of cache data
- delete thisCache[ name ];
-
- // If we've removed all the data, remove the element's cache
- if( jQuery.isEmptyObject(thisCache) )
- jQuery.removeData( elem );
- }
-
- // Otherwise, we want to remove all of the element's data
- } else {
- // Clean up the element expando
- try {
- delete elem[ expando ];
- } catch(e){
- // IE has trouble directly removing the expando
- // but it's ok with using removeAttribute
- if ( elem.removeAttribute )
- elem.removeAttribute( expando );
- }
-
- // Completely remove the data cache
- delete cache[ id ];
- }
- },
- queue: function( elem, type, data ) {
- if( !elem ) return;
-
- type = (type || "fx") + "queue";
- var q = jQuery.data( elem, type );
-
- // Speed up dequeue by getting out quickly if this is just a lookup
- if( !data ) return q || [];
-
- if ( !q || jQuery.isArray(data) )
- q = jQuery.data( elem, type, jQuery.makeArray(data) );
- else
- q.push( data );
-
- return q;
- },
-
- dequeue: function( elem, type ){
- type = type || "fx";
-
- var queue = jQuery.queue( elem, type ), fn = queue.shift();
-
- // If the fx queue is dequeued, always remove the progress sentinel
- if( fn === "inprogress" ) fn = queue.shift();
-
- if( fn ) {
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if( type == "fx" ) queue.unshift("inprogress");
-
- fn.call(elem, function() { jQuery.dequeue(elem, type); });
- }
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ){
- if(typeof key === "undefined" && this.length) return jQuery.data(this[0], true);
-
- var parts = key.split(".");
- parts[1] = parts[1] ? "." + parts[1] : "";
-
- if ( value === undefined ) {
- var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
-
- if ( data === undefined && this.length )
- data = jQuery.data( this[0], key );
-
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
- } else
- return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
- jQuery.data( this, key, value );
- });
- },
-
- removeData: function( key ){
- return this.each(function(){
- jQuery.removeData( this, key );
- });
- },
- queue: function(type, data){
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- }
-
- if ( data === undefined )
- return jQuery.queue( this[0], type );
-
- return this.each(function(i, elem){
- var queue = jQuery.queue( this, type, data );
-
- if( type == "fx" && queue[0] !== "inprogress" )
- jQuery.dequeue( this, type )
- });
- },
- dequeue: function(type){
- return this.each(function(){
- jQuery.dequeue( this, type );
- });
- },
- clearQueue: function(type){
- return this.queue( type || "fx", [] );
- }
+var expando = "jQuery" + now(), uuid = 0, windowData = {};
+
+jQuery.extend({
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache;
+
+ // Compute a unique ID for the element
+ if(!id) id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !cache[ id ] )
+ cache[ id ] = {};
+
+ var thisCache = cache[ id ];
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined ) thisCache[ name ] = data;
+
+ if(name === true) return thisCache
+ else if(name) return thisCache[name]
+ else return id
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( thisCache ) {
+ // Remove the section of cache data
+ delete thisCache[ name ];
+
+ // If we've removed all the data, remove the element's cache
+ if( jQuery.isEmptyObject(thisCache) )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete cache[ id ];
+ }
+ },
+ queue: function( elem, type, data ) {
+ if( !elem ) return;
+
+ type = (type || "fx") + "queue";
+ var q = jQuery.data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if( !data ) return q || [];
+
+ if ( !q || jQuery.isArray(data) )
+ q = jQuery.data( elem, type, jQuery.makeArray(data) );
+ else
+ q.push( data );
+
+ return q;
+ },
+
+ dequeue: function( elem, type ){
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ), fn = queue.shift();
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if( fn === "inprogress" ) fn = queue.shift();
+
+ if( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if( type == "fx" ) queue.unshift("inprogress");
+
+ fn.call(elem, function() { jQuery.dequeue(elem, type); });
+ }
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ){
+ if(typeof key === "undefined" && this.length) return jQuery.data(this[0], true);
+
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+ jQuery.data( this, key, value );
+ });
+ },
+
+ removeData: function( key ){
+ return this.each(function(){
+ jQuery.removeData( this, key );
+ });
+ },
+ queue: function(type, data){
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined )
+ return jQuery.queue( this[0], type );
+
+ return this.each(function(i, elem){
+ var queue = jQuery.queue( this, type, data );
+
+ if( type == "fx" && queue[0] !== "inprogress" )
+ jQuery.dequeue( this, type )
+ });
+ },
+ dequeue: function(type){
+ return this.each(function(){
+ jQuery.dequeue( this, type );
+ });
+ },
+ clearQueue: function(type){
+ return this.queue( type || "fx", [] );
+ }
});/*!
* Sizzle CSS Selector Engine - v1.0
* Copyright 2009, The Dojo Foundation
Modified: servres/trunk/conifer/static/main.css
===================================================================
--- servres/trunk/conifer/static/main.css 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/static/main.css 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,342 +1,342 @@
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, font, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td {
- margin: 0;
- padding: 0;
- border: 0;
- outline: 0;
- font-weight: inherit;
- font-style: inherit;
- font-size: 100%;
- font-family: inherit;
- vertical-align: baseline;
-}
-/* remember to define focus styles! */
-:focus {
- outline: 0;
-}
-body {
- line-height: 1;
- color: black;
- background: white;
-}
-ol, ul {
- list-style: none;
-}
-/* tables still need 'cellspacing="0"' in the markup */
-table {
- border-collapse: separate;
- border-spacing: 0;
-}
-caption, th, td {
- text-align: left;
- font-weight: normal;
-}
-blockquote:before, blockquote:after,
-q:before, q:after {
- content: "";
-}
-blockquote, q {
- quotes: "" "";
-}
-
-/* General look and feel */
-
-body * { font-family: Verdana, sans-serif; }
-pre, code { font-family: monospace; }
-
-body, html { margin: 0; padding: 0; }
-
-body {
- background-color: #005;
- font-size: normal;
-}
-
-div#outer {
- background-color: white;
- width: 960px; margin-bottom: 50px;
- margin-left: auto; margin-right: auto;
-}
-#mainpanel { background-color: white; padding: 0 12px 24px 12px;
-min-height: 300px;
-}
-
-/* General headers and footers */
-
-#header, #footer {
- color: white; padding: 8px 4px 12px 8px;
- background-color: #448;
-}
-
-#header div#search {
- float: right;
- margin: 0;
- color: #ccc;
-}
-
-#header #welcome {
- margin-top: 4px;
-}
-
-#brandheader { background-color: white; padding: 8px; }
-
-#header a { color: #fff; font-weight: bold; padding: 10px 12px 10px 12px; }
-#header a.loginbutton { background-color: #a44; }
-#header a:hover { background-color: #fb7; color: black; text-decoration: none; }
-
-tbody td, tbody th { vertical-align: top; }
-
-#footer {
- margin: 12px;
- padding-bottom: 12px;
- background-color: #ddf;
- color: black;
- border-bottom: white 10px solid;
-}
-
-/* heading sizes and colours. */
-
-h1 { font-size: 150%; font-weight: bold; }
-h2 { font-size: 125%; font-weight: bold; }
-h3 { font-size: 120%; font-weight: bold; }
-p { margin: 24px 0px; }
-h1 { color: navy; margin: 36px 0 18px 0; }
-h2 { color: #336; margin: 24px 0 12px 0; }
-h3, h4 { color: darkgreen; }
-h1 a, h2 a { color: navy; }
-
-a { color: blue; text-decoration: none; }
-a:hover { text-decoration: underline; }
-
-/* error panel on the person-edit form */
-
-.errors { margin: 1em; padding: 1em; background-color: #fdd; }
-.errors h2 { font-size: 120%; }
-
-/* actions (e.g. "edit user" link) */
-.action a { font-weight: bold; }
-
-#tabbar { margin: 18px 0; padding: 0; clear: both; }
-#tabbar li { display: inline; }
-#tabbar li a { padding: 15px 18px 5px 18px; background-color: #ddf; color: black; text-decoration: none; }
-#tabbar li a:hover { background-color: #fc8; }
-
-/*
-#tabbar li.active a { background-color: #fa6; font-weight: bold; }
-*/
-
-.pagination_controls {
- text-align: center; margin: 12px 0;
-}
-
-.pagination_controls .nums {
- padding: 0 12px;
-}
-
-.pagetable td { border: #ddd 1px solid; padding: 8px; }
-.pagetable .odd {
- background-color: #F8F8F8;
-}
-.pagetable thead th { font-size: smaller; text-align: left; padding: 2px 8px; }
-
-
-/* nested titles: like breadcrumbs when drilling into an itemtree */
-
-.nestedtitle h2 { margin-top: 8px; }
-.nestedtitle a { color: navy; }
-
-span.final_item { font-weight: bold; font-size: 110%; }
-
-/* item trees (tree of headings and items in a course */
-
-#sidepanel { width: 183px; float: right; text-align: right;}
-#sidepanel div { margin: 6px 0; }
-
-#treepanel { width: 740px; }
-
-.helptext {
- margin-top: 30px; font-size: 90%;
- padding: 10px;
- background-color: #eef;
- clear: both;
-}
-
-.itemtree {
- margin-left: 20px;
- padding-left: 20px;
- list-style-type: none;
-}
-
-.itemtree .itemtree { margin-left: 30px; padding-left: 0; }
-
-.itemtree li { padding-left: 0; margin-left: 0; }
-
-.itemtree li { margin: 12px 8px; }
-.itemtree li .mainline { padding-left: 8px; }
-
-.itemtree .metalink { padding-left: 8px; color: gray; }
-.itemtree .metalink a {
- color: gray;
-}
-
-.itemtree .editlinks { padding-left: 12px; color: gray;
- font-size: small;
- }
-.itemtree .editlinks a { color: navy; }
-
-.itemadd {
- margin-top: 30px; font-size: 90%;
- padding: 10px;
- background-color: #eef;
- clear: both;
-}
-.itemadd li {
- margin: 10px;
-}
-
-.itemadd a { color: navy; }
-
-/* specialized display of items in tree, by type */
-
-.itemtree li.item_HEADING {
- list-style-image: url(tango/folder.png);
-}
-.itemtree li.item_HEADING > a {
- color: navy;
-}
-
-li.item_HEADING .headingmainline {
- margin-bottom: 12px;
-}
-
-li.item_HEADING .headingmainline a.mainlink {
- border-bottom: #aaa 1px solid;
-}
-
-li.item_HEADING .headingmainline a.mainlink:hover {
- border-bottom: none;
-}
-
-.itemtree li.item_ELEC {
- list-style-image: url(tango/document.png);
-}
-
-.itemtree li.item_URL {
- list-style-image: url(tango/applications-internet.png);
-}
-
-.itemtree li.item_PHYS {
- /* fixme: need a better icon */
- list-style-image: url(tango/x-office-address-book.png);
-}
-
-
-.instructors {
- border: 1px solid #ccc;
- float: left;
- width: 50%;
- padding: 2px 2px 2px 2px;
- font-size: 1em;
- line-height: 1em;
- text-align: left;
- margin-right: 5px;
-}
-
-.topbox {
- border: 1px solid #ccc;
- width: 50%;
- line-height: 1em;
- text-align: left;
-}
-
-table.topheading { width: 100%; }
-.topheading th {
- background-color: #ddf;
-}
-
-.topheading th, .topheading td {
-padding: 8px;
-}
-
-p.todo, div.todo { background-color: #fdd; padding: 6px; margin: 12px; border-left: #d99 6px solid; }
-
-.newsitem p { margin: 12px 0; }
-.newsitem ul { list-style: circle; margin-left: 30px; }
-.newsitem ul li { margin: 8px; }
-
-.newsitem {
- max-width: 600px;
- line-height: 125%;
-}
-.newsitem .newsdate {
- margin: 4px 0 8px 0; text-align: right;
- font-size: 80%; color: navy;
-}
-
-.menublockopener { margin-left: 0.25em; color: #bbb !important; font-weight: normal !important; }
-.menublock { background-color: #f2e4cc; font-size: 95%; padding: 1px 4px; }
-
-#coursebanner { background-color: #f2e4cc; margin: -12px -12px 12px -12px; padding: 8px; }
-#coursesearch { float: right; }
-#coursebanner h1 { margin: 12px 0; font-size: 125%; }
-
-#edit_course_link { margin: 8px 0 8px 0; font-size: 95%; }
-
-
-#breadcrumbs { margin: 8px 0px 16px 0; width: 716px;
- text-indent: -24px; padding-left: 24px; }
-
-.errorlist { float: right; }
-.errorlist li { color: red; font-size: 90%; }
-
-
-/* a nice table-style for forms. */
-.formtable tbody th {
- padding: 0 8px 16px 0;
- width: 200px;
- text-align: left;
- font-size: 90%;
- font-weight: normal;
-}
-
-thead th { padding: 8px; font-weight: bold; font-size: 90%; }
-.metadata_table tbody th,
-.metadata_table tbody td {
- padding: 8px; border: #ddd 1px solid;
-}
-.metadata_table input { width: 600px; }
-.metadata_table .meta3 input { width: 10px; }
-
-.metadata_table tbody th {
- background-color: #eee;
-}
-
-.metadata_table a.bigdownload { padding: 8px 58px; font-weight: bold; font-size: 105%; }
-.metadata_table a.bigdownload:hover { background-color: #dfd; color: black; }
-
-h2.metadata_subhead {font-size: 105%; padding: 0; margin: 18px 0 9px 0;}
-
-.metadata_table tbody th {
- text-align: left; width: 120px;
-}
-.gap { height: 24px; }
-.metadata_table td { max-width: 800px; overflow: hidden; }
-
-/* panels that appear when specific OPTIONs or radio-buttons are selected. */
-.specific { padding: 8px; margin: 0 16px; background-color: #eef; }
-
-
-li.sort_item { margin-top: 20px !important;
- border: gray 1px dotted; width: 400px; }
-
-li.sort_item:hover { background-color: #eee; }
-
-ul.heading_tree li { list-style: none; }
-ul.heading_tree { margin: 0; padding-left: 0; }
-ul.heading_tree ul { margin: 0; padding-left: 25px; }
-
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+body {
+ line-height: 1;
+ color: black;
+ background: white;
+}
+ol, ul {
+ list-style: none;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: "";
+}
+blockquote, q {
+ quotes: "" "";
+}
+
+/* General look and feel */
+
+body * { font-family: Verdana, sans-serif; }
+pre, code { font-family: monospace; }
+
+body, html { margin: 0; padding: 0; }
+
+body {
+ background-color: #005;
+ font-size: normal;
+}
+
+div#outer {
+ background-color: white;
+ width: 960px; margin-bottom: 50px;
+ margin-left: auto; margin-right: auto;
+}
+#mainpanel { background-color: white; padding: 0 12px 24px 12px;
+min-height: 300px;
+}
+
+/* General headers and footers */
+
+#header, #footer {
+ color: white; padding: 8px 4px 12px 8px;
+ background-color: #448;
+}
+
+#header div#search {
+ float: right;
+ margin: 0;
+ color: #ccc;
+}
+
+#header #welcome {
+ margin-top: 4px;
+}
+
+#brandheader { background-color: white; padding: 8px; }
+
+#header a { color: #fff; font-weight: bold; padding: 10px 12px 10px 12px; }
+#header a.loginbutton { background-color: #a44; }
+#header a:hover { background-color: #fb7; color: black; text-decoration: none; }
+
+tbody td, tbody th { vertical-align: top; }
+
+#footer {
+ margin: 12px;
+ padding-bottom: 12px;
+ background-color: #ddf;
+ color: black;
+ border-bottom: white 10px solid;
+}
+
+/* heading sizes and colours. */
+
+h1 { font-size: 150%; font-weight: bold; }
+h2 { font-size: 125%; font-weight: bold; }
+h3 { font-size: 120%; font-weight: bold; }
+p { margin: 24px 0px; }
+h1 { color: navy; margin: 36px 0 18px 0; }
+h2 { color: #336; margin: 24px 0 12px 0; }
+h3, h4 { color: darkgreen; }
+h1 a, h2 a { color: navy; }
+
+a { color: blue; text-decoration: none; }
+a:hover { text-decoration: underline; }
+
+/* error panel on the person-edit form */
+
+.errors { margin: 1em; padding: 1em; background-color: #fdd; }
+.errors h2 { font-size: 120%; }
+
+/* actions (e.g. "edit user" link) */
+.action a { font-weight: bold; }
+
+#tabbar { margin: 18px 0; padding: 0; clear: both; }
+#tabbar li { display: inline; }
+#tabbar li a { padding: 15px 18px 5px 18px; background-color: #ddf; color: black; text-decoration: none; }
+#tabbar li a:hover { background-color: #fc8; }
+
+/*
+#tabbar li.active a { background-color: #fa6; font-weight: bold; }
+*/
+
+.pagination_controls {
+ text-align: center; margin: 12px 0;
+}
+
+.pagination_controls .nums {
+ padding: 0 12px;
+}
+
+.pagetable td { border: #ddd 1px solid; padding: 8px; }
+.pagetable .odd {
+ background-color: #F8F8F8;
+}
+.pagetable thead th { font-size: smaller; text-align: left; padding: 2px 8px; }
+
+
+/* nested titles: like breadcrumbs when drilling into an itemtree */
+
+.nestedtitle h2 { margin-top: 8px; }
+.nestedtitle a { color: navy; }
+
+span.final_item { font-weight: bold; font-size: 110%; }
+
+/* item trees (tree of headings and items in a course */
+
+#sidepanel { width: 183px; float: right; text-align: right;}
+#sidepanel div { margin: 6px 0; }
+
+#treepanel { width: 740px; }
+
+.helptext {
+ margin-top: 30px; font-size: 90%;
+ padding: 10px;
+ background-color: #eef;
+ clear: both;
+}
+
+.itemtree {
+ margin-left: 20px;
+ padding-left: 20px;
+ list-style-type: none;
+}
+
+.itemtree .itemtree { margin-left: 30px; padding-left: 0; }
+
+.itemtree li { padding-left: 0; margin-left: 0; }
+
+.itemtree li { margin: 12px 8px; }
+.itemtree li .mainline { padding-left: 8px; }
+
+.itemtree .metalink { padding-left: 8px; color: gray; }
+.itemtree .metalink a {
+ color: gray;
+}
+
+.itemtree .editlinks { padding-left: 12px; color: gray;
+ font-size: small;
+ }
+.itemtree .editlinks a { color: navy; }
+
+.itemadd {
+ margin-top: 30px; font-size: 90%;
+ padding: 10px;
+ background-color: #eef;
+ clear: both;
+}
+.itemadd li {
+ margin: 10px;
+}
+
+.itemadd a { color: navy; }
+
+/* specialized display of items in tree, by type */
+
+.itemtree li.item_HEADING {
+ list-style-image: url(tango/folder.png);
+}
+.itemtree li.item_HEADING > a {
+ color: navy;
+}
+
+li.item_HEADING .headingmainline {
+ margin-bottom: 12px;
+}
+
+li.item_HEADING .headingmainline a.mainlink {
+ border-bottom: #aaa 1px solid;
+}
+
+li.item_HEADING .headingmainline a.mainlink:hover {
+ border-bottom: none;
+}
+
+.itemtree li.item_ELEC {
+ list-style-image: url(tango/document.png);
+}
+
+.itemtree li.item_URL {
+ list-style-image: url(tango/applications-internet.png);
+}
+
+.itemtree li.item_PHYS {
+ /* fixme: need a better icon */
+ list-style-image: url(tango/x-office-address-book.png);
+}
+
+
+.instructors {
+ border: 1px solid #ccc;
+ float: left;
+ width: 50%;
+ padding: 2px 2px 2px 2px;
+ font-size: 1em;
+ line-height: 1em;
+ text-align: left;
+ margin-right: 5px;
+}
+
+.topbox {
+ border: 1px solid #ccc;
+ width: 50%;
+ line-height: 1em;
+ text-align: left;
+}
+
+table.topheading { width: 100%; }
+.topheading th {
+ background-color: #ddf;
+}
+
+.topheading th, .topheading td {
+padding: 8px;
+}
+
+p.todo, div.todo { background-color: #fdd; padding: 6px; margin: 12px; border-left: #d99 6px solid; }
+
+.newsitem p { margin: 12px 0; }
+.newsitem ul { list-style: circle; margin-left: 30px; }
+.newsitem ul li { margin: 8px; }
+
+.newsitem {
+ max-width: 600px;
+ line-height: 125%;
+}
+.newsitem .newsdate {
+ margin: 4px 0 8px 0; text-align: right;
+ font-size: 80%; color: navy;
+}
+
+.menublockopener { margin-left: 0.25em; color: #bbb !important; font-weight: normal !important; }
+.menublock { background-color: #f2e4cc; font-size: 95%; padding: 1px 4px; }
+
+#coursebanner { background-color: #f2e4cc; margin: -12px -12px 12px -12px; padding: 8px; }
+#coursesearch { float: right; }
+#coursebanner h1 { margin: 12px 0; font-size: 125%; }
+
+#edit_course_link { margin: 8px 0 8px 0; font-size: 95%; }
+
+
+#breadcrumbs { margin: 8px 0px 16px 0; width: 716px;
+ text-indent: -24px; padding-left: 24px; }
+
+.errorlist { float: right; }
+.errorlist li { color: red; font-size: 90%; }
+
+
+/* a nice table-style for forms. */
+.formtable tbody th {
+ padding: 0 8px 16px 0;
+ width: 200px;
+ text-align: left;
+ font-size: 90%;
+ font-weight: normal;
+}
+
+thead th { padding: 8px; font-weight: bold; font-size: 90%; }
+.metadata_table tbody th,
+.metadata_table tbody td {
+ padding: 8px; border: #ddd 1px solid;
+}
+.metadata_table input { width: 600px; }
+.metadata_table .meta3 input { width: 10px; }
+
+.metadata_table tbody th {
+ background-color: #eee;
+}
+
+.metadata_table a.bigdownload { padding: 8px 58px; font-weight: bold; font-size: 105%; }
+.metadata_table a.bigdownload:hover { background-color: #dfd; color: black; }
+
+h2.metadata_subhead {font-size: 105%; padding: 0; margin: 18px 0 9px 0;}
+
+.metadata_table tbody th {
+ text-align: left; width: 120px;
+}
+.gap { height: 24px; }
+.metadata_table td { max-width: 800px; overflow: hidden; }
+
+/* panels that appear when specific OPTIONs or radio-buttons are selected. */
+.specific { padding: 8px; margin: 0 16px; background-color: #eef; }
+
+
+li.sort_item { margin-top: 20px !important;
+ border: gray 1px dotted; width: 400px; }
+
+li.sort_item:hover { background-color: #eee; }
+
+ul.heading_tree li { list-style: none; }
+ul.heading_tree { margin: 0; padding-left: 0; }
+ul.heading_tree ul { margin: 0; padding-left: 25px; }
+
Modified: servres/trunk/conifer/static/xslt/test.xsl
===================================================================
--- servres/trunk/conifer/static/xslt/test.xsl 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/static/xslt/test.xsl 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,18 +1,18 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-libxslt needs explicit namespaces
--->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-xmlns:marc="http://www.loc.gov/MARC21/slim" version="1.0">
-
-<xsl:template match="text()"/>
-
-<xsl:template match="/marc:record">
- <xsl:apply-templates/>
-</xsl:template>
-
-<xsl:template match="marc:datafield[@tag='245']">
-<xsl:value-of select="marc:subfield[@code='a']"/>
-</xsl:template>
-
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+libxslt needs explicit namespaces
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+xmlns:marc="http://www.loc.gov/MARC21/slim" version="1.0">
+
+<xsl:template match="text()"/>
+
+<xsl:template match="/marc:record">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="marc:datafield[@tag='245']">
+<xsl:value-of select="marc:subfield[@code='a']"/>
+</xsl:template>
+
</xsl:stylesheet>
\ No newline at end of file
Modified: servres/trunk/conifer/syrup/urls.py
===================================================================
--- servres/trunk/conifer/syrup/urls.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/syrup/urls.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,63 +1,63 @@
-from django.conf.urls.defaults import *
-
-# I'm not ready to break items out into their own urls.py, but I do
-# want to cut down on the common boilerplate in the urlpatterns below.
-
-ITEM_PREFIX = r'^course/(?P<course_id>\d+)/item/(?P<item_id>\d+)/'
-GENERIC_REGEX = r'((?P<obj_id>\d+)/)?(?P<action>.+)?$'
-
-urlpatterns = patterns('conifer.syrup.views',
- (r'^$', 'welcome'),
- (r'^course/$', 'my_courses'),
- (r'^course/new/$', 'add_new_course'),
- (r'^course/new/ajax_title$', 'add_new_course_ajax_title'),
- (r'^course/invitation/$', 'course_invitation'),
- (r'^browse/$', 'browse'),
- (r'^browse/(?P<browse_option>.*)/$', 'browse'),
- (r'^prefs/$', 'user_prefs'),
- (r'^z3950test/$', 'z3950_test'),
- #MARK: propose we kill open_courses, we have browse.
- (r'^opencourse/$', 'open_courses'),
- (r'^search/$', 'search'),
- (r'^zsearch/$', 'zsearch'),
- #MARK: propose we kill instructors, we have browse
- (r'^instructors/$', 'instructors'),
- (r'^instructors/search/(?P<instructor>.*)$', 'instructor_search'),
- #MARK: propose we kill departments, we have browse
- (r'^departments/$', 'departments'),
- (r'^course/(?P<course_id>\d+)/$', 'course_detail'),
- (r'^instructor/(?P<instructor_id>.*)/$', 'instructor_detail'),
- (r'^department/(?P<department_id>.*)/$', 'department_detail'),
- (r'^course/(?P<course_id>\d+)/search/$', 'course_search'),
- (r'^course/(?P<course_id>\d+)/edit/$', 'edit_course'),
- (r'^course/(?P<course_id>\d+)/edit/delete/$', 'delete_course'),
- (r'^course/(?P<course_id>\d+)/edit/permission/$', 'edit_course_permissions'),
- (r'^course/(?P<course_id>\d+)/feeds/(?P<feed_type>.*)$', 'course_feeds'),
- (r'^course/(?P<course_id>\d+)/join/$', 'course_join'),
- (ITEM_PREFIX + r'$', 'item_detail'),
- (ITEM_PREFIX + r'dl/(?P<filename>.*)$', 'item_download'),
- (ITEM_PREFIX + r'meta$', 'item_metadata'),
- (ITEM_PREFIX + r'edit/$', 'item_edit'),
- (ITEM_PREFIX + r'delete/$', 'item_delete'),
- (ITEM_PREFIX + r'add/$', 'item_add'), # for adding sub-things
- (ITEM_PREFIX + r'add/cat_search/$', 'item_add_cat_search'),
-
- (r'^admin/$', 'admin_index'),
- (r'^admin/terms/' + GENERIC_REGEX, 'admin_terms'),
- (r'^admin/depts/' + GENERIC_REGEX, 'admin_depts'),
- (r'^admin/news/' + GENERIC_REGEX, 'admin_news'),
- (r'^admin/targets/' + GENERIC_REGEX, 'admin_targets'),
-
- (r'^phys/$', 'phys_index'),
- (r'^phys/checkout/$', 'phys_checkout'),
- (r'^phys/mark_arrived/$', 'phys_mark_arrived'),
- (r'^phys/mark_arrived/match/$', 'phys_mark_arrived_match'),
- (r'^phys/circlist/$', 'phys_circlist'),
-
- (r'^course/(?P<course_id>\d+)/reseq$', 'course_reseq'),
- (ITEM_PREFIX + r'reseq', 'item_heading_reseq'),
- (ITEM_PREFIX + r'relocate/', 'item_relocate'), # move to new subheading
-# (r'^admin/terms/(?P<term_id>\d+)/$', 'admin_term_edit'),
-# (r'^admin/terms/(?P<term_id>\d+)/delete$', 'admin_term_delete'),
-# (r'^admin/terms/$', 'admin_term'),
-)
+from django.conf.urls.defaults import *
+
+# I'm not ready to break items out into their own urls.py, but I do
+# want to cut down on the common boilerplate in the urlpatterns below.
+
+ITEM_PREFIX = r'^course/(?P<course_id>\d+)/item/(?P<item_id>\d+)/'
+GENERIC_REGEX = r'((?P<obj_id>\d+)/)?(?P<action>.+)?$'
+
+urlpatterns = patterns('conifer.syrup.views',
+ (r'^$', 'welcome'),
+ (r'^course/$', 'my_courses'),
+ (r'^course/new/$', 'add_new_course'),
+ (r'^course/new/ajax_title$', 'add_new_course_ajax_title'),
+ (r'^course/invitation/$', 'course_invitation'),
+ (r'^browse/$', 'browse'),
+ (r'^browse/(?P<browse_option>.*)/$', 'browse'),
+ (r'^prefs/$', 'user_prefs'),
+ (r'^z3950test/$', 'z3950_test'),
+ #MARK: propose we kill open_courses, we have browse.
+ (r'^opencourse/$', 'open_courses'),
+ (r'^search/$', 'search'),
+ (r'^zsearch/$', 'zsearch'),
+ #MARK: propose we kill instructors, we have browse
+ (r'^instructors/$', 'instructors'),
+ (r'^instructors/search/(?P<instructor>.*)$', 'instructor_search'),
+ #MARK: propose we kill departments, we have browse
+ (r'^departments/$', 'departments'),
+ (r'^course/(?P<course_id>\d+)/$', 'course_detail'),
+ (r'^instructor/(?P<instructor_id>.*)/$', 'instructor_detail'),
+ (r'^department/(?P<department_id>.*)/$', 'department_detail'),
+ (r'^course/(?P<course_id>\d+)/search/$', 'course_search'),
+ (r'^course/(?P<course_id>\d+)/edit/$', 'edit_course'),
+ (r'^course/(?P<course_id>\d+)/edit/delete/$', 'delete_course'),
+ (r'^course/(?P<course_id>\d+)/edit/permission/$', 'edit_course_permissions'),
+ (r'^course/(?P<course_id>\d+)/feeds/(?P<feed_type>.*)$', 'course_feeds'),
+ (r'^course/(?P<course_id>\d+)/join/$', 'course_join'),
+ (ITEM_PREFIX + r'$', 'item_detail'),
+ (ITEM_PREFIX + r'dl/(?P<filename>.*)$', 'item_download'),
+ (ITEM_PREFIX + r'meta$', 'item_metadata'),
+ (ITEM_PREFIX + r'edit/$', 'item_edit'),
+ (ITEM_PREFIX + r'delete/$', 'item_delete'),
+ (ITEM_PREFIX + r'add/$', 'item_add'), # for adding sub-things
+ (ITEM_PREFIX + r'add/cat_search/$', 'item_add_cat_search'),
+
+ (r'^admin/$', 'admin_index'),
+ (r'^admin/terms/' + GENERIC_REGEX, 'admin_terms'),
+ (r'^admin/depts/' + GENERIC_REGEX, 'admin_depts'),
+ (r'^admin/news/' + GENERIC_REGEX, 'admin_news'),
+ (r'^admin/targets/' + GENERIC_REGEX, 'admin_targets'),
+
+ (r'^phys/$', 'phys_index'),
+ (r'^phys/checkout/$', 'phys_checkout'),
+ (r'^phys/mark_arrived/$', 'phys_mark_arrived'),
+ (r'^phys/mark_arrived/match/$', 'phys_mark_arrived_match'),
+ (r'^phys/circlist/$', 'phys_circlist'),
+
+ (r'^course/(?P<course_id>\d+)/reseq$', 'course_reseq'),
+ (ITEM_PREFIX + r'reseq', 'item_heading_reseq'),
+ (ITEM_PREFIX + r'relocate/', 'item_relocate'), # move to new subheading
+# (r'^admin/terms/(?P<term_id>\d+)/$', 'admin_term_edit'),
+# (r'^admin/terms/(?P<term_id>\d+)/delete$', 'admin_term_delete'),
+# (r'^admin/terms/$', 'admin_term'),
+)
Modified: servres/trunk/conifer/syrup/views/general.py
===================================================================
--- servres/trunk/conifer/syrup/views/general.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/syrup/views/general.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,165 +1,165 @@
-from _common import *
-from django.utils.translation import ugettext as _
-from search import *
-#from lxml import etree
-#import libxml2
-#import libxslt
-import os
-
-
-BASE_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
-HERE = lambda s: os.path.join(BASE_DIRECTORY, s)
-
-
-#-----------------------------------------------------------------------------
-
-def welcome(request):
- return g.render('welcome.xhtml')
-
-# MARK: propose we get rid of this. We already have a 'Courses' browser.
-def open_courses(request):
- page_num = int(request.GET.get('page', 1))
- count = int(request.GET.get('count', 5))
- paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
- return g.render('open_courses.xhtml', paginator=paginator,
- page_num=page_num,
- count=count)
-# MARK: propose we drop this too. We have a browse.
-def instructors(request):
- page_num = int(request.GET.get('page', 1))
- count = int(request.GET.get('count', 5))
- action = request.GET.get('action', 'browse')
- if action == 'join':
- paginator = Paginator(models.User.active_instructors(), count)
- elif action == 'drop':
- paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
- else:
- paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
-
- return g.render('instructors.xhtml', paginator=paginator,
- page_num=page_num,
- count=count)
-
-def instructor_search(request, instructor):
- return search(request, with_instructor=instructor)
-
-# MARK: propose we get rid of this. We have browse.
-def departments(request):
- raise NotImplementedError
-
-
-def user_prefs(request):
- if request.method != 'POST':
- return g.render('prefs.xhtml')
- else:
- profile = request.user.get_profile()
- profile.wants_email_notices = bool(request.POST.get('wants_email_notices'))
- profile.save()
- return HttpResponseRedirect('../')
-
-def z3950_test(request):
- #z39.50 testing area
-
-
- styledoc = libxml2.parseFile(HERE('../../static/xslt/test.xsl'))
- stylexsl = libxslt.parseStylesheetDoc(styledoc)
-
- #testing JZKitZ3950 - it seems to work, but i have a character set problem
- #with the returned marc
- #nope - the problem is weak mapping with the limited solr test set
- #i think this can be sorted out
-
- #conn = zoom.Connection ('z3950.loc.gov', 7090)
- conn = zoom.Connection ('zed.concat.ca', 210)
- print("connecting...")
- conn.databaseName = 'OWA'
- conn.preferredRecordSyntax = 'XML'
- # conn.preferredRecordSyntax = 'USMARC'
- query = zoom.Query ('CCL', 'ti="agar"')
- res = conn.search (query)
- collector = []
- # if we wanted to get into funkiness
- m = zmarc.MARC8_to_Unicode ()
- for r in res:
- #print(type(r.data))
- #print(type(m.translate(r.data)))
- zhit = str("<?xml version=\"1.0\"?>") + (m.translate(r.data))
- #doc = libxml2.parseDoc(zhit)
- #print(stylexsl.applyStylesheet(doc, None))
-
- conn.close ()
- res_str = "" . join(collector)
- return g.render('z3950_test.xhtml', res_str=res_str)
-
-def browse(request, browse_option=''):
- #the defaults should be moved into a config file or something...
- page_num = int(request.GET.get('page', 1))
- count = int(request.GET.get('count', 5))
-
- if browse_option == '':
- queryset = None
- template = 'browse_index.xhtml'
- elif browse_option == 'instructors':
- queryset = models.User.active_instructors()
- queryset = queryset.filter(user_filters(request.user)['instructors'])
- template = 'instructors.xhtml'
- elif browse_option == 'departments':
- queryset = models.Department.objects.filter(active=True)
- template = 'departments.xhtml'
- elif browse_option == 'courses':
- # fixme, course filter should not be (active=True) but based on user identity.
- for_courses = user_filters(request.user)['courses']
- queryset = models.Course.objects.filter(for_courses)
- template = 'courses.xhtml'
-
- queryset = queryset and queryset.distinct()
- paginator = Paginator(queryset, count)
- return g.render(template, paginator=paginator,
- page_num=page_num,
- count=count)
-
- at login_required
-def my_courses(request):
- return g.render('my_courses.xhtml')
-
-def instructor_detail(request, instructor_id):
- page_num = int(request.GET.get('page', 1))
- count = int(request.GET.get('count', 5))
- '''
- i am not sure this is the best way to go from instructor
- to course
- '''
- courses = models.Course.objects.filter(member__user=instructor_id,
- member__role='INSTR')
- filters = user_filters(request.user)
- courses = courses.filter(filters['courses'])
- paginator = Paginator(courses.order_by('title'), count)
-
- '''
- no concept of active right now, maybe suppressed is a better
- description anyway?
- '''
- # filter(active=True).order_by('title'), count)
- instructor = models.User.objects.get(pk=instructor_id)
- return g.render('courses.xhtml',
- custom_title=_('Courses taught by %s') % instructor.get_full_name(),
- paginator=paginator,
- page_num=page_num,
- count=count)
-
-def department_detail(request, department_id):
- page_num = int(request.GET.get('page', 1))
- count = int(request.GET.get('count', 5))
-
- paginator = Paginator(models.Course.objects.
- filter(department__id=department_id).
- order_by('title'), count)
-
- department = models.Department.objects.get(pk=department_id)
-
- return g.render('courses.xhtml',
- custom_title=_('Courses with Materials in %s') % department.name,
- paginator=paginator,
- page_num=page_num,
- count=count)
-
+from _common import *
+from django.utils.translation import ugettext as _
+from search import *
+#from lxml import etree
+#import libxml2
+#import libxslt
+import os
+
+
+BASE_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
+HERE = lambda s: os.path.join(BASE_DIRECTORY, s)
+
+
+#-----------------------------------------------------------------------------
+
+def welcome(request):
+ return g.render('welcome.xhtml')
+
+# MARK: propose we get rid of this. We already have a 'Courses' browser.
+def open_courses(request):
+ page_num = int(request.GET.get('page', 1))
+ count = int(request.GET.get('count', 5))
+ paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
+ return g.render('open_courses.xhtml', paginator=paginator,
+ page_num=page_num,
+ count=count)
+# MARK: propose we drop this too. We have a browse.
+def instructors(request):
+ page_num = int(request.GET.get('page', 1))
+ count = int(request.GET.get('count', 5))
+ action = request.GET.get('action', 'browse')
+ if action == 'join':
+ paginator = Paginator(models.User.active_instructors(), count)
+ elif action == 'drop':
+ paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
+ else:
+ paginator = Paginator(models.Course.objects.all(), count) # fixme, what filter?
+
+ return g.render('instructors.xhtml', paginator=paginator,
+ page_num=page_num,
+ count=count)
+
+def instructor_search(request, instructor):
+ return search(request, with_instructor=instructor)
+
+# MARK: propose we get rid of this. We have browse.
+def departments(request):
+ raise NotImplementedError
+
+
+def user_prefs(request):
+ if request.method != 'POST':
+ return g.render('prefs.xhtml')
+ else:
+ profile = request.user.get_profile()
+ profile.wants_email_notices = bool(request.POST.get('wants_email_notices'))
+ profile.save()
+ return HttpResponseRedirect('../')
+
+def z3950_test(request):
+ #z39.50 testing area
+
+
+ styledoc = libxml2.parseFile(HERE('../../static/xslt/test.xsl'))
+ stylexsl = libxslt.parseStylesheetDoc(styledoc)
+
+ #testing JZKitZ3950 - it seems to work, but i have a character set problem
+ #with the returned marc
+ #nope - the problem is weak mapping with the limited solr test set
+ #i think this can be sorted out
+
+ #conn = zoom.Connection ('z3950.loc.gov', 7090)
+ conn = zoom.Connection ('zed.concat.ca', 210)
+ print("connecting...")
+ conn.databaseName = 'OWA'
+ conn.preferredRecordSyntax = 'XML'
+ # conn.preferredRecordSyntax = 'USMARC'
+ query = zoom.Query ('CCL', 'ti="agar"')
+ res = conn.search (query)
+ collector = []
+ # if we wanted to get into funkiness
+ m = zmarc.MARC8_to_Unicode ()
+ for r in res:
+ #print(type(r.data))
+ #print(type(m.translate(r.data)))
+ zhit = str("<?xml version=\"1.0\"?>") + (m.translate(r.data))
+ #doc = libxml2.parseDoc(zhit)
+ #print(stylexsl.applyStylesheet(doc, None))
+
+ conn.close ()
+ res_str = "" . join(collector)
+ return g.render('z3950_test.xhtml', res_str=res_str)
+
+def browse(request, browse_option=''):
+ #the defaults should be moved into a config file or something...
+ page_num = int(request.GET.get('page', 1))
+ count = int(request.GET.get('count', 5))
+
+ if browse_option == '':
+ queryset = None
+ template = 'browse_index.xhtml'
+ elif browse_option == 'instructors':
+ queryset = models.User.active_instructors()
+ queryset = queryset.filter(user_filters(request.user)['instructors'])
+ template = 'instructors.xhtml'
+ elif browse_option == 'departments':
+ queryset = models.Department.objects.filter(active=True)
+ template = 'departments.xhtml'
+ elif browse_option == 'courses':
+ # fixme, course filter should not be (active=True) but based on user identity.
+ for_courses = user_filters(request.user)['courses']
+ queryset = models.Course.objects.filter(for_courses)
+ template = 'courses.xhtml'
+
+ queryset = queryset and queryset.distinct()
+ paginator = Paginator(queryset, count)
+ return g.render(template, paginator=paginator,
+ page_num=page_num,
+ count=count)
+
+ at login_required
+def my_courses(request):
+ return g.render('my_courses.xhtml')
+
+def instructor_detail(request, instructor_id):
+ page_num = int(request.GET.get('page', 1))
+ count = int(request.GET.get('count', 5))
+ '''
+ i am not sure this is the best way to go from instructor
+ to course
+ '''
+ courses = models.Course.objects.filter(member__user=instructor_id,
+ member__role='INSTR')
+ filters = user_filters(request.user)
+ courses = courses.filter(filters['courses'])
+ paginator = Paginator(courses.order_by('title'), count)
+
+ '''
+ no concept of active right now, maybe suppressed is a better
+ description anyway?
+ '''
+ # filter(active=True).order_by('title'), count)
+ instructor = models.User.objects.get(pk=instructor_id)
+ return g.render('courses.xhtml',
+ custom_title=_('Courses taught by %s') % instructor.get_full_name(),
+ paginator=paginator,
+ page_num=page_num,
+ count=count)
+
+def department_detail(request, department_id):
+ page_num = int(request.GET.get('page', 1))
+ count = int(request.GET.get('count', 5))
+
+ paginator = Paginator(models.Course.objects.
+ filter(department__id=department_id).
+ order_by('title'), count)
+
+ department = models.Department.objects.get(pk=department_id)
+
+ return g.render('courses.xhtml',
+ custom_title=_('Courses with Materials in %s') % department.name,
+ paginator=paginator,
+ page_num=page_num,
+ count=count)
+
Modified: servres/trunk/conifer/syrup/views/items.py
===================================================================
--- servres/trunk/conifer/syrup/views/items.py 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/syrup/views/items.py 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,510 +1,510 @@
-from _common import *
-from django.utils.translation import ugettext as _
-
- at members_only
-def item_detail(request, course_id, item_id):
- """Display an item (however that makes sense)."""
- # really, displaying an item will vary based on what type of item
- # it is -- e.g. a URL item would redirect to the target URL. I'd
- # like this URL to be the generic dispatcher, but for now let's
- # just display some metadata about the item.
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- if item.url:
- return _heading_url(request, item)
- else:
- return item_metadata(request, course_id, item_id)
-
- at members_only
-def item_metadata(request, course_id, item_id):
- """Display a metadata page for the item."""
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- if item.item_type == 'HEADING':
- return _heading_detail(request, item)
- else:
- return g.render('item/item_metadata.xhtml', course=item.course,
- item=item)
-
-def _heading_url(request, item):
- return HttpResponseRedirect(item.url)
-
-def _heading_detail(request, item):
- """Display a heading. Show the subitems for this heading."""
- return g.render('item/item_heading_detail.xhtml', item=item)
-
-
- at instructors_only
-def item_add(request, course_id, item_id):
- # The parent_item_id is the id for the parent-heading item. Zero
- # represents 'top-level', i.e. the new item should have no
- # heading.
- #For any other number, we must check that the parent
- # item is of the Heading type.
- parent_item_id = item_id
- if parent_item_id=='0':
- parent_item = None
- course = get_object_or_404(models.Course, pk=course_id)
- siblings = course.item_set.filter(parent_heading=None)
- else:
- parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
- assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
- course = parent_item.course
- siblings = course.item_set.filter(parent_heading=parent_item)
-
- try:
- next_order = 1 + max(i.sort_order for i in siblings)
- except:
- next_order = 0
- if not course.can_edit(request.user):
- return _access_denied(_('You are not an editor.'))
-
- item_type = request.GET.get('item_type')
- assert item_type, _('No item_type parameter was provided.')
-
- # for the moment, only HEADINGs, URLs and ELECs can be added. fixme.
- assert item_type in ('HEADING', 'URL', 'ELEC', 'PHYS'), \
- _('Sorry, only HEADINGs, URLs and ELECs can be added right now.')
-
- if request.method != 'POST' and item_type == 'PHYS':
- # special handling: send to catalogue search
- return HttpResponseRedirect('cat_search/')
-
- if request.method != 'POST':
- item = models.Item() # dummy object
- metadata_formset = metadata_formset_class(queryset=item.metadata_set.all())
- return g.render('item/item_add_%s.xhtml' % item_type.lower(),
- **locals())
- else:
- # fixme, this will need refactoring. But not yet.
- author = request.user.get_full_name() or request.user.username
- item = models.Item() # dummy object
- metadata_formset = metadata_formset_class(request.POST, queryset=item.metadata_set.all())
- assert metadata_formset.is_valid()
- def do_metadata(item):
- for obj in [obj for obj in metadata_formset.cleaned_data if obj]: # ignore empty dicts
- if not obj.get('DELETE'):
- item.metadata_set.create(name=obj['name'], value=obj['value'])
-
- if item_type == 'HEADING':
- title = request.POST.get('title', '').strip()
- if not title:
- # fixme, better error handling.
- return HttpResponseRedirect(request.get_full_path())
- else:
- item = models.Item(
- course=course,
- item_type='HEADING',
- sort_order = next_order,
- parent_heading=parent_item,
- title=title,
- )
- item.save()
- do_metadata(item)
- item.save()
- elif item_type == 'URL':
- title = request.POST.get('title', '').strip()
- url = request.POST.get('url', '').strip()
- if not (title and url):
- # fixme, better error handling.
- return HttpResponseRedirect(request.get_full_path())
- else:
- item = models.Item(
- course=course,
- item_type='URL',
- parent_heading=parent_item,
- sort_order = next_order,
- title=title,
- url = url)
- item.save()
- do_metadata(item)
- item.save()
- elif item_type == 'ELEC':
- title = request.POST.get('title', '').strip()
- upload = request.FILES.get('file')
- if not (title and upload):
- # fixme, better error handling.
- return HttpResponseRedirect(request.get_full_path())
- item = models.Item(
- course=course,
- item_type='ELEC',
- parent_heading=parent_item,
- sort_order = next_order,
- title=title,
- fileobj_mimetype = upload.content_type,
- )
- item.fileobj.save(upload.name, upload)
- item.save()
- do_metadata(item)
- item.save()
- else:
- raise NotImplementedError
-
- if parent_item:
- return HttpResponseRedirect(parent_item.item_url('meta'))
- else:
- return HttpResponseRedirect(course.course_url())
-
- at instructors_only
-def item_add_cat_search(request, course_id, item_id):
- # this chunk stolen from item_add(). Refactor.
- parent_item_id = item_id
- if parent_item_id=='0':
- parent_item = None
- course = get_object_or_404(models.Course, pk=course_id)
- siblings = course.item_set.filter(parent_heading=None)
- else:
- parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
- assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
- course = parent_item.course
- siblings = course.item_set.filter(parent_heading=parent_item)
-
- try:
- next_order = 1 + max(i.sort_order for i in siblings)
- except:
- next_order = 0
-
- #----------
-
- if request.method != 'POST':
- if not 'query' in request.GET:
- return g.render('item/item_add_cat_search.xhtml', results=[], query='',
- course=course, 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)
- return g.render('item/item_add_cat_search.xhtml',
- results=results, query=query,
- start=start, limit=limit, numhits=numhits,
- course=course, parent_item=parent_item)
- else:
- # User has selected an item; add it to course site.
- raw_pickitem = request.POST.get('pickitem', '').strip()
- #fixme, this block copied from item_add. refactor.
- parent_item_id = item_id
- if parent_item_id == '0':
- # no heading (toplevel)
- parent_item = None
- course = get_object_or_404(models.Course, pk=course_id)
- else:
- parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
- assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
- course = parent_item.course
- if not course.can_edit(request.user):
- return _access_denied(_('You are not an editor.'))
-
- pickitem = simplejson.loads(raw_pickitem)
- dublin = marcxml_dictionary_to_dc(pickitem)
-
- # one last thing. If this picked item has an 856$9 field, then
- # it's an electronic resource, not a physical item. In that
- # case, we add it as a URL, not a PHYS.
- if '8569' in pickitem:
- dct = dict(item_type='URL', url=pickitem.get('856u'))
- else:
- dct = dict(item_type='PHYS')
-
- item = course.item_set.create(parent_heading=parent_item,
- sort_order=next_order,
- title=dublin.get('dc:title','Untitled'),
- **dct)
- item.save()
-
- for dc, value in dublin.items():
- md = item.metadata_set.create(item=item, name=dc, value=value)
- # store the whole darn MARC-dict as well (JSON)
- item.metadata_set.create(item=item, name='syrup:marc', value=raw_pickitem)
- item.save()
- return HttpResponseRedirect('../../../%d/meta' % item.id)
-
-#------------------------------------------------------------
-
-#this is used in item_edit.
-metadata_formset_class = modelformset_factory(models.Metadata,
- fields=['name','value'],
- extra=3, can_delete=True)
-
- at instructors_only
-def item_edit(request, course_id, item_id):
- course = get_object_or_404(models.Course, pk=course_id)
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- item_type = item.item_type
- template = 'item/item_add_%s.xhtml' % item_type.lower()
- parent_item = item.parent_heading
-
- if request.method != 'POST':
- metadata_formset = metadata_formset_class(queryset=item.metadata_set.all())
- return g.render(template, **locals())
- else:
- metadata_formset = metadata_formset_class(request.POST, queryset=item.metadata_set.all())
- assert metadata_formset.is_valid()
- if 'file' in request.FILES:
- # this is a 'replace-current-file' action.
- upload = request.FILES.get('file')
- item.fileobj.save(upload.name, upload)
- item.fileobj_mimetype = upload.content_type
- else:
- # generally update the item.
- [setattr(item, k, v) for (k,v) in request.POST.items()]
- # generally update the metadata
- item.metadata_set.all().delete()
- for obj in [obj for obj in metadata_formset.cleaned_data if obj]: # ignore empty dicts
- if not obj.get('DELETE'):
- item.metadata_set.create(name=obj['name'], value=obj['value'])
-
- item.save()
- return HttpResponseRedirect(item.parent_url())
-
- at instructors_only
-def item_delete(request, course_id, item_id):
- course = get_object_or_404(models.Course, pk=course_id)
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- if request.method != 'POST':
- return g.render('item/item_delete_confirm.xhtml', **locals())
- else:
- if 'yes' in request.POST:
- # I think Django's ON DELETE CASCADE-like behaviour will
- # take care of the sub-items.
- if item.parent_heading:
- redir = HttpResponseRedirect(item.parent_heading.item_url('meta'))
- else:
- redir = HttpResponseRedirect(course.course_url())
- item.delete()
- return redir
- else:
- return HttpResponseRedirect('../meta')
-
- at members_only
-def item_download(request, course_id, item_id, filename):
- course = get_object_or_404(models.Course, pk=course_id)
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- assert item.item_type == 'ELEC', _('Can only download ELEC documents!')
- fileiter = item.fileobj.chunks()
- resp = HttpResponse(fileiter)
- resp['Content-Type'] = item.fileobj_mimetype or 'application/octet-stream'
- #resp['Content-Disposition'] = 'attachment; filename=%s' % name
- return resp
-
-
-
-#------------------------------------------------------------
-# resequencing items
-
-def _reseq(request, course, parent_heading):
- new_order = request.POST['new_order'].strip().split(' ')
- # new_order is now a list like this: ['item_3', 'item_8', 'item_1', ...].
- # get at the ints.
- new_order = [int(n.split('_')[1]) for n in new_order]
- print >> sys.stderr, new_order
- the_items = list(course.item_set.filter(parent_heading=parent_heading).order_by('sort_order'))
- # sort the items by position in new_order
- the_items.sort(key=lambda item: new_order.index(item.id))
- for newnum, item in enumerate(the_items):
- item.sort_order = newnum
- item.save()
- return HttpResponse("'ok'");
-
- at instructors_only
-def course_reseq(request, course_id):
- course = get_object_or_404(models.Course, pk=course_id)
- parent_heading = None
- return _reseq(request, course, parent_heading)
-
- at instructors_only
-def item_heading_reseq(request, course_id, item_id):
- course = get_object_or_404(models.Course, pk=course_id)
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- parent_heading = item
- return _reseq(request, course, parent_heading)
-
-
- at instructors_only
-def item_relocate(request, course_id, item_id):
- """Move an item from its current subheading to another one."""
- course = get_object_or_404(models.Course, pk=course_id)
- item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
- if request.method != 'POST':
- return g.render('item/item_relocate.xhtml', **locals())
- else:
- newheading = int(request.POST['heading'])
- if newheading == 0:
- new_parent = None
- else:
- new_parent = course.item_set.get(pk=newheading)
- if item in new_parent.hierarchy():
- # then we would create a cycle. Bail out.
- return simple_message(_('Impossible item-move!'),
- _('You cannot make an item a descendant of itself!'))
- item.parent_heading = new_parent
- item.save()
- if new_parent:
- return HttpResponseRedirect(new_parent.item_url('meta'))
- else:
- return HttpResponseRedirect(course.course_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', course__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)
-
-
+from _common import *
+from django.utils.translation import ugettext as _
+
+ at members_only
+def item_detail(request, course_id, item_id):
+ """Display an item (however that makes sense)."""
+ # really, displaying an item will vary based on what type of item
+ # it is -- e.g. a URL item would redirect to the target URL. I'd
+ # like this URL to be the generic dispatcher, but for now let's
+ # just display some metadata about the item.
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ if item.url:
+ return _heading_url(request, item)
+ else:
+ return item_metadata(request, course_id, item_id)
+
+ at members_only
+def item_metadata(request, course_id, item_id):
+ """Display a metadata page for the item."""
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ if item.item_type == 'HEADING':
+ return _heading_detail(request, item)
+ else:
+ return g.render('item/item_metadata.xhtml', course=item.course,
+ item=item)
+
+def _heading_url(request, item):
+ return HttpResponseRedirect(item.url)
+
+def _heading_detail(request, item):
+ """Display a heading. Show the subitems for this heading."""
+ return g.render('item/item_heading_detail.xhtml', item=item)
+
+
+ at instructors_only
+def item_add(request, course_id, item_id):
+ # The parent_item_id is the id for the parent-heading item. Zero
+ # represents 'top-level', i.e. the new item should have no
+ # heading.
+ #For any other number, we must check that the parent
+ # item is of the Heading type.
+ parent_item_id = item_id
+ if parent_item_id=='0':
+ parent_item = None
+ course = get_object_or_404(models.Course, pk=course_id)
+ siblings = course.item_set.filter(parent_heading=None)
+ else:
+ parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
+ assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
+ course = parent_item.course
+ siblings = course.item_set.filter(parent_heading=parent_item)
+
+ try:
+ next_order = 1 + max(i.sort_order for i in siblings)
+ except:
+ next_order = 0
+ if not course.can_edit(request.user):
+ return _access_denied(_('You are not an editor.'))
+
+ item_type = request.GET.get('item_type')
+ assert item_type, _('No item_type parameter was provided.')
+
+ # for the moment, only HEADINGs, URLs and ELECs can be added. fixme.
+ assert item_type in ('HEADING', 'URL', 'ELEC', 'PHYS'), \
+ _('Sorry, only HEADINGs, URLs and ELECs can be added right now.')
+
+ if request.method != 'POST' and item_type == 'PHYS':
+ # special handling: send to catalogue search
+ return HttpResponseRedirect('cat_search/')
+
+ if request.method != 'POST':
+ item = models.Item() # dummy object
+ metadata_formset = metadata_formset_class(queryset=item.metadata_set.all())
+ return g.render('item/item_add_%s.xhtml' % item_type.lower(),
+ **locals())
+ else:
+ # fixme, this will need refactoring. But not yet.
+ author = request.user.get_full_name() or request.user.username
+ item = models.Item() # dummy object
+ metadata_formset = metadata_formset_class(request.POST, queryset=item.metadata_set.all())
+ assert metadata_formset.is_valid()
+ def do_metadata(item):
+ for obj in [obj for obj in metadata_formset.cleaned_data if obj]: # ignore empty dicts
+ if not obj.get('DELETE'):
+ item.metadata_set.create(name=obj['name'], value=obj['value'])
+
+ if item_type == 'HEADING':
+ title = request.POST.get('title', '').strip()
+ if not title:
+ # fixme, better error handling.
+ return HttpResponseRedirect(request.get_full_path())
+ else:
+ item = models.Item(
+ course=course,
+ item_type='HEADING',
+ sort_order = next_order,
+ parent_heading=parent_item,
+ title=title,
+ )
+ item.save()
+ do_metadata(item)
+ item.save()
+ elif item_type == 'URL':
+ title = request.POST.get('title', '').strip()
+ url = request.POST.get('url', '').strip()
+ if not (title and url):
+ # fixme, better error handling.
+ return HttpResponseRedirect(request.get_full_path())
+ else:
+ item = models.Item(
+ course=course,
+ item_type='URL',
+ parent_heading=parent_item,
+ sort_order = next_order,
+ title=title,
+ url = url)
+ item.save()
+ do_metadata(item)
+ item.save()
+ elif item_type == 'ELEC':
+ title = request.POST.get('title', '').strip()
+ upload = request.FILES.get('file')
+ if not (title and upload):
+ # fixme, better error handling.
+ return HttpResponseRedirect(request.get_full_path())
+ item = models.Item(
+ course=course,
+ item_type='ELEC',
+ parent_heading=parent_item,
+ sort_order = next_order,
+ title=title,
+ fileobj_mimetype = upload.content_type,
+ )
+ item.fileobj.save(upload.name, upload)
+ item.save()
+ do_metadata(item)
+ item.save()
+ else:
+ raise NotImplementedError
+
+ if parent_item:
+ return HttpResponseRedirect(parent_item.item_url('meta'))
+ else:
+ return HttpResponseRedirect(course.course_url())
+
+ at instructors_only
+def item_add_cat_search(request, course_id, item_id):
+ # this chunk stolen from item_add(). Refactor.
+ parent_item_id = item_id
+ if parent_item_id=='0':
+ parent_item = None
+ course = get_object_or_404(models.Course, pk=course_id)
+ siblings = course.item_set.filter(parent_heading=None)
+ else:
+ parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
+ assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
+ course = parent_item.course
+ siblings = course.item_set.filter(parent_heading=parent_item)
+
+ try:
+ next_order = 1 + max(i.sort_order for i in siblings)
+ except:
+ next_order = 0
+
+ #----------
+
+ if request.method != 'POST':
+ if not 'query' in request.GET:
+ return g.render('item/item_add_cat_search.xhtml', results=[], query='',
+ course=course, 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)
+ return g.render('item/item_add_cat_search.xhtml',
+ results=results, query=query,
+ start=start, limit=limit, numhits=numhits,
+ course=course, parent_item=parent_item)
+ else:
+ # User has selected an item; add it to course site.
+ raw_pickitem = request.POST.get('pickitem', '').strip()
+ #fixme, this block copied from item_add. refactor.
+ parent_item_id = item_id
+ if parent_item_id == '0':
+ # no heading (toplevel)
+ parent_item = None
+ course = get_object_or_404(models.Course, pk=course_id)
+ else:
+ parent_item = get_object_or_404(models.Item, pk=parent_item_id, course__id=course_id)
+ assert parent_item.item_type == 'HEADING', _('You can only add items to headings!')
+ course = parent_item.course
+ if not course.can_edit(request.user):
+ return _access_denied(_('You are not an editor.'))
+
+ pickitem = simplejson.loads(raw_pickitem)
+ dublin = marcxml_dictionary_to_dc(pickitem)
+
+ # one last thing. If this picked item has an 856$9 field, then
+ # it's an electronic resource, not a physical item. In that
+ # case, we add it as a URL, not a PHYS.
+ if '8569' in pickitem:
+ dct = dict(item_type='URL', url=pickitem.get('856u'))
+ else:
+ dct = dict(item_type='PHYS')
+
+ item = course.item_set.create(parent_heading=parent_item,
+ sort_order=next_order,
+ title=dublin.get('dc:title','Untitled'),
+ **dct)
+ item.save()
+
+ for dc, value in dublin.items():
+ md = item.metadata_set.create(item=item, name=dc, value=value)
+ # store the whole darn MARC-dict as well (JSON)
+ item.metadata_set.create(item=item, name='syrup:marc', value=raw_pickitem)
+ item.save()
+ return HttpResponseRedirect('../../../%d/meta' % item.id)
+
+#------------------------------------------------------------
+
+#this is used in item_edit.
+metadata_formset_class = modelformset_factory(models.Metadata,
+ fields=['name','value'],
+ extra=3, can_delete=True)
+
+ at instructors_only
+def item_edit(request, course_id, item_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ item_type = item.item_type
+ template = 'item/item_add_%s.xhtml' % item_type.lower()
+ parent_item = item.parent_heading
+
+ if request.method != 'POST':
+ metadata_formset = metadata_formset_class(queryset=item.metadata_set.all())
+ return g.render(template, **locals())
+ else:
+ metadata_formset = metadata_formset_class(request.POST, queryset=item.metadata_set.all())
+ assert metadata_formset.is_valid()
+ if 'file' in request.FILES:
+ # this is a 'replace-current-file' action.
+ upload = request.FILES.get('file')
+ item.fileobj.save(upload.name, upload)
+ item.fileobj_mimetype = upload.content_type
+ else:
+ # generally update the item.
+ [setattr(item, k, v) for (k,v) in request.POST.items()]
+ # generally update the metadata
+ item.metadata_set.all().delete()
+ for obj in [obj for obj in metadata_formset.cleaned_data if obj]: # ignore empty dicts
+ if not obj.get('DELETE'):
+ item.metadata_set.create(name=obj['name'], value=obj['value'])
+
+ item.save()
+ return HttpResponseRedirect(item.parent_url())
+
+ at instructors_only
+def item_delete(request, course_id, item_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ if request.method != 'POST':
+ return g.render('item/item_delete_confirm.xhtml', **locals())
+ else:
+ if 'yes' in request.POST:
+ # I think Django's ON DELETE CASCADE-like behaviour will
+ # take care of the sub-items.
+ if item.parent_heading:
+ redir = HttpResponseRedirect(item.parent_heading.item_url('meta'))
+ else:
+ redir = HttpResponseRedirect(course.course_url())
+ item.delete()
+ return redir
+ else:
+ return HttpResponseRedirect('../meta')
+
+ at members_only
+def item_download(request, course_id, item_id, filename):
+ course = get_object_or_404(models.Course, pk=course_id)
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ assert item.item_type == 'ELEC', _('Can only download ELEC documents!')
+ fileiter = item.fileobj.chunks()
+ resp = HttpResponse(fileiter)
+ resp['Content-Type'] = item.fileobj_mimetype or 'application/octet-stream'
+ #resp['Content-Disposition'] = 'attachment; filename=%s' % name
+ return resp
+
+
+
+#------------------------------------------------------------
+# resequencing items
+
+def _reseq(request, course, parent_heading):
+ new_order = request.POST['new_order'].strip().split(' ')
+ # new_order is now a list like this: ['item_3', 'item_8', 'item_1', ...].
+ # get at the ints.
+ new_order = [int(n.split('_')[1]) for n in new_order]
+ print >> sys.stderr, new_order
+ the_items = list(course.item_set.filter(parent_heading=parent_heading).order_by('sort_order'))
+ # sort the items by position in new_order
+ the_items.sort(key=lambda item: new_order.index(item.id))
+ for newnum, item in enumerate(the_items):
+ item.sort_order = newnum
+ item.save()
+ return HttpResponse("'ok'");
+
+ at instructors_only
+def course_reseq(request, course_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ parent_heading = None
+ return _reseq(request, course, parent_heading)
+
+ at instructors_only
+def item_heading_reseq(request, course_id, item_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ parent_heading = item
+ return _reseq(request, course, parent_heading)
+
+
+ at instructors_only
+def item_relocate(request, course_id, item_id):
+ """Move an item from its current subheading to another one."""
+ course = get_object_or_404(models.Course, pk=course_id)
+ item = get_object_or_404(models.Item, pk=item_id, course__id=course_id)
+ if request.method != 'POST':
+ return g.render('item/item_relocate.xhtml', **locals())
+ else:
+ newheading = int(request.POST['heading'])
+ if newheading == 0:
+ new_parent = None
+ else:
+ new_parent = course.item_set.get(pk=newheading)
+ if item in new_parent.hierarchy():
+ # then we would create a cycle. Bail out.
+ return simple_message(_('Impossible item-move!'),
+ _('You cannot make an item a descendant of itself!'))
+ item.parent_heading = new_parent
+ item.save()
+ if new_parent:
+ return HttpResponseRedirect(new_parent.item_url('meta'))
+ else:
+ return HttpResponseRedirect(course.course_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', course__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/departments.xhtml
===================================================================
--- servres/trunk/conifer/templates/departments.xhtml 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/templates/departments.xhtml 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,26 +1,26 @@
-<?python
-title = defined('custom_title') and custom_title or _('Departments')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<xi:include href="paginate.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript">
- <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
- $(function() { $('.pagetable').tablesorter(); });
- </script>
-</head>
-<body>
- <h1>${title}</h1>
- <tr py:def="pageheader()">
- <th>Department</th>
- </tr>
- <span py:def="pagerow(department)">
- <td><a href="${ROOT}${department_url(department)}">${department.name}</a></td>
- </span>
- ${pagetable(paginator, count, pagerow, pageheader)}
-</body>
-</html>
+<?python
+title = defined('custom_title') and custom_title or _('Departments')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<xi:include href="paginate.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript">
+ <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
+ $(function() { $('.pagetable').tablesorter(); });
+ </script>
+</head>
+<body>
+ <h1>${title}</h1>
+ <tr py:def="pageheader()">
+ <th>Department</th>
+ </tr>
+ <span py:def="pagerow(department)">
+ <td><a href="${ROOT}${department_url(department)}">${department.name}</a></td>
+ </span>
+ ${pagetable(paginator, count, pagerow, pageheader)}
+</body>
+</html>
Modified: servres/trunk/conifer/templates/edit_course.xhtml
===================================================================
--- servres/trunk/conifer/templates/edit_course.xhtml 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/templates/edit_course.xhtml 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,58 +1,58 @@
-<?python
-if instance.id:
- title = _('Edit course details')
-else:
- title = _('Create a new course site')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<xi:include href="components/course.xhtml"/>
-<head>
- <title>${title}</title>
- <!--
- Disabling this title lookup for now, something goes amiss in the browser interaction
- and the title field is disabled when it shouldn't be - art
- -->
- <!--
- <script type="text/javascript" src="${ROOT}/static/edit_course.js"/>
- -->
-</head>
-<body>
- <div py:if="instance.id">${course_banner(instance)}</div>
- <h1>${title}</h1>
- <p py:if="instance.id"><a href="permission/">Edit course permissions</a> • <a href="${instance.course_url()}">Return to course page</a></p>
- <form action="." method="POST">
- <tr py:def="field_row(field, example=None)">
- <th>${field.label}</th>
- <td>
- <ul py:if="field.errors" class="errorlist">
- <li py:for="err in field.errors">${err}</li>
- </ul>
- ${Markup(field)}
- </td>
- <td class="example" py:if="example">e.g., ${example}</td>
- </tr>
- <h2>General description</h2>
- <table class="metadata_table">
- ${field_row(form.code, example)}
- ${field_row(form.title)}
- ${field_row(form.term)}
- ${field_row(form.department)}
- <!-- <tr><th>Department</th><td>${Markup(form.department)} ${errorlist(form.department)}</td></tr> -->
- </table>
- <p><input type="submit" value="Continue"/> ${go_back_link()}</p>
- </form>
- <div class="gap"/>
- <div py:if="instance.id">
- <h2>Delete this course</h2>
- <form action="delete/" method="POST">
- <p><input type="checkbox" name="confirm_delete" id="confirm_delete"/>
- <label for="confirm_delete">Yes, I want to delete this course site and all of its contents.</label>
- </p>
- <p><input type="submit" value="Delete this course"/> ${go_back_link()}</p>
- </form>
- </div>
-</body>
-</html>
+<?python
+if instance.id:
+ title = _('Edit course details')
+else:
+ title = _('Create a new course site')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<xi:include href="components/course.xhtml"/>
+<head>
+ <title>${title}</title>
+ <!--
+ Disabling this title lookup for now, something goes amiss in the browser interaction
+ and the title field is disabled when it shouldn't be - art
+ -->
+ <!--
+ <script type="text/javascript" src="${ROOT}/static/edit_course.js"/>
+ -->
+</head>
+<body>
+ <div py:if="instance.id">${course_banner(instance)}</div>
+ <h1>${title}</h1>
+ <p py:if="instance.id"><a href="permission/">Edit course permissions</a> • <a href="${instance.course_url()}">Return to course page</a></p>
+ <form action="." method="POST">
+ <tr py:def="field_row(field, example=None)">
+ <th>${field.label}</th>
+ <td>
+ <ul py:if="field.errors" class="errorlist">
+ <li py:for="err in field.errors">${err}</li>
+ </ul>
+ ${Markup(field)}
+ </td>
+ <td class="example" py:if="example">e.g., ${example}</td>
+ </tr>
+ <h2>General description</h2>
+ <table class="metadata_table">
+ ${field_row(form.code, example)}
+ ${field_row(form.title)}
+ ${field_row(form.term)}
+ ${field_row(form.department)}
+ <!-- <tr><th>Department</th><td>${Markup(form.department)} ${errorlist(form.department)}</td></tr> -->
+ </table>
+ <p><input type="submit" value="Continue"/> ${go_back_link()}</p>
+ </form>
+ <div class="gap"/>
+ <div py:if="instance.id">
+ <h2>Delete this course</h2>
+ <form action="delete/" method="POST">
+ <p><input type="checkbox" name="confirm_delete" id="confirm_delete"/>
+ <label for="confirm_delete">Yes, I want to delete this course site and all of its contents.</label>
+ </p>
+ <p><input type="submit" value="Delete this course"/> ${go_back_link()}</p>
+ </form>
+ </div>
+</body>
+</html>
Modified: servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_add_cat_search.xhtml 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/templates/item/item_add_cat_search.xhtml 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,89 +1,89 @@
-<?python
-from django.utils.simplejson import dumps
-from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc as to_dublin
-title = _('Add physical or electronic item, by catalogue search')
-helptext = _('Use keywords or CCL syntax for searching, for example: ti="detroit river" and au="wilgus"')
-dc_keys = ['dc:title', 'dc:creator', 'dc:publisher', 'dc:date']
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="../master.xhtml"/>
-<xi:include href="../paginate.xhtml"/>
-<xi:include href="../components/course.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript">
- <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
- $(function() { $('.pagetable').tablesorter(); });
- </script>
- <script py:if="not 'query' in request.GET"> <!-- !focus on query box if nothing to scroll. -->
- $(function() { $('#query').focus(); });
- </script>
-</head>
-<body>
- ${course_banner(course)}
- ${nested_title(parent_item)}
- <h2>${title}</h2>
- <div class="helptext">
- ${helptext}
- </div>
-
- <form method="GET" action=".">
- <input type="text" id="query" name="query" value="${query}"
- style="font-size: larger; width: 600px;"/>
- <input type="submit" value="Search"/>
- ${go_back_link()}
-
- </form>
- <div py:def="page_control" py:if="results">
- <p>
- ${start}–${min(numhits, start+limit-1)} of ${numhits} results.
- <span py:if="start-limit>0">
- <a href=".?query=${query}&start=${start-limit}&limit=${limit}">Previous ${limit}</a>
- •
- </span>
- <span py:if="start+limit<numhits">
- <a href=".?query=${query}&start=${start+limit}&limit=${limit}">Next ${limit}</a>
- </span>
- </p>
- </div>
- ${page_control()}
- <table class="pagetable" py:if="'query' in request.GET">
- <thead>
- <tr><th>#</th><th>Title</th><th>Author</th><th>Publisher</th><th>PubDate</th></tr>
- </thead>
- <tbody py:for="resultnum, res in enumerate(results)"
- py:with="dc=to_dublin(res)">
- <tr>
- <td>${resultnum+start}.</td>
- <td>
- ${dc.get('dc:title', '???')}
- <a href="javascript:$('#full_${resultnum}').toggle(); void(0);">details</a>
- <p py:if="res.get('8569')" style="margin: 8px 0; font-size: 90%; color: darkred;">
- Electronic resource. <a href="${res.get('856u')}">view</a>
- </p>
- </td>
- <td py:for="k in dc_keys[1:]">${dc.get(k) or '—'}</td>
- <td>
- <form action="." method="POST">
- <input type="hidden" name="pickitem" value="${dumps(res)}"/>
- <input type="submit" value="Pick this item"/>
- </form>
- </td>
- </tr>
- <tr id="full_${resultnum}" style="display: none;">
- <td colspan="4" style="padding-left: 36;">
- <table class="metadata_table">
- <?python allkeys = res.keys(); allkeys.sort(); ?>
- <tr py:for="k in allkeys">
- <th>${k}</th><td>${res[k]}</td>
- </tr>
- </table>
- </td>
- </tr>
- </tbody>
- </table>
- ${page_control()}
- </body>
-</html>
+<?python
+from django.utils.simplejson import dumps
+from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc as to_dublin
+title = _('Add physical or electronic item, by catalogue search')
+helptext = _('Use keywords or CCL syntax for searching, for example: ti="detroit river" and au="wilgus"')
+dc_keys = ['dc:title', 'dc:creator', 'dc:publisher', 'dc:date']
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="../master.xhtml"/>
+<xi:include href="../paginate.xhtml"/>
+<xi:include href="../components/course.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript">
+ <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
+ $(function() { $('.pagetable').tablesorter(); });
+ </script>
+ <script py:if="not 'query' in request.GET"> <!-- !focus on query box if nothing to scroll. -->
+ $(function() { $('#query').focus(); });
+ </script>
+</head>
+<body>
+ ${course_banner(course)}
+ ${nested_title(parent_item)}
+ <h2>${title}</h2>
+ <div class="helptext">
+ ${helptext}
+ </div>
+
+ <form method="GET" action=".">
+ <input type="text" id="query" name="query" value="${query}"
+ style="font-size: larger; width: 600px;"/>
+ <input type="submit" value="Search"/>
+ ${go_back_link()}
+
+ </form>
+ <div py:def="page_control" py:if="results">
+ <p>
+ ${start}–${min(numhits, start+limit-1)} of ${numhits} results.
+ <span py:if="start-limit>0">
+ <a href=".?query=${query}&start=${start-limit}&limit=${limit}">Previous ${limit}</a>
+ •
+ </span>
+ <span py:if="start+limit<numhits">
+ <a href=".?query=${query}&start=${start+limit}&limit=${limit}">Next ${limit}</a>
+ </span>
+ </p>
+ </div>
+ ${page_control()}
+ <table class="pagetable" py:if="'query' in request.GET">
+ <thead>
+ <tr><th>#</th><th>Title</th><th>Author</th><th>Publisher</th><th>PubDate</th></tr>
+ </thead>
+ <tbody py:for="resultnum, res in enumerate(results)"
+ py:with="dc=to_dublin(res)">
+ <tr>
+ <td>${resultnum+start}.</td>
+ <td>
+ ${dc.get('dc:title', '???')}
+ <a href="javascript:$('#full_${resultnum}').toggle(); void(0);">details</a>
+ <p py:if="res.get('8569')" style="margin: 8px 0; font-size: 90%; color: darkred;">
+ Electronic resource. <a href="${res.get('856u')}">view</a>
+ </p>
+ </td>
+ <td py:for="k in dc_keys[1:]">${dc.get(k) or '—'}</td>
+ <td>
+ <form action="." method="POST">
+ <input type="hidden" name="pickitem" value="${dumps(res)}"/>
+ <input type="submit" value="Pick this item"/>
+ </form>
+ </td>
+ </tr>
+ <tr id="full_${resultnum}" style="display: none;">
+ <td colspan="4" style="padding-left: 36;">
+ <table class="metadata_table">
+ <?python allkeys = res.keys(); allkeys.sort(); ?>
+ <tr py:for="k in allkeys">
+ <th>${k}</th><td>${res[k]}</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ ${page_control()}
+ </body>
+</html>
Modified: servres/trunk/conifer/templates/master.xhtml
===================================================================
--- servres/trunk/conifer/templates/master.xhtml 2010-01-19 03:33:20 UTC (rev 761)
+++ servres/trunk/conifer/templates/master.xhtml 2010-01-22 02:48:02 UTC (rev 762)
@@ -1,79 +1,79 @@
-<?python
-app_name = _('Syrup Reserves System')
-search = _('search...')
-import os
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="">
- <py:match path="head" once="True">
- <head py:attrs="select('@*')"
- py:with="t=list(select('title/text()'))">
- <title>${app_name}<py:if test="t">: ${t}</py:if></title>
- <link rel="stylesheet" type="text/css" href="${ROOT}/static/main.css"/>
- <!--
- using the trunk version of jquery to get around some IE 8 problems
- -->
- <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery.js"/>
- <!--
- <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery-1.3.2.min.js"/>
- -->
- <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery-ui-1.7.1.custom.min.js"/>
- <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery.tablesorter.min.js"/>
- ${select('*[local-name()!="title"]')}
- </head>
- </py:match>
- <py:match path="body" once="true">
- <body py:attrs="select('@*')">
- <div id="outer">
- <div id="brandheader">
- <div style="float: right; font-size: x-large; padding: 12px; color: #888;">
- ${app_name}
- </div>
- <img src="${ROOT}/static/institution-logo.png" style="height: 50px;"/>
- </div>
- <!--
- <div id="header" py:if="user.is_authenticated()">
- trying to keep the search box consistent for now
- -->
- <div id="header">
- <div id="search">
- <form method="get" action="${ROOT}/search"
- onsubmit="if(q.value.replace(/^\s*/, '').replace(/\s*$/, '') =='') return false;"
- >
- <input id="q" name="q" maxlength="100" size="25" type="text"
- value="${search}" onblur="if(this.value=='') this.value='${search}';"
- onfocus="if(this.value=='${search}') this.value='';"/>
- </form>
- </div>
- <div id="welcome" py:if="user.is_authenticated()">
- <strong style="padding-right: 18px;">Welcome, ${user.first_name or user.username}!</strong>
- <a href="${ROOT}/accounts/logout">Log Out</a>
- • <a href="${ROOT}/prefs/">Preferences</a>
- </div>
- <div id="welcome" py:if="not user.is_authenticated()">
- <strong style="padding-right: 18px;">Welcome!</strong>
- <a class="loginbutton" href="${ROOT}/accounts/login/">Log In</a>
- • <a href="${ROOT}/prefs/">Preferences</a>
- </div>
- </div>
- <xi:include py:if="user.is_authenticated()" href="tabbar.xhtml"/>
- <xi:include py:if="not user.is_authenticated()" href="tabbar_anonymous.xhtml"/>
- <div id="mainpanel">
- ${select('*|text()')}
- </div>
- <div id="footer">
- <div>
- Syrup is a subproject of <a href="http://conifer.mcmaster.ca/">Project Conifer</a> © 2009
- </div>
- </div>
- </div>
- </body>
- </py:match>
-
- <span py:def="go_back_link(url=None, msg=_('or Cancel'))" py:with="url=url or request.META.get('HTTP_REFERER', '../')">
- <a style="margin-left: 12px;" href="${url}">${msg}</a>
- </span>
-
-</html>
+<?python
+app_name = _('Syrup Reserves System')
+search = _('search...')
+import os
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+ <py:match path="head" once="True">
+ <head py:attrs="select('@*')"
+ py:with="t=list(select('title/text()'))">
+ <title>${app_name}<py:if test="t">: ${t}</py:if></title>
+ <link rel="stylesheet" type="text/css" href="${ROOT}/static/main.css"/>
+ <!--
+ using the trunk version of jquery to get around some IE 8 problems
+ -->
+ <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery.js"/>
+ <!--
+ <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery-1.3.2.min.js"/>
+ -->
+ <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery-ui-1.7.1.custom.min.js"/>
+ <script type="text/javascript" src="${ROOT}/static/jquery/js/jquery.tablesorter.min.js"/>
+ ${select('*[local-name()!="title"]')}
+ </head>
+ </py:match>
+ <py:match path="body" once="true">
+ <body py:attrs="select('@*')">
+ <div id="outer">
+ <div id="brandheader">
+ <div style="float: right; font-size: x-large; padding: 12px; color: #888;">
+ ${app_name}
+ </div>
+ <img src="${ROOT}/static/institution-logo.png" style="height: 50px;"/>
+ </div>
+ <!--
+ <div id="header" py:if="user.is_authenticated()">
+ trying to keep the search box consistent for now
+ -->
+ <div id="header">
+ <div id="search">
+ <form method="get" action="${ROOT}/search"
+ onsubmit="if(q.value.replace(/^\s*/, '').replace(/\s*$/, '') =='') return false;"
+ >
+ <input id="q" name="q" maxlength="100" size="25" type="text"
+ value="${search}" onblur="if(this.value=='') this.value='${search}';"
+ onfocus="if(this.value=='${search}') this.value='';"/>
+ </form>
+ </div>
+ <div id="welcome" py:if="user.is_authenticated()">
+ <strong style="padding-right: 18px;">Welcome, ${user.first_name or user.username}!</strong>
+ <a href="${ROOT}/accounts/logout">Log Out</a>
+ • <a href="${ROOT}/prefs/">Preferences</a>
+ </div>
+ <div id="welcome" py:if="not user.is_authenticated()">
+ <strong style="padding-right: 18px;">Welcome!</strong>
+ <a class="loginbutton" href="${ROOT}/accounts/login/">Log In</a>
+ • <a href="${ROOT}/prefs/">Preferences</a>
+ </div>
+ </div>
+ <xi:include py:if="user.is_authenticated()" href="tabbar.xhtml"/>
+ <xi:include py:if="not user.is_authenticated()" href="tabbar_anonymous.xhtml"/>
+ <div id="mainpanel">
+ ${select('*|text()')}
+ </div>
+ <div id="footer">
+ <div>
+ Syrup is a subproject of <a href="http://conifer.mcmaster.ca/">Project Conifer</a> © 2009
+ </div>
+ </div>
+ </div>
+ </body>
+ </py:match>
+
+ <span py:def="go_back_link(url=None, msg=_('or Cancel'))" py:with="url=url or request.META.get('HTTP_REFERER', '../')">
+ <a style="margin-left: 12px;" href="${url}">${msg}</a>
+ </span>
+
+</html>
More information about the open-ils-commits
mailing list