[open-ils-commits] r914 - in servres/trunk/conifer: custom libsystems/z3950 syrup syrup/migrations syrup/views templates/item (gfawcett)
svn at svn.open-ils.org
svn at svn.open-ils.org
Wed Jul 14 20:54:55 EDT 2010
Author: gfawcett
Date: 2010-07-14 20:54:53 -0400 (Wed, 14 Jul 2010)
New Revision: 914
Removed:
servres/trunk/conifer/syrup/migrations/0002_auto__add_field_site_term__add_field_site_course__add_unique_site_owne.py
Modified:
servres/trunk/conifer/custom/lib_integration.py
servres/trunk/conifer/libsystems/z3950/marcxml.py
servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
servres/trunk/conifer/syrup/migrations/0001_initial.py
servres/trunk/conifer/syrup/models.py
servres/trunk/conifer/syrup/views/items.py
servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
servres/trunk/conifer/templates/item/item_add_url.xhtml
servres/trunk/conifer/templates/item/item_metadata.xhtml
Log:
changing 'published' to a char field; storing marc record in item. Breaking schema change.
Unicode in Z39.50 results is currently broken.
Modified: servres/trunk/conifer/custom/lib_integration.py
===================================================================
--- servres/trunk/conifer/custom/lib_integration.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/custom/lib_integration.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -50,7 +50,7 @@
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
+from conifer.libsystems.z3950.marcxml import marcxml_to_records
@caching('patroninfo', timeout=300)
@@ -89,7 +89,7 @@
# 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)
+ results = marcxml_to_records(I.url_to_marcxml(query))
numhits = len(results)
else:
cat_host, cat_port, cat_db = settings.Z3950_CONFIG
Modified: servres/trunk/conifer/libsystems/z3950/marcxml.py
===================================================================
--- servres/trunk/conifer/libsystems/z3950/marcxml.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/libsystems/z3950/marcxml.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -1,5 +1,28 @@
from xml.etree import ElementTree
+def marcxml_to_records(rec):
+ tree = ElementTree.fromstring(rec)
+ if tree.tag == '{http://www.loc.gov/MARC21/slim}collection':
+ # then we may have multiple records
+ records = tree.findall('{http://www.loc.gov/MARC21/slim}record')
+ elif tree.tag == '{http://www.loc.gov/MARC21/slim}record':
+ records = [tree]
+ else:
+ return []
+ return records
+
+def record_to_dictionary(record, multiples=True):
+ tree = ElementTree.fromstring(record)
+ dct = {}
+ for df in tree.findall('{http://www.loc.gov/MARC21/slim}datafield'):
+ t = df.attrib['tag']
+ for sf in df.findall('{http://www.loc.gov/MARC21/slim}subfield'):
+ c = sf.attrib['code']
+ v = sf.text or ''
+ dct.setdefault(t+c, []).append(v)
+ dct = dict((k,'\n'.join(v or [])) for k,v in dct.items())
+ return dct
+
def marcxml_to_dictionary(rec, multiples=False):
tree = ElementTree.fromstring(rec)
if tree.tag == '{http://www.loc.gov/MARC21/slim}collection':
@@ -30,23 +53,26 @@
extract some Dublin Core elements from it. Fixme, I'm sure this
could be way improved."""
out = {}
- meta = [('245a', 'dc:title'), ('100a', 'dc:creator'), ('260b', 'dc:publisher'),
+ meta = [('245a', 'dc:title'), ('100a', 'dc:creator'),
('260c', 'dc:date'), ('700a', 'dc:contributor')]
for marc, dc in meta:
value = dct.get(marc)
if value:
out[dc] = value
- title = out.get('dc:title')
+
+ pub = [v.strip() for k,v in sorted(dct.items()) if k.startswith('260')]
+ if pub:
+ out['dc:publisher'] = strip_punct(' '.join(pub))
+
+ title = [v.strip() for k,v in sorted(dct.items()) if k in ('245a', '245b')]
if title:
- if '245b' in dct:
- title += (' %s' % dct['245b'])
- # if title ends with a single character, strip it. usually a
- # spurious punctuation character.
- if ' ' in title:
- init, last = title.rsplit(' ',1)
- if len(last) == 1:
- title = init
- out['dc:title'] = title
+ out['dc:title'] = strip_punct(' '.join(title))
return out
+def strip_punct(s):
+ # strip whitespace and trailing single punctuation characters
+ s = s.strip()
+ if s and s[-1] in ',.;:/':
+ s = s[:-1]
+ return s.strip()
Modified: servres/trunk/conifer/libsystems/z3950/pyz3950_search.py
===================================================================
--- servres/trunk/conifer/libsystems/z3950/pyz3950_search.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/libsystems/z3950/pyz3950_search.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -74,16 +74,15 @@
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)
+ parsed.append(rec)
return parsed, len(res)
# decoding MARC \X.. UTF-8 patterns.
-_marc_utf8_pattern = re.compile(r'\\X([0-9A-F]{2})')
+_marc_utf8_pattern = re.compile(r'\\X([0-9A-F]{2})', re.I)
def _decode_marc_utf8(regex_match):
return chr(int(regex_match.group(1), 16))
Modified: servres/trunk/conifer/syrup/migrations/0001_initial.py
===================================================================
--- servres/trunk/conifer/syrup/migrations/0001_initial.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/syrup/migrations/0001_initial.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -5,132 +5,121 @@
from django.db import models
class Migration(SchemaMigration):
-
+
def forwards(self, orm):
# Adding model 'UserProfile'
db.create_table('syrup_userprofile', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('last_email_notice', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, null=True, blank=True)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('wants_email_notices', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('ils_userid', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True)),
- ('wants_email_notices', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
- ('last_email_notice', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['UserProfile'])
# Adding model 'ServiceDesk'
db.create_table('syrup_servicedesk', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
('external_id', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['ServiceDesk'])
# Adding model 'Term'
db.create_table('syrup_term', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('code', self.gf('django.db.models.fields.CharField')(max_length=64)),
('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
- ('start', self.gf('django.db.models.fields.DateField')()),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('finish', self.gf('django.db.models.fields.DateField')()),
+ ('start', self.gf('django.db.models.fields.DateField')()),
+ ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['Term'])
# Adding model 'Department'
db.create_table('syrup_department', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('service_desk', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.ServiceDesk'])),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
- ('service_desk', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.ServiceDesk'])),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['Department'])
# Adding model 'Course'
db.create_table('syrup_course', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('code', self.gf('django.db.models.fields.CharField')(max_length=64)),
('name', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('department', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Department'])),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['Course'])
# Adding model 'Z3950Target'
db.create_table('syrup_z3950target', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
- ('host', self.gf('django.db.models.fields.CharField')(max_length=50)),
('database', self.gf('django.db.models.fields.CharField')(max_length=50)),
- ('port', self.gf('django.db.models.fields.IntegerField')(default=210)),
('syntax', self.gf('django.db.models.fields.CharField')(default='USMARC', max_length=10)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
+ ('host', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('port', self.gf('django.db.models.fields.IntegerField')(default=210)),
))
db.send_create_signal('syrup', ['Z3950Target'])
# Adding model 'Config'
db.create_table('syrup_config', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
('value', self.gf('django.db.models.fields.CharField')(max_length=8192)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
))
db.send_create_signal('syrup', ['Config'])
# Adding model 'Site'
db.create_table('syrup_site', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('term', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Term'])),
+ ('service_desk', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.ServiceDesk'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('passkey', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=256, null=True, blank=True)),
+ ('access', self.gf('django.db.models.fields.CharField')(default='CLOSE', max_length=5)),
+ ('course', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Course'])),
('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
- ('service_desk', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.ServiceDesk'])),
- ('access', self.gf('django.db.models.fields.CharField')(default='CLOSE', max_length=5)),
- ('passkey', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=256, null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['Site'])
- # Adding M2M table for field courses on 'Site'
- db.create_table('syrup_site_courses', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('site', models.ForeignKey(orm['syrup.site'], null=False)),
- ('course', models.ForeignKey(orm['syrup.course'], null=False))
- ))
- db.create_unique('syrup_site_courses', ['site_id', 'course_id'])
+ # Adding unique constraint on 'Site', fields ['course', 'term', 'owner']
+ db.create_unique('syrup_site', ['course_id', 'term_id', 'owner_id'])
- # Adding M2M table for field terms on 'Site'
- db.create_table('syrup_site_terms', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('site', models.ForeignKey(orm['syrup.site'], null=False)),
- ('term', models.ForeignKey(orm['syrup.term'], null=False))
- ))
- db.create_unique('syrup_site_terms', ['site_id', 'term_id'])
-
# Adding model 'Group'
db.create_table('syrup_group', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Site'])),
+ ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('external_id', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=2048, null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
))
db.send_create_signal('syrup', ['Group'])
# Adding model 'Membership'
db.create_table('syrup_membership', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Group'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
- ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
- ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Group'])),
('role', self.gf('django.db.models.fields.CharField')(default='STUDT', max_length=6)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('syrup', ['Membership'])
@@ -139,26 +128,26 @@
# Adding model 'Item'
db.create_table('syrup_item', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
- ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Site'])),
- ('item_type', self.gf('django.db.models.fields.CharField')(max_length=7)),
- ('bib_id', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)),
- ('marcxml', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
- ('title', self.gf('django.db.models.fields.CharField')(max_length=8192, db_index=True)),
- ('author', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=8192, null=True, blank=True)),
+ ('parent_heading', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Item'], null=True, blank=True)),
('publisher', self.gf('django.db.models.fields.CharField')(max_length=8192, null=True, blank=True)),
- ('published', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
('itemtype', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=1, null=True, blank=True)),
- ('parent_heading', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Item'], null=True, blank=True)),
+ ('marcxml', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ('author', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=8192, null=True, blank=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
- ('fileobj', self.gf('django.db.models.fields.files.FileField')(default=None, max_length=255, null=True, blank=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=8192, db_index=True)),
('fileobj_mimetype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+ ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['syrup.Site'])),
+ ('item_type', self.gf('django.db.models.fields.CharField')(max_length=7)),
+ ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ('fileobj', self.gf('django.db.models.fields.files.FileField')(default=None, max_length=255, null=True, blank=True)),
+ ('published', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('bib_id', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)),
))
db.send_create_signal('syrup', ['Item'])
-
-
+
+
def backwards(self, orm):
# Deleting model 'UserProfile'
@@ -185,12 +174,9 @@
# Deleting model 'Site'
db.delete_table('syrup_site')
- # Removing M2M table for field courses on 'Site'
- db.delete_table('syrup_site_courses')
+ # Removing unique constraint on 'Site', fields ['course', 'term', 'owner']
+ db.delete_unique('syrup_site', ['course_id', 'term_id', 'owner_id'])
- # Removing M2M table for field terms on 'Site'
- db.delete_table('syrup_site_terms')
-
# Deleting model 'Group'
db.delete_table('syrup_group')
@@ -202,8 +188,8 @@
# Deleting model 'Item'
db.delete_table('syrup_item')
-
-
+
+
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -286,7 +272,7 @@
'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'marcxml': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'parent_heading': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Item']", 'null': 'True', 'blank': 'True'}),
- 'published': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'published': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'null': 'True', 'blank': 'True'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Site']"}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'db_index': 'True'}),
@@ -311,16 +297,16 @@
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'syrup.site': {
- 'Meta': {'object_name': 'Site'},
+ 'Meta': {'unique_together': "(('course', 'term', 'owner'),)", 'object_name': 'Site'},
'access': ('django.db.models.fields.CharField', [], {'default': "'CLOSE'", 'max_length': '5'}),
- 'courses': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['syrup.Course']"}),
+ 'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Course']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'passkey': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '256', 'null': 'True', 'blank': 'True'}),
'service_desk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.ServiceDesk']"}),
- 'terms': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['syrup.Term']"})
+ 'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Term']"})
},
'syrup.term': {
'Meta': {'object_name': 'Term'},
@@ -353,5 +339,5 @@
'syntax': ('django.db.models.fields.CharField', [], {'default': "'USMARC'", 'max_length': '10'})
}
}
-
+
complete_apps = ['syrup']
Deleted: servres/trunk/conifer/syrup/migrations/0002_auto__add_field_site_term__add_field_site_course__add_unique_site_owne.py
===================================================================
--- servres/trunk/conifer/syrup/migrations/0002_auto__add_field_site_term__add_field_site_course__add_unique_site_owne.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/syrup/migrations/0002_auto__add_field_site_term__add_field_site_course__add_unique_site_owne.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -1,205 +0,0 @@
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding field 'Site.term'
- db.add_column('syrup_site', 'term', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['syrup.Term']), keep_default=False)
-
- # Adding field 'Site.course'
- db.add_column('syrup_site', 'course', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['syrup.Course']), keep_default=False)
-
- # Removing M2M table for field courses on 'Site'
- db.delete_table('syrup_site_courses')
-
- # Removing M2M table for field terms on 'Site'
- db.delete_table('syrup_site_terms')
-
- # Adding unique constraint on 'Site', fields ['owner', 'course', 'term']
- db.create_unique('syrup_site', ['owner_id', 'course_id', 'term_id'])
-
-
- def backwards(self, orm):
-
- # Deleting field 'Site.term'
- db.delete_column('syrup_site', 'term_id')
-
- # Deleting field 'Site.course'
- db.delete_column('syrup_site', 'course_id')
-
- # Adding M2M table for field courses on 'Site'
- db.create_table('syrup_site_courses', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('site', models.ForeignKey(orm['syrup.site'], null=False)),
- ('course', models.ForeignKey(orm['syrup.course'], null=False))
- ))
- db.create_unique('syrup_site_courses', ['site_id', 'course_id'])
-
- # Adding M2M table for field terms on 'Site'
- db.create_table('syrup_site_terms', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('site', models.ForeignKey(orm['syrup.site'], null=False)),
- ('term', models.ForeignKey(orm['syrup.term'], null=False))
- ))
- db.create_unique('syrup_site_terms', ['site_id', 'term_id'])
-
- # Removing unique constraint on 'Site', fields ['owner', 'course', 'term']
- db.delete_unique('syrup_site', ['owner_id', 'course_id', 'term_id'])
-
-
- models = {
- 'auth.group': {
- 'Meta': {'object_name': 'Group'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
- },
- 'auth.permission': {
- 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- 'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'contenttypes.contenttype': {
- 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- },
- 'syrup.config': {
- 'Meta': {'object_name': 'Config'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'value': ('django.db.models.fields.CharField', [], {'max_length': '8192'})
- },
- 'syrup.course': {
- 'Meta': {'object_name': 'Course'},
- 'code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'department': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Department']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
- },
- 'syrup.department': {
- 'Meta': {'object_name': 'Department'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'service_desk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.ServiceDesk']"})
- },
- 'syrup.group': {
- 'Meta': {'object_name': 'Group'},
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'external_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Site']"})
- },
- 'syrup.item': {
- 'Meta': {'object_name': 'Item'},
- 'author': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8192', 'null': 'True', 'blank': 'True'}),
- 'bib_id': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'fileobj': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
- 'fileobj_mimetype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'item_type': ('django.db.models.fields.CharField', [], {'max_length': '7'}),
- 'itemtype': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1', 'null': 'True', 'blank': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'marcxml': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'parent_heading': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Item']", 'null': 'True', 'blank': 'True'}),
- 'published': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
- 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'null': 'True', 'blank': 'True'}),
- 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Site']"}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'db_index': 'True'}),
- 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
- },
- 'syrup.membership': {
- 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'Membership'},
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Group']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'role': ('django.db.models.fields.CharField', [], {'default': "'STUDT'", 'max_length': '6'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'syrup.servicedesk': {
- 'Meta': {'object_name': 'ServiceDesk'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'external_id': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- },
- 'syrup.site': {
- 'Meta': {'unique_together': "(('course', 'term', 'owner'),)", 'object_name': 'Site'},
- 'access': ('django.db.models.fields.CharField', [], {'default': "'CLOSE'", 'max_length': '5'}),
- 'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Course']"}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
- 'passkey': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '256', 'null': 'True', 'blank': 'True'}),
- 'service_desk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.ServiceDesk']"}),
- 'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Term']"})
- },
- 'syrup.term': {
- 'Meta': {'object_name': 'Term'},
- 'code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'finish': ('django.db.models.fields.DateField', [], {}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'start': ('django.db.models.fields.DateField', [], {})
- },
- 'syrup.userprofile': {
- 'Meta': {'object_name': 'UserProfile'},
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'ils_userid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
- 'last_email_notice': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}),
- 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
- 'wants_email_notices': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})
- },
- 'syrup.z3950target': {
- 'Meta': {'object_name': 'Z3950Target'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
- 'database': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
- 'host': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'port': ('django.db.models.fields.IntegerField', [], {'default': '210'}),
- 'syntax': ('django.db.models.fields.CharField', [], {'default': "'USMARC'", 'max_length': '10'})
- }
- }
-
- complete_apps = ['syrup']
Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/syrup/models.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -14,6 +14,9 @@
campus = settings.CAMPUS_INTEGRATION
# TODO: fixme, not sure if conifer.custom is a good parent.
from conifer.custom import lib_integration
+from conifer.libsystems.z3950.marcxml import record_to_dictionary
+from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc
+from django.utils import simplejson as json
#----------------------------------------------------------------------
@@ -405,7 +408,7 @@
# publisher: "Place: Publisher", as in a bibliography, for display.
publisher = m.CharField(max_length=8192, null=True, blank=True)
- published = m.DateField(null=True, blank=True)
+ published = m.CharField(max_length=64, null=True, blank=True)
ITEMTYPE_CHOICES = [
# From http://www.oclc.org/bibformats/en/fixedfield/type.shtm.
@@ -447,6 +450,15 @@
fileobj_mimetype = m.CharField(max_length=128, blank=True, null=True)
+ #--------------------------------------------------
+ # MARC
+ def marc_as_dict(self):
+ return record_to_dictionary(self.marcxml)
+
+ def marc_dc_subset(self):
+ return json.dumps(self.marc_as_dict())
+
+ #--------------------------------------------------
def __unicode__(self):
return self.title
@@ -495,7 +507,7 @@
and a friendly description of the physical item's status"""
# TODO: this needs to be reimplemented, based on copy detail
# lookup in the ILS. It also may not belong here!
- raise NotImplementedError
+ return (True, 'NOT-IMPLEMENTED')
# TODO: stuff I'm not sure about yet. I don't think it belongs here.
Modified: servres/trunk/conifer/syrup/views/items.py
===================================================================
--- servres/trunk/conifer/syrup/views/items.py 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/syrup/views/items.py 2010-07-15 00:54:53 UTC (rev 914)
@@ -1,5 +1,6 @@
from _common import *
from django.utils.translation import ugettext as _
+from xml.etree import ElementTree as E
@members_only
def item_detail(request, site_id, item_id):
@@ -170,9 +171,13 @@
if not site.can_edit(request.user):
return _access_denied(_('You are not an editor.'))
- pickitem = simplejson.loads(raw_pickitem)
+ pickitem = marcxml_to_dictionary(raw_pickitem)
dublin = marcxml_dictionary_to_dc(pickitem)
+ assert dublin
+
+ #TODO: this data munging does not belong here.
+
# 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.
@@ -180,19 +185,21 @@
dct = dict(item_type='URL', url=pickitem.get('856u'))
else:
dct = dict(item_type='PHYS')
-
+
try:
pubdate = dublin.get('dc:date')
- pubdate = re.search('^([0-9]+)', pubdate).group(1)
- pubdate = '%d-01-01' % int(pubdate)
+ m = re.search('([0-9]+)', pubdate)
+ if m:
+ pubdate = pubdate.group(1)
except:
- pubdate = None
+ pubdate = ''
item = site.item_set.create(parent_heading=parent_item,
title=dublin.get('dc:title','Untitled'),
author=dublin.get('dc:creator'),
publisher=dublin.get('dc:publisher',''),
published=pubdate,
+ marcxml=raw_pickitem,
**dct)
item.save()
return HttpResponseRedirect('../../../%d/meta' % item.id)
Modified: servres/trunk/conifer/templates/item/item_add_cat_search.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_add_cat_search.xhtml 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/templates/item/item_add_cat_search.xhtml 2010-07-15 00:54:53 UTC (rev 914)
@@ -1,5 +1,7 @@
<?python
+from xml.etree import ElementTree as E
from django.utils.simplejson import dumps
+from conifer.libsystems.z3950.marcxml import record_to_dictionary
from conifer.libsystems.z3950.marcxml import marcxml_dictionary_to_dc as to_dublin
title = _('Add physical or electronic item, by catalogue search')
helptext = _('Use keywords or CCL syntax for searching, for example: ti="detroit river" and au="wilgus"')
@@ -54,20 +56,21 @@
<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)">
+ py:with="dct=record_to_dictionary(res); dc=to_dublin(dct)">
<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 py:if="dct.get('8569')" style="margin: 8px 0; font-size: 90%; color: darkred;">
+ Electronic resource. <a href="${dct.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)}"/>
+ <!-- !TODO: is utf8 okay here? I shouldn't have to do any decoding here. -->
+ <input type="hidden" name="pickitem" value="${unicode(res, 'utf8')}"/>
<input type="submit" value="Pick this item"/>
</form>
</td>
@@ -75,9 +78,9 @@
<tr id="full_${resultnum}" style="display: none;">
<td colspan="4" style="padding-left: 36;">
<table class="metadata_table">
- <?python allkeys = res.keys(); allkeys.sort(); ?>
+ <?python allkeys = dct.keys(); allkeys.sort(); ?>
<tr py:for="k in allkeys">
- <th>${k}</th><td>${res[k]}</td>
+ <th>${k}</th><td>${dct[k]}</td>
</tr>
</table>
</td>
Modified: servres/trunk/conifer/templates/item/item_add_url.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_add_url.xhtml 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/templates/item/item_add_url.xhtml 2010-07-15 00:54:53 UTC (rev 914)
@@ -23,7 +23,25 @@
<table class="metadata_table">
<tr><th>Title</th><td><input type="text" name="title" value="${item.title}"/></td></tr>
<tr><th>URL</th><td><input type="text" name="url" value="${item.url}"/></td></tr>
+ <tr><th>Author</th><td><input type="text" name="author"
+ value="${item.author}"/></td></tr>
+ <tr><th>Publisher</th><td><input type="text" name="publisher"
+ value="${item.publisher}"/></td></tr>
+ <tr><th>Published</th><td><input type="text" name="published"
+ value="${item.published}"/></td></tr>
</table>
+ <div py:if="item.marcxml">
+ <p id="marcdatashow"><a href="javascript:void($('#marcdata').toggle());">Display MARC Record</a></p>
+ <div id="marcdata" style="display: none;">
+ <?python dct = item.marc_as_dict(); keys=dct.keys(); keys.sort() ?>
+ <table class="metadata_table">
+ <tr py:for="k in keys">
+ <th>${k}</th>
+ <td>${dct[k]}</td>
+ </tr>
+ </table>
+ </div>
+ </div>
<p>
<input py:if="not is_edit" type="submit" value="Add URL"/>
<input py:if="is_edit" type="submit" value="Update URL"/>
Modified: servres/trunk/conifer/templates/item/item_metadata.xhtml
===================================================================
--- servres/trunk/conifer/templates/item/item_metadata.xhtml 2010-07-15 00:54:47 UTC (rev 913)
+++ servres/trunk/conifer/templates/item/item_metadata.xhtml 2010-07-15 00:54:53 UTC (rev 914)
@@ -33,6 +33,9 @@
</div>
<table class="metadata_table" style="margin-top: 1em;">
<tr><th>Title</th><td>${item.title}</td></tr>
+ <tr py:if="item.author"><th>Author</th><td>${item.author}</td></tr>
+ <tr py:if="item.publisher"><th>Publisher</th><td>${item.publisher}</td></tr>
+ <tr py:if="item.published"><th>Published</th><td>${item.published and item.published.year}</td></tr>
<tr><th>Type</th><td>${item.get_item_type_display()}</td></tr>
<tr py:if="item.item_type=='PHYS'"
py:with="avail, status = item.describe_physical_item_status()">
@@ -48,29 +51,17 @@
<tr><th/><td><a class="bigdownload" href="${item.item_url()}">Download</a></td></tr>
</table>
</div>
- <h2>TODO: get metadata back...</h2>
- <div py:if="False and metadata">
- <h2 class="metadata_subhead">Additional metadata</h2>
- <table class="metadata_table">
- <tr py:for="attr in metadata">
- <th>${attr.get_name_display()}</th>
- <td py:if="attr.name != 'syrup:marc'">
- ${attr.value}
- </td>
- <td py:if="attr.name == 'syrup:marc'">
- <div id="marcdatashow"><a href="javascript:$('#marcdata').show();$('#marcdatashow').hide();void(0);">show</a></div>
+ <div py:if="item.marcxml">
+ <p id="marcdatashow"><a href="javascript:void($('#marcdata').toggle());">Display MARC Record</a></p>
<div id="marcdata" style="display: none;">
- <?python dct = loads(attr.value); keys=dct.keys(); keys.sort() ?>
- <table>
+ <?python dct = item.marc_as_dict(); keys=dct.keys(); keys.sort() ?>
+ <table class="metadata_table">
<tr py:for="k in keys">
<th>${k}</th>
<td>${dct[k]}</td>
</tr>
</table>
</div>
- </td>
- </tr>
- </table>
- </div>
+ </div>
</body>
</html>
More information about the open-ils-commits
mailing list