Page MenuHomec4science

engine.py
No OneTemporary

File Metadata

Created
Wed, Jul 3, 03:56

engine.py

# This file is part of Invenio.
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2015 CERN.
#
# Invenio is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio 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 Invenio; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
# pylint: disable=C0103
"""Invenio BibEdit Engine."""
__revision__ = "$Id"
from datetime import datetime
import re
import zlib
import copy
import urllib
import urllib2
import cookielib
import json
import sys
from flask import url_for
from invenio.modules import formatter as bibformat
from invenio.ext.logging import register_exception
from invenio.utils.json import CFG_JSON_AVAILABLE
from invenio.utils.url import auto_version_url
from invenio.legacy.bibrecord.xmlmarc2textmarc import create_marc_record
from invenio.config import (CFG_SITE_LANG,
CFG_BIBCATALOG_SYSTEM_RT_URL,
CFG_BIBEDIT_SHOW_HOLDING_PEN_REMOVED_FIELDS,
CFG_BIBCATALOG_SYSTEM, CFG_BIBEDIT_AUTOCOMPLETE,
CFG_BIBEDIT_AJAX_RESULT_CODES_REV)
from invenio.legacy.bibedit.db_layer import get_name_tags_all, reserve_record_id, \
get_related_hp_changesets, get_hp_update_xml, delete_hp_change, \
get_record_last_modification_date, get_record_revision_author, \
get_marcxml_of_record_revision, delete_related_holdingpen_changes, \
get_record_revisions, get_info_of_record_revision, \
deactivate_cache
from invenio.legacy.bibedit.utils import cache_exists, cache_expired, \
create_cache, delete_cache, get_bibrecord, \
get_cache_contents, get_cache_mtime, get_record_templates, \
get_record_template, latest_record_revision, record_locked_by_other_user, \
record_locked_by_queue, save_xml_record, touch_cache, \
update_cache_contents, get_field_templates, get_marcxml_of_revision, \
revision_to_timestamp, timestamp_to_revision, \
get_record_revision_timestamps, get_record_revision_authors, record_revision_exists, \
can_record_have_physical_copies, extend_record_with_template, \
replace_references, merge_record_with_template, record_xml_output, \
record_is_conference, add_record_cnum, get_xml_from_textmarc, \
record_locked_by_user_details, crossref_process_template, \
modify_record_timestamp, get_affiliation_for_paper, InvalidCache, \
get_new_ticket_RT_info
from invenio.legacy.bibrecord import create_record, print_rec, record_add_field, \
record_add_subfield_into, record_delete_field, \
record_delete_subfield_from, \
record_modify_subfield, record_move_subfield, \
create_field, record_replace_field, record_move_fields, \
record_modify_controlfield, record_get_field_values, \
record_get_subfields, record_get_field_instances, record_add_fields, \
record_strip_empty_fields, record_strip_empty_volatile_subfields, \
record_strip_controlfields, record_order_subfields, \
field_add_subfield, field_get_subfield_values, record_extract_dois
from invenio.config import CFG_BIBEDIT_PROTECTED_FIELDS, CFG_CERN_SITE, \
CFG_SITE_URL, CFG_SITE_RECORD, CFG_BIBEDIT_KB_SUBJECTS, \
CFG_INSPIRE_SITE, CFG_BIBUPLOAD_INTERNAL_DOI_PATTERN, \
CFG_BIBEDIT_INTERNAL_DOI_PROTECTION_LEVEL
from invenio.legacy.search_engine import record_exists, perform_request_search, \
guess_primary_collection_of_a_record
from invenio.legacy.webuser import session_param_get, session_param_set
from invenio.legacy.bibcatalog.api import BIBCATALOG_SYSTEM
from invenio.legacy.bibcatalog.system import get_bibcat_from_prefs
from invenio.legacy.webpage import page
from invenio.utils.html import get_mathjax_header
from invenio.utils.text import wash_for_xml, show_diff
from invenio.modules.knowledge.api import get_kbd_values_for_bibedit, get_kbr_values, \
get_kbt_items_for_bibedit, kb_exists
from invenio.legacy.bibcirculation.db_layer import get_number_copies, has_copies
from invenio.legacy.bibcirculation.utils import create_item_details_url
from invenio.legacy.refextract.api import FullTextNotAvailable, \
get_pdf_doc, \
record_has_fulltext
from invenio.legacy.bibrecord import xmlmarc2textmarc as xmlmarc2textmarc
from invenio.utils.crossref import get_marcxml_for_doi, CrossrefError
from invenio.base.globals import cfg
try:
BIBCATALOG_SYSTEM.ticket_search(0)
CFG_CAN_SEARCH_FOR_TICKET = True
except NotImplementedError:
CFG_CAN_SEARCH_FOR_TICKET = False
re_revdate_split = re.compile(r'^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)')
def bibedit_register_exception(data):
register_exception(alert_admin=True,
prefix="\n".join(data['action_log']))
def get_empty_fields_templates():
"""
Returning the templates of empty fields::
-an empty data field
-an empty control field
"""
return [{
"name": "Empty field",
"description": "The data field not containing any " +
"information filled in",
"tag" : "",
"ind1" : "",
"ind2" : "",
"subfields" : [("", "")],
"isControlfield" : False
}, {
"name" : "Empty control field",
"description" : "The controlfield not containing any " +
"data or tag description",
"isControlfield" : True,
"tag" : "",
"value" : ""
}]
def get_available_fields_templates():
"""
A method returning all the available field templates
Returns a list of descriptors. Each descriptor has
the same structure as a full field descriptor inside the
record
"""
templates = get_field_templates()
result = get_empty_fields_templates()
for template in templates:
tplTag = template[3].keys()[0]
field = template[3][tplTag][0]
if (field[0] == []):
# if the field is a controlField, add different structure
result.append({
"name" : template[1],
"description" : template[2],
"isControlfield" : True,
"tag" : tplTag,
"value" : field[3]
})
else:
result.append({
"name": template[1],
"description": template[2],
"tag" : tplTag,
"ind1" : field[1],
"ind2" : field[2],
"subfields" : field[0],
"isControlfield" : False
})
return result
def perform_request_init(uid, ln, req, lastupdated):
"""Handle the initial request by adding menu and JavaScript to the page."""
errors = []
warnings = []
body = ''
# Add script data.
record_templates = get_record_templates()
record_templates.sort()
tag_names = get_name_tags_all()
protected_fields = ['001']
protected_fields.extend(cfg['CFG_BIBEDIT_PROTECTED_FIELDS'].split(','))
cern_site = 'false'
if not CFG_JSON_AVAILABLE:
title = 'Record Editor'
body = '''Sorry, the record editor cannot operate when the
`simplejson' module is not installed. Please see the INSTALL
file.'''
return page(title = title,
body = body,
errors = [],
warnings = [],
uid = uid,
language = ln,
navtrail = "",
lastupdated = lastupdated,
req = req)
body += '<link rel="stylesheet" type="text/css" href="/vendors/jquery-ui/themes/redmond/jquery-ui.min.css" />'
body += '<link rel="stylesheet" type="text/css" href="/vendors/jquery-ui/themes/redmond/theme.css" />'
body += '<link rel="stylesheet" type="text/css" href="%s" />' % (
url_for('editor.static', filename='css/editor/base.css'), )
if CFG_CERN_SITE:
cern_site = 'true'
data = {'gRECORD_TEMPLATES': record_templates,
'gTAG_NAMES': tag_names,
'gPROTECTED_FIELDS': protected_fields,
'gINTERNAL_DOI_PROTECTION_LEVEL': CFG_BIBEDIT_INTERNAL_DOI_PROTECTION_LEVEL,
'gSITE_URL': CFG_SITE_URL,
'gSITE_RECORD': CFG_SITE_RECORD,
'gCERN_SITE': cern_site,
'gINSPIRE_SITE': CFG_INSPIRE_SITE,
'gHASH_CHECK_INTERVAL': cfg['CFG_BIBEDIT_JS_HASH_CHECK_INTERVAL'],
'gCHECK_SCROLL_INTERVAL': cfg['CFG_BIBEDIT_JS_CHECK_SCROLL_INTERVAL'],
'gSTATUS_ERROR_TIME': cfg['CFG_BIBEDIT_JS_STATUS_ERROR_TIME'],
'gSTATUS_INFO_TIME': cfg['CFG_BIBEDIT_JS_STATUS_INFO_TIME'],
'gCLONED_RECORD_COLOR':
'"' + cfg['CFG_BIBEDIT_JS_CLONED_RECORD_COLOR'] + '"',
'gCLONED_RECORD_COLOR_FADE_DURATION':
cfg['CFG_BIBEDIT_JS_CLONED_RECORD_COLOR_FADE_DURATION'],
'gNEW_ADD_FIELD_FORM_COLOR':
'"' + cfg['CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR'] + '"',
'gNEW_ADD_FIELD_FORM_COLOR_FADE_DURATION':
cfg['CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR_FADE_DURATION'],
'gNEW_CONTENT_COLOR': '"' + cfg['CFG_BIBEDIT_JS_NEW_CONTENT_COLOR'] + '"',
'gNEW_CONTENT_COLOR_FADE_DURATION':
cfg['CFG_BIBEDIT_JS_NEW_CONTENT_COLOR_FADE_DURATION'],
'gNEW_CONTENT_HIGHLIGHT_DELAY':
cfg['CFG_BIBEDIT_JS_NEW_CONTENT_HIGHLIGHT_DELAY'],
'gTICKET_REFRESH_DELAY': cfg['CFG_BIBEDIT_JS_TICKET_REFRESH_DELAY'],
'gRESULT_CODES': cfg['CFG_BIBEDIT_AJAX_RESULT_CODES'],
'gAUTOSUGGEST_TAGS' : cfg['CFG_BIBEDIT_AUTOSUGGEST_TAGS'],
'gAUTOCOMPLETE_TAGS' : cfg['CFG_BIBEDIT_AUTOCOMPLETE_TAGS_KBS'].keys(),
'gKEYWORD_TAG' : '"' + cfg['CFG_BIBEDIT_KEYWORD_TAG'] + '"',
'gREQUESTS_UNTIL_SAVE' : cfg['CFG_BIBEDIT_REQUESTS_UNTIL_SAVE'],
'gAVAILABLE_KBS': get_available_kbs(),
'gDOILookupField': '"' + cfg['CFG_BIBEDIT_DOI_LOOKUP_FIELD'] + '"',
'gDisplayReferenceTags': cfg['CFG_BIBEDIT_DISPLAY_REFERENCE_TAGS'],
'gDisplayAuthorTags': cfg['CFG_BIBEDIT_DISPLAY_AUTHOR_TAGS'],
'gExcludeCuratorTags': cfg['CFG_BIBEDIT_EXCLUDE_CURATOR_TAGS'],
'gSHOW_HP_REMOVED_FIELDS': CFG_BIBEDIT_SHOW_HOLDING_PEN_REMOVED_FIELDS,
'gBIBCATALOG_SYSTEM_RT_URL': repr(CFG_BIBCATALOG_SYSTEM_RT_URL),
'gAutoComplete': json.dumps(CFG_BIBEDIT_AUTOCOMPLETE)
}
def convert(data):
"""Return JS friendly strings. """
if isinstance(data, unicode):
return str(data)
else:
return json.dumps(data)
body += '<script type="text/javascript">\n'
for key in data:
body += ' var %s = %s;\n' % (key, convert(data[key]))
body += ' </script>\n'
# Adding the information about field templates
fieldTemplates = get_available_fields_templates()
body += "<script>\n" + \
" var fieldTemplates = %s\n" % (json.dumps(fieldTemplates), ) + \
"</script>\n"
# Add scripts (the ordering is NOT irrelevant).
scripts = [
'vendors/jquery.jeditable/index.js',
'vendors/jquery.hotkeys/jquery.hotkeys.js',
'vendors/json2/json2.js'
]
bibedit_scripts = [
'refextract.js',
'display.js',
'engine.js',
'keys.js',
'menu.js',
'holdingpen.js',
'marcxml.js',
'clipboard.js'
]
for script in scripts:
body += ' <script type="text/javascript" src="%s">' \
'</script>\n' % (url_for('static', filename=script), )
for script in bibedit_scripts:
body += ' <script type="text/javascript" src="%s">' \
'</script>\n' % (url_for('editor.static',
filename='js/editor/' + script), )
# Init BibEdit
body += '<script>$(init_bibedit);</script>'
# Build page structure and menu.
# rec = create_record(format_record(235, "xm"))[0]
#oaiId = record_extract_oai_id(rec)
body += bibedit_templates.menu()
body += bibedit_templates.focuson()
body += """<div id="bibEditContent">
<div class="revisionLine"></div>
<div id="Toptoolbar"></div>
<div id="bibEditMessage"></div>
<div id="bibEditContentTable"></div>
</div>"""
return body, errors, warnings
def get_available_kbs():
"""
Return list of KBs that are available in the system to be used with
BibEdit
"""
kb_list = [CFG_BIBEDIT_KB_SUBJECTS]
available_kbs = [kb for kb in kb_list if kb_exists(kb)]
return available_kbs
def get_marcxml_of_revision_id(recid, revid):
"""
Return MARCXML string with corresponding to revision REVID
(=RECID.REVDATE) of a record. Return empty string if revision
does not exist.
"""
job_date = "%s-%s-%s %s:%s:%s" % re_revdate_split.search(revid).groups()
tmp_res = get_marcxml_of_record_revision(recid, job_date)
if tmp_res:
for row in tmp_res:
xml = zlib.decompress(row[0]) + "\n"
# xml contains marcxml of record
# now we create a record object from this xml and sort fields and subfields
# and return marcxml
rec = create_record(xml)[0]
record_order_subfields(rec)
marcxml = record_xml_output(rec, order_fn="_order_by_tags")
return marcxml
def perform_request_compare(ln, recid, rev1, rev2):
"""Handle a request for comparing two records"""
body = ""
errors = []
warnings = []
person1 = ""
person2 = ""
if (not record_revision_exists(recid, rev1)) or \
(not record_revision_exists(recid, rev2)):
body = "The requested record revision does not exist !"
else:
xml1 = get_marcxml_of_revision_id(recid, rev1)
xml2 = get_marcxml_of_revision_id(recid, rev2)
# Create MARC representations of the records
marc1 = create_marc_record(create_record(xml1)[0], '', {"text-marc": 1, "aleph-marc": 0})
marc2 = create_marc_record(create_record(xml2)[0], '', {"text-marc": 1, "aleph-marc": 0})
comparison = show_diff(marc1,
marc2,
prefix="<pre>", suffix="</pre>",
prefix_removed='<strong class="diff_field_deleted">',
suffix_removed='</strong>',
prefix_added='<strong class="diff_field_added">',
suffix_added='</strong>')
job_date1 = "%s-%s-%s %s:%s:%s" % re_revdate_split.search(rev1).groups()
job_date2 = "%s-%s-%s %s:%s:%s" % re_revdate_split.search(rev2).groups()
# Geting the author of each revision
info1 = get_info_of_record_revision(recid, job_date1)
info2 = get_info_of_record_revision(recid, job_date2)
if info1:
person1 = info1[0][1]
if info2:
person2 = info2[0][1]
body += bibedit_templates.history_comparebox(ln, job_date1, job_date2,
person1, person2, comparison)
return body, errors, warnings
def perform_request_newticket(recid, uid):
"""create a new ticket with this record's number
@param recid: record id
@param uid: user id
@return: (error_msg, url)
"""
t_url = ""
errmsg = ""
if CFG_BIBCATALOG_SYSTEM is not None:
t_id = BIBCATALOG_SYSTEM.ticket_submit(uid, "", recid, "")
if t_id:
#get the ticket's URL
t_url = BIBCATALOG_SYSTEM.ticket_get_attribute(uid, t_id, 'url_modify')
else:
errmsg = "ticket_submit failed"
else:
errmsg = "No ticket system configured"
return (errmsg, t_url)
def perform_request_ajax(req, recid, uid, data, isBulk=False):
try:
return _perform_request_ajax(req, recid, uid, data, isBulk)
except: # pylint: disable=W0702
# Custom error exception for bibedit
# We have an action log that we want to display in full
bibedit_register_exception(data)
return {'resultCode': CFG_BIBEDIT_AJAX_RESULT_CODES_REV['server_error']}
def _perform_request_ajax(req, recid, uid, data, isBulk=False):
"""Handle Ajax requests by redirecting to appropriate function."""
response = {}
request_type = data['requestType']
undo_redo = None
if "undoRedo" in data:
undo_redo = data["undoRedo"]
# Call function based on request type.
if request_type == 'searchForRecord':
# Search request.
response.update(perform_request_bibedit_search(data, req))
elif request_type in ['changeTagFormat']:
# User related requests.
response.update(perform_request_user(req, request_type, recid, data))
elif request_type in ('getRecord', 'submit', 'cancel', 'newRecord',
'deleteRecord', 'deleteRecordCache', 'prepareRecordMerge', 'revert',
'updateCacheRef', 'submittextmarc'):
# 'Major' record related requests.
response.update(perform_request_record(req, request_type, recid, uid,
data))
elif request_type in ('addField', 'addSubfields',
'addFieldsSubfieldsOnPositions', 'modifyContent',
'modifySubfieldTag', 'modifyFieldTag',
'moveSubfield', 'deleteFields', 'moveField',
'modifyField', 'otherUpdateRequest',
'disableHpChange', 'deactivateHoldingPenChangeset'):
# Record updates.
cacheMTime = data['cacheMTime']
if 'hpChanges' in data:
hpChanges = data['hpChanges']
else:
hpChanges = {}
response.update(perform_request_update_record(request_type, recid,
uid, cacheMTime, data,
hpChanges, undo_redo,
isBulk))
elif request_type in ('autosuggest', 'autocomplete', 'autokeyword'):
response.update(perform_request_autocomplete(request_type, recid, uid,
data))
elif request_type in ('getTickets', 'closeTicket', 'openTicket', 'createTicket','getNewTicketRTInfo'):
# BibCatalog requests.
response.update(perform_request_bibcatalog(request_type, uid, data))
elif request_type in ('getHoldingPenUpdates', ):
response.update(perform_request_holdingpen(request_type, recid))
elif request_type in ('getHoldingPenUpdateDetails',
'deleteHoldingPenChangeset'):
updateId = data['changesetNumber']
response.update(perform_request_holdingpen(request_type, recid,
updateId))
elif request_type in ('applyBulkUpdates', ):
# a general version of a bulk request
changes = data['requestsData']
cacheMTime = data['cacheMTime']
response.update(perform_bulk_request_ajax(req, recid, uid, changes,
undo_redo, cacheMTime))
elif request_type in ('preview', ):
response.update(perform_request_preview_record(request_type, recid, uid, data))
elif request_type in ('get_pdf_url', ):
response.update(perform_request_get_pdf_url(recid))
elif request_type in ('refextract', ):
txt = None
if 'txt' in data:
txt = data["txt"]
response.update(perform_request_ref_extract(recid, uid, txt))
elif request_type in ('refextracturl', ):
response.update(perform_request_ref_extract_url(recid, uid, data['url']))
elif request_type == 'getTextMarc':
response.update(perform_request_get_textmarc(recid, uid))
elif request_type == "getTableView":
response.update(perform_request_get_tableview(recid, uid, data))
elif request_type == "DOISearch":
response.update(perform_doi_search(data['doi']))
elif request_type == "deactivateRecordCache":
deactivate_cache(recid, uid)
response.update({"cacheMTime": data['cacheMTime']})
elif request_type == "guessAffiliations":
response.update(perform_guess_affiliations(uid, data))
return response
def perform_bulk_request_ajax(req, recid, uid, reqsData, undoRedo, cacheMTime):
""" An AJAX handler used when treating bulk updates """
lastResult = {}
lastTime = cacheMTime
if get_cache_mtime(recid, uid) != cacheMTime:
return {"resultCode": 107}
isFirst = True
for data in reqsData:
assert data is not None
data['cacheMTime'] = lastTime
if isFirst and undoRedo is not None:
# we add the undo/redo handler to the first operation in order to
# save the handler on the server side !
data['undoRedo'] = undoRedo
isFirst = False
lastResult = _perform_request_ajax(req, recid, uid, data, isBulk=True)
lastTime = lastResult['cacheMTime']
return lastResult
def perform_request_bibedit_search(data, req):
"""Handle search requests."""
response = {}
searchType = data['searchType']
if searchType is None:
searchType = "anywhere"
searchPattern = data['searchPattern']
if searchType == 'anywhere':
pattern = searchPattern
else:
pattern = searchType + ':' + searchPattern
pattern = urllib.unquote(pattern)
result_set = list(perform_request_search(req=req, p=pattern))
response['resultCode'] = 1
response['resultSet'] = result_set[0:cfg['CFG_BIBEDIT_MAX_SEARCH_RESULTS']]
return response
def perform_request_user(req, request_type, recid, data):
"""Handle user related requests."""
response = {}
if request_type == 'changeTagFormat':
tagformat_settings = session_param_get(req, 'bibedit_tagformat', {})
tagformat_settings[recid] = data['tagFormat']
session_param_set(req, 'bibedit_tagformat', tagformat_settings)
response['resultCode'] = 2
return response
def perform_request_holdingpen(request_type, recId, changeId=None):
"""
A method performing the holdingPen ajax request. The following types of
requests can be made::
-getHoldingPenUpdates: retrieving the holding pen updates pending
for a given record
"""
response = {}
if request_type == 'getHoldingPenUpdates':
changeSet = get_related_hp_changesets(recId)
changes = []
for change in changeSet:
changes.append((str(change[0]), str(change[1])))
changes.reverse() # newest to older order
response["changes"] = changes
elif request_type == 'getHoldingPenUpdateDetails':
# returning the list of changes related to the holding pen update
# the format based on what the record difference xtool returns
assert(changeId is not None)
hpContent = get_hp_update_xml(changeId)
holdingPenRecord = create_record(hpContent[0], "xm")[0]
if not holdingPenRecord:
response['resultCode'] = 107
else:
template_to_merge = extend_record_with_template(recId)
if template_to_merge:
merged_record = merge_record_with_template(holdingPenRecord,
template_to_merge,
is_hp_record=True)
if merged_record:
holdingPenRecord = merged_record
# order subfields alphabetically
record_order_subfields(holdingPenRecord)
# databaseRecord = get_record(hpContent[1])
response['record'] = holdingPenRecord
response['changeset_number'] = changeId
elif request_type == 'deleteHoldingPenChangeset':
assert(changeId is not None)
delete_hp_change(changeId)
return response
def perform_request_record(req, request_type, recid, uid, data, ln=CFG_SITE_LANG):
"""Handle 'major' record related requests like fetching, submitting or
deleting a record, cancel editing or preparing a record for merging.
"""
response = {}
if request_type == 'newRecord':
# Create a new record.
new_recid = reserve_record_id()
new_type = data['newType']
if new_type == 'empty':
# Create a new empty record.
create_cache(recid, uid)
response['resultCode'], response['newRecID'] = 6, new_recid
elif new_type == 'template':
# Create a new record from XML record template.
template_filename = data['templateFilename']
template = get_record_template(template_filename)
if not template:
response['resultCode'] = 108
else:
record = create_record(template)[0]
if not record:
response['resultCode'] = 109
else:
record_add_field(record, '001',
controlfield_value=str(new_recid))
create_cache(new_recid, uid, record, True)
response['cacheMTime'] = get_cache_mtime(new_recid, uid)
response['resultCode'], response['newRecID'] = 7, new_recid
elif new_type == 'import':
# Import data from external source, using DOI
doi = data['doi']
if not doi:
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_no_doi_specified']
else:
try:
marcxml_template = get_marcxml_for_doi(doi)
except CrossrefError as inst:
response['resultCode'] = \
cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV'][inst.code]
else:
record = crossref_process_template(marcxml_template, CFG_INSPIRE_SITE)
if not record:
response['resultCode'] = 109
else:
record_add_field(record, '001',
controlfield_value=str(new_recid))
template_to_merge = extend_record_with_template(recstruct=record)
if template_to_merge:
merged_record = merge_record_with_template(record, template_to_merge)
if merged_record:
record = merged_record
create_cache(new_recid, uid, record, True)
response['cacheMTime'] = get_cache_mtime(new_recid, uid)
response['resultCode'], response['newRecID'] = 7, new_recid
elif new_type == 'clone':
# Clone an existing record (from the users cache).
existing_cache = cache_exists(recid, uid)
if existing_cache:
try:
cache = get_cache_contents(recid, uid)
record = cache[2]
except InvalidCache:
# if, for example, the cache format was wrong (outdated)
record = get_bibrecord(recid)
else:
# Cache missing. Fall back to using original version.
record = get_bibrecord(recid)
record_delete_field(record, '001')
record_delete_field(record, '005')
record_add_field(record, '001', controlfield_value=str(new_recid))
create_cache(new_recid, uid, record, True)
response['resultCode'], response['newRecID'] = 8, new_recid
elif request_type == 'getRecord':
# Fetch the record. Possible error situations:
# - Non-existing record
# - Deleted record
# - Record locked by other user
# - Record locked by queue
# A cache file will be created if it does not exist.
# If the cache is outdated (i.e., not based on the latest DB revision),
# cacheOutdated will be set to True in the response.
record_status = record_exists(recid)
existing_cache = cache_exists(recid, uid)
read_only_mode = False
if "inReadOnlyMode" in data:
read_only_mode = data['inReadOnlyMode']
if data.get('deleteRecordCache'):
delete_cache(recid, uid)
existing_cache = False
pending_changes = []
disabled_hp_changes = {}
if record_status == 0:
response['resultCode'] = 102
elif not read_only_mode and not existing_cache and \
record_locked_by_other_user(recid, uid):
name, email, locked_since = record_locked_by_user_details(recid, uid)
response['locked_details'] = {'name': name,
'email': email,
'locked_since': locked_since}
response['resultCode'] = 104
elif not read_only_mode and existing_cache and \
cache_expired(recid, uid) and \
record_locked_by_other_user(recid, uid):
response['resultCode'] = 104
elif not read_only_mode and record_locked_by_queue(recid):
response['resultCode'] = 105
else:
if read_only_mode:
if 'recordRevision' in data and data['recordRevision'] != 'sampleValue':
record_revision_ts = data['recordRevision']
record_xml = get_marcxml_of_revision(recid,
record_revision_ts)
record = create_record(record_xml)[0]
record_revision = timestamp_to_revision(record_revision_ts)
pending_changes = []
disabled_hp_changes = {}
else:
# a normal cacheless retrieval of a record
record = get_bibrecord(recid)
record_revision = get_record_last_modification_date(recid)
if record_revision is None:
record_revision = datetime.now().timetuple()
pending_changes = []
disabled_hp_changes = {}
cache_dirty = False
mtime = 0
undo_list = []
redo_list = []
else:
try:
cache_dirty, record_revision, record, pending_changes, \
disabled_hp_changes, undo_list, redo_list = \
get_cache_contents(recid, uid)
except InvalidCache:
# No cache found in the DB
record_revision, record = create_cache(recid, uid)
if not record:
response['resultCode'] = 103
return response
pending_changes = []
disabled_hp_changes = {}
cache_dirty = False
undo_list = []
redo_list = []
else:
touch_cache(recid, uid)
if not latest_record_revision(recid, record_revision) and \
get_record_revisions(recid) != ():
# This sould prevent from using old cache in case of
# viewing old version. If there are no revisions,
# it means we should skip this step because this
# is a new record
response['cacheOutdated'] = True
mtime = get_cache_mtime(recid, uid)
if data.get('clonedRecord', ''):
response['resultCode'] = 9
else:
response['resultCode'] = 3
revision_author = get_record_revision_author(recid, record_revision)
latest_revision = get_record_last_modification_date(recid)
if latest_revision is None:
latest_revision = datetime.now().timetuple()
last_revision_ts = revision_to_timestamp(latest_revision)
revisions_history = get_record_revision_timestamps(recid)
revisions_authors = get_record_revision_authors(recid)
number_of_physical_copies = get_number_copies(recid)
bibcirc_details_URL = create_item_details_url(recid, ln)
can_have_copies = can_record_have_physical_copies(recid)
managed_DOIs = [doi for doi in record_extract_dois(record) if \
re.compile(CFG_BIBUPLOAD_INTERNAL_DOI_PATTERN).match(doi)]
# For some collections, merge template with record
template_to_merge = extend_record_with_template(recid)
if template_to_merge and not read_only_mode:
merged_record = merge_record_with_template(record, template_to_merge)
if merged_record:
record = merged_record
mtime = update_cache_contents(recid, uid, record_revision,
record, pending_changes,
disabled_hp_changes,
undo_list, redo_list)
if record_status == -1:
# The record was deleted
response['resultCode'] = 103
response['record_has_pdf'] = record_has_fulltext(recid)
response['record_hide_authors'] = check_hide_authors(record)
response['cacheDirty'], response['record'], \
response['cacheMTime'], response['recordRevision'], \
response['revisionAuthor'], response['lastRevision'], \
response['revisionsHistory'], response['revisionsAuthors'], \
response['inReadOnlyMode'], response['pendingHpChanges'], \
response['disabledHpChanges'], response['undoList'], \
response['redoList'] = cache_dirty, \
record, mtime, revision_to_timestamp(record_revision), \
revision_author, last_revision_ts, revisions_history, \
revisions_authors, read_only_mode, pending_changes, \
disabled_hp_changes, undo_list, redo_list
response['numberOfCopies'] = number_of_physical_copies
response['managed_DOIs'] = managed_DOIs
response['bibCirculationUrl'] = bibcirc_details_URL
response['canRecordHavePhysicalCopies'] = can_have_copies
# Set tag format from user's session settings.
tagformat_settings = session_param_get(req, 'bibedit_tagformat')
tagformat = (tagformat_settings is not None) and tagformat_settings.get(recid, cfg['CFG_BIBEDIT_TAG_FORMAT']) or cfg['CFG_BIBEDIT_TAG_FORMAT']
response['tagFormat'] = tagformat
# KB information
response['KBSubject'] = CFG_BIBEDIT_KB_SUBJECTS
# Autocomplete information
response['primaryCollection'] = guess_primary_collection_of_a_record(recid)
elif request_type == 'submit':
# Submit the record. Possible error situations:
# - Missing cache file
# - Cache file modified in other editor
# - Record locked by other user
# - Record locked by queue
# If the cache is outdated cacheOutdated will be set to True in the
# response.
perform_request_submit(recid=recid,
uid=uid,
data=data,
response=response)
elif request_type == 'revert':
revId = data['revId']
job_date = "%s-%s-%s %s:%s:%s" % re_revdate_split.search(revId).groups()
revision_xml = get_marcxml_of_revision(recid, job_date)
# Modify the 005 tag in order to merge with the latest version of record
last_revision_ts = data['lastRevId'] + ".0"
revision_xml = modify_record_timestamp(revision_xml, last_revision_ts)
save_xml_record(recid, uid, revision_xml)
if (cache_exists(recid, uid)):
delete_cache(recid, uid)
response['resultCode'] = 4
elif request_type == 'cancel':
# Cancel editing by deleting the cache file. Possible error situations:
# - Cache file modified in other editor
if cache_exists(recid, uid):
if get_cache_mtime(recid, uid) == data['cacheMTime']:
delete_cache(recid, uid)
response['resultCode'] = 5
else:
response['resultCode'] = 107
else:
response['resultCode'] = 5
elif request_type == 'deleteRecord':
# Submit the record. Possible error situations:
# - Record locked by other user
# - Record locked by queue
# As the user is requesting deletion we proceed even if the cache file
# is missing and we don't check if the cache is outdated or has
# been modified in another editor.
existing_cache = cache_exists(recid, uid)
pending_changes = []
if has_copies(recid):
response['resultCode'] = \
cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_physical_copies_exist']
elif existing_cache and cache_expired(recid, uid) and \
record_locked_by_other_user(recid, uid):
response['resultCode'] = \
cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_rec_locked_by_user']
elif record_locked_by_queue(recid):
response['resultCode'] = \
cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_rec_locked_by_queue']
else:
if not existing_cache:
create_cache(recid, uid)
record_revision, record, pending_changes, \
deactivated_hp_changes, undo_list, redo_list = \
get_cache_contents(recid, uid)[1:]
else:
try:
dummy_cache_dirty, record_revision, record, \
pending_changes, deactivated_hp_changes, undo_list, \
redo_list = get_cache_contents(recid, uid)
except InvalidCache:
record_revision, record, pending_changes, \
deactivated_hp_changes = create_cache(recid, uid)
record_add_field(record, '980', ' ', ' ', '', [('c', 'DELETED')])
undo_list = []
redo_list = []
update_cache_contents(recid, uid, record_revision, record,
pending_changes, deactivated_hp_changes,
undo_list, redo_list)
save_xml_record(recid, uid)
delete_related_holdingpen_changes(recid) # we don't need any changes
# related to a deleted record
response['resultCode'] = 10
elif request_type == 'deleteRecordCache':
# Delete the cache file. Ignore the request if the cache has been
# modified in another editor.
if 'cacheMTime' in data:
if cache_exists(recid, uid) and get_cache_mtime(recid, uid) == \
data['cacheMTime']:
delete_cache(recid, uid)
response['resultCode'] = 11
elif request_type == 'updateCacheRef':
# Update cache with the contents coming from BibEdit JS interface
# Used when updating references using ref extractor
record_revision, record, pending_changes, \
deactivated_hp_changes, undo_list, redo_list = \
get_cache_contents(recid, uid)[1:]
record = create_record(data['recXML'])[0]
response['cacheMTime'] = update_cache_contents(recid,
uid,
record_revision,
record,
pending_changes,
deactivated_hp_changes,
undo_list,
redo_list)
response['cacheDirty'] = True
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['cache_updated_with_references']
elif request_type == 'prepareRecordMerge':
# We want to merge the cache with the current DB version of the record,
# so prepare an XML file from the file cache, to be used by BibMerge.
# Possible error situations:
# - Missing cache file
# - Record locked by other user
# - Record locked by queue
# We don't check if cache is outdated (a likely scenario for this
# request) or if it has been modified in another editor.
if not cache_exists(recid, uid):
response['resultCode'] = 106
elif cache_expired(recid, uid) and \
record_locked_by_other_user(recid, uid):
response['resultCode'] = 104
elif record_locked_by_queue(recid):
response['resultCode'] = 105
else:
save_xml_record(recid, uid, to_upload=False, to_merge=True)
response['resultCode'] = 12
elif request_type == 'submittextmarc':
# Textmarc content coming from the user
textmarc_record = data['textmarc']
xml_conversion_status = get_xml_from_textmarc(recid, textmarc_record, uid)
if xml_conversion_status['resultMsg'] == "textmarc_parsing_error":
response.update(xml_conversion_status)
return response
# Simulate upload to catch errors
errors_upload = perform_upload_check(xml_conversion_status['resultXML'], '--replace')
if errors_upload:
response['resultCode'], response['errors'] = 113, \
errors_upload
return response
response.update(xml_conversion_status)
if xml_conversion_status['resultMsg'] == 'textmarc_parsing_success':
create_cache(recid, uid,
create_record(response['resultXML'])[0])
save_xml_record(recid, uid)
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']["record_submitted"]
return response
def perform_upload_check(xml_record, mode):
"""Perform a upload simulation with the given record and mode.
@return: string describing errors
@rtype: string
"""
import invenio.legacy.bibupload.engine as bibupload_module
from invenio.legacy.bibupload.engine import xml_marc_to_records, bibupload
from invenio.legacy.bibrecord import \
record_strip_empty_volatile_subfields, \
record_strip_empty_fields
error_cache = []
def my_writer(msg, stream=sys.stdout, verbose=1):
if verbose == 1:
if 'DONE' not in msg:
error_cache.append(msg.strip())
orig_writer = bibupload_module.write_message
bibupload_module.write_message = my_writer
error_cache.extend(perform_basic_upload_checks(xml_record))
if error_cache:
# There has been some critical error
return '\n'.join(error_cache)
recs = xml_marc_to_records(xml_record)
try:
upload_mode = mode[2:]
# Adapt input data for bibupload function
if upload_mode == "r insert-or-replace":
upload_mode = "replace_or_insert"
for record in recs:
if record:
record_strip_empty_volatile_subfields(record)
record_strip_empty_fields(record)
bibupload(record, opt_mode=upload_mode, pretend=True)
finally:
bibupload_module.write_message = orig_writer
return '\n'.join(error_cache)
def perform_basic_upload_checks(xml_record):
""" Performs tests that would provoke the bibupload task to fail with
an exit status 1, to prevent batchupload from crashing while alarming
the user wabout the issue
"""
from invenio.legacy.bibupload.engine import writing_rights_p
from invenio.legacy.bibrecord import create_records
errors = []
if not writing_rights_p():
errors.append("Error: BibUpload does not have rights to write fulltext files.")
recs = create_records(xml_record, 1, 1)
if recs == []:
errors.append("Error: Cannot parse MARCXML file.")
elif recs[0][0] is None:
errors.append("Error: MARCXML file has wrong format: %s" % recs)
return errors
def perform_request_update_record(request_type, recid, uid, cacheMTime, data,
hpChanges, undoRedoOp, isBulk=False):
"""
Handle record update requests like adding, modifying, moving or deleting
of fields or subfields. Possible common error situations::
- Missing cache file
- Cache file modified in other editor
@param undoRedoOp: Indicates in "undo"/"redo"/undo_descriptor operation is
performed by a current request.
"""
response = {}
if not cache_exists(recid, uid):
response['resultCode'] = 106
elif get_cache_mtime(recid, uid) != cacheMTime and isBulk is False:
# In case of a bulk request, the changes are deliberately performed
# immediately one after another
response['resultCode'] = 107
else:
record_revision, record, pending_changes, deactivated_hp_changes, \
undo_list, redo_list = get_cache_contents(recid, uid)[1:]
# process all the Holding Pen changes operations ... regardles the
# request type
if "toDisable" in hpChanges:
for changeId in hpChanges["toDisable"]:
pending_changes[changeId]["applied_change"] = True
if "toEnable" in hpChanges:
for changeId in hpChanges["toEnable"]:
pending_changes[changeId]["applied_change"] = False
if "toOverride" in hpChanges:
pending_changes = hpChanges["toOverride"]
if "changesetsToDeactivate" in hpChanges:
for changesetId in hpChanges["changesetsToDeactivate"]:
deactivated_hp_changes[changesetId] = True
if "changesetsToActivate" in hpChanges:
for changesetId in hpChanges["changesetsToActivate"]:
deactivated_hp_changes[changesetId] = False
# processing the undo/redo entries
if undoRedoOp == "undo":
try:
redo_list = [undo_list[-1]] + redo_list
undo_list = undo_list[:-1]
except:
raise Exception("An exception occured when undoing previous" +
" operation. Undo list: " + str(undo_list) +
" Redo list " + str(redo_list))
elif undoRedoOp == "redo":
try:
undo_list = undo_list + [redo_list[0]]
redo_list = redo_list[1:]
except:
raise Exception("An exception occured when redoing previous" +
" operation. Undo list: " + str(undo_list) +
" Redo list " + str(redo_list))
else:
# This is a genuine operation - we have to add a new descriptor
# to the undo list and cancel the redo unless the operation is
# a bulk operation
if undoRedoOp is not None:
undo_list = undo_list + [undoRedoOp]
redo_list = []
else:
assert isBulk is True
field_position_local = data.get('fieldPosition')
if field_position_local is not None:
field_position_local = int(field_position_local)
if request_type == 'otherUpdateRequest':
# An empty request. Might be useful if we want to perform
# operations that require only the actions performed globally,
# like modifying the holdingPen changes list
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV'][
'editor_modifications_changed']
elif request_type == 'deactivateHoldingPenChangeset':
# the changeset has been marked as processed ( user applied it in
# the editor). Marking as used in the cache file.
# CAUTION: This function has been implemented here because logically
# it fits with the modifications made to the cache file.
# No changes are made to the Holding Pen physically. The
# changesets are related to the cache because we want to
# cancel the removal every time the cache disappears for
# any reason
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV'][
'disabled_hp_changeset']
elif request_type == 'addField':
if data['controlfield']:
record_add_field(record, data['tag'],
controlfield_value=data['value'])
response['resultCode'] = 20
else:
record_add_field(record, data['tag'], data['ind1'],
data['ind2'], subfields=data['subfields'],
field_position_local=field_position_local)
response['resultCode'] = 21
elif request_type == 'addSubfields':
subfields = data['subfields']
for subfield in subfields:
record_add_subfield_into(record, data['tag'], subfield[0],
subfield[1], subfield_position=None,
field_position_local=field_position_local)
if len(subfields) == 1:
response['resultCode'] = 22
else:
response['resultCode'] = 23
elif request_type == 'addFieldsSubfieldsOnPositions':
#1) Sorting the fields by their identifiers
fieldsToAdd = data['fieldsToAdd']
subfieldsToAdd = data['subfieldsToAdd']
for tag in fieldsToAdd.keys():
positions = fieldsToAdd[tag].keys()
positions.sort()
for position in positions:
# now adding fields at a position
isControlfield = (len(fieldsToAdd[tag][position][0]) == 0)
# if there are n subfields, this is a control field
if isControlfield:
controlfieldValue = fieldsToAdd[tag][position][3]
record_add_field(record, tag,
field_position_local=int(position),
controlfield_value=controlfieldValue)
else:
subfields = fieldsToAdd[tag][position][0]
ind1 = fieldsToAdd[tag][position][1]
ind2 = fieldsToAdd[tag][position][2]
record_add_field(record, tag, ind1, ind2,
subfields=subfields,
field_position_local=int(position))
# now adding the subfields
for tag in subfieldsToAdd.keys():
for fieldPosition in subfieldsToAdd[tag].keys(): # now the fields
# order not important !
subfieldsPositions = subfieldsToAdd[tag][fieldPosition]. \
keys()
subfieldsPositions.sort()
for subfieldPosition in subfieldsPositions:
subfield = subfieldsToAdd[tag][fieldPosition][subfieldPosition]
record_add_subfield_into(record, tag, subfield[0], subfield[1],
subfield_position=int(subfieldPosition),
field_position_local=int(fieldPosition))
response['resultCode'] = \
cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['added_positioned_subfields']
elif request_type == 'modifyField': # changing the field structure
# first remove subfields and then add new... change the indices
subfields = data['subFields'] # parse the JSON representation of
# the subfields here
new_field = create_field(subfields, data['ind1'], data['ind2'])
record_replace_field(record, data['tag'], new_field,
field_position_local=data['fieldPosition'])
response['resultCode'] = 26
elif request_type == 'modifyContent':
if data['subfieldIndex'] is not None:
record_modify_subfield(record, data['tag'],
data['subfieldCode'], data['value'],
int(data['subfieldIndex']),
field_position_local=field_position_local)
else:
record_modify_controlfield(record, data['tag'], data["value"],
field_position_local=field_position_local)
response['resultCode'] = 24
elif request_type == 'modifySubfieldTag':
record_add_subfield_into(record, data['tag'], data['subfieldCode'],
data["value"], subfield_position= int(data['subfieldIndex']),
field_position_local=field_position_local)
record_delete_subfield_from(record, data['tag'], int(data['subfieldIndex']) + 1,
field_position_local=field_position_local)
response['resultCode'] = 24
elif request_type == 'modifyFieldTag':
subfields = record_get_subfields(record, data['oldTag'],
field_position_local=field_position_local)
record_add_field(record, data['newTag'], data['ind1'],
data['ind2'] , subfields=subfields)
record_delete_field(record, data['oldTag'], ind1=data['oldInd1'],
ind2=data['oldInd2'], field_position_local=field_position_local)
response['resultCode'] = 32
elif request_type == 'moveSubfield':
record_move_subfield(record, data['tag'],
int(data['subfieldIndex']), int(data['newSubfieldIndex']),
field_position_local=field_position_local)
response['resultCode'] = 25
elif request_type == 'moveField':
if data['direction'] == 'up':
final_position_local = field_position_local-1
else: # direction is 'down'
final_position_local = field_position_local+1
record_move_fields(record, data['tag'], [field_position_local],
final_position_local)
response['resultCode'] = 32
elif request_type == 'deleteFields':
to_delete = data['toDelete']
deleted_fields = 0
deleted_subfields = 0
for tag in to_delete:
#Sorting the fields in a edcreasing order by the local position!
fieldsOrder = to_delete[tag].keys()
fieldsOrder.sort(lambda a, b: int(b) - int(a))
for field_position_local in fieldsOrder:
if not to_delete[tag][field_position_local]:
# No subfields specified - delete entire field.
record_delete_field(record, tag,
field_position_local=int(field_position_local))
deleted_fields += 1
else:
for subfield_position in \
to_delete[tag][field_position_local][::-1]:
# Delete subfields in reverse order (to keep the
# indexing correct).
record_delete_subfield_from(record, tag,
int(subfield_position),
field_position_local=int(field_position_local))
deleted_subfields += 1
if deleted_fields == 1 and deleted_subfields == 0:
response['resultCode'] = 26
elif deleted_fields and deleted_subfields == 0:
response['resultCode'] = 27
elif deleted_subfields == 1 and deleted_fields == 0:
response['resultCode'] = 28
elif deleted_subfields and deleted_fields == 0:
response['resultCode'] = 29
else:
response['resultCode'] = 30
response['cacheMTime'] = update_cache_contents(recid,
uid,
record_revision,
record,
pending_changes,
deactivated_hp_changes,
undo_list, redo_list)
response['cacheDirty'] = True
return response
def perform_request_autocomplete(request_type, recid, uid, data):
"""
Perfrom an AJAX request associated with the retrieval of autocomplete
data.
@param request_type: Type of the currently served request
@param recid: the identifer of the record
@param uid: The identifier of the user being currently logged in
@param data: The request data containing possibly important additional
arguments
"""
response = {}
# get the values based on which one needs to search
searchby = data['value']
# we check if the data is properly defined
fulltag = ''
if 'maintag' in data and 'subtag1' in data and \
'subtag2' in data and 'subfieldcode' in data:
maintag = data['maintag']
subtag1 = data['subtag1']
subtag2 = data['subtag2']
u_subtag1 = subtag1
u_subtag2 = subtag2
if (not subtag1) or (subtag1 == ' '):
u_subtag1 = '_'
if (not subtag2) or (subtag2 == ' '):
u_subtag2 = '_'
subfieldcode = data['subfieldcode']
fulltag = maintag+u_subtag1+u_subtag2+subfieldcode
if (request_type == 'autokeyword'):
# call the keyword-form-ontology function
if fulltag and searchby:
items = get_kbt_items_for_bibedit(cfg['CFG_BIBEDIT_KEYWORD_TAXONOMY'],
cfg['CFG_BIBEDIT_KEYWORD_RDFLABEL'],
searchby)
response['autokeyword'] = items
if (request_type == 'autosuggest'):
# call knowledge base function to put the suggestions in an array..
if fulltag and searchby and len(searchby) > 3:
# add trailing '*' wildcard for 'search_unit_in_bibxxx()' if not already present
suggest_values = get_kbd_values_for_bibedit(fulltag, "", searchby+"*")
# remove ..
new_suggest_vals = []
for sugg in suggest_values:
if sugg.startswith(searchby):
new_suggest_vals.append(sugg)
response['autosuggest'] = new_suggest_vals
if (request_type == 'autocomplete'):
# call the values function with the correct kb_name
if fulltag in cfg['CFG_BIBEDIT_AUTOCOMPLETE_TAGS_KBS']:
kbname = cfg['CFG_BIBEDIT_AUTOCOMPLETE_TAGS_KBS'][fulltag]
# check if the seachby field has semicolons. Take all
# the semicolon-separated items..
items = []
vals = []
if searchby:
if searchby.rfind(';'):
items = searchby.split(';')
else:
items = [searchby.strip()]
for item in items:
item = item.strip()
kbrvals = get_kbr_values(kbname, item, '', 'e') # we want an exact match
if kbrvals and kbrvals[0]: # add the found val into vals
vals.append(kbrvals[0])
#check that the values are not already contained in other
#instances of this field
record = get_cache_contents(recid, uid)[2]
xml_rec = wash_for_xml(print_rec(record))
record, status_code, dummy_errors = create_record(xml_rec)
existing_values = []
if (status_code != 0):
existing_values = record_get_field_values(record,
maintag,
subtag1,
subtag2,
subfieldcode)
#get the new values.. i.e. vals not in existing
new_vals = vals
for val in new_vals:
if val in existing_values:
new_vals.remove(val)
response['autocomplete'] = new_vals
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['autosuggestion_scanned']
return response
def perform_request_bibcatalog(request_type, uid, data):
"""Handle request to BibCatalog (RT).
"""
response = {}
if request_type == 'getTickets':
# Insert the tickets data in the response, if possible
if not CFG_BIBCATALOG_SYSTEM or not CFG_CAN_SEARCH_FOR_TICKET:
response['tickets'] = "<!--No ticket system configured-->"
elif uid:
bibcat_resp = BIBCATALOG_SYSTEM.check_system(uid)
if bibcat_resp == "":
tickets_found = BIBCATALOG_SYSTEM.ticket_search(uid,
status=['new', 'open'], recordid=data['recID'])
tickets = []
for t_id in tickets_found:
ticket_info = BIBCATALOG_SYSTEM.ticket_get_info(
uid, t_id, ['url_display', 'url_close', 'subject', 'text', 'queue', 'created'])
t_url = ticket_info['url_display']
t_close_url = ticket_info['url_close']
t_subject = ticket_info['subject']
t_text = ticket_info['text']
t_queue = ticket_info['queue']
date_string = ticket_info['created']
date_splitted = date_string.split(" ")
# convert date to readable format
try:
t_date = date_splitted[2] + ' ' + date_splitted[1] +\
" " + date_splitted[4] + " " +\
date_splitted[3].split(":")[0] + ":" +\
date_splitted[3].split(":")[1]
except IndexError:
t_date = date_string
ticket = {"id": str(t_id), "queue": t_queue, "date": t_date, "url": t_url,
"close_url": t_close_url, "subject": t_subject, "text": t_text}
tickets.append(ticket)
response['tickets'] = tickets
response['resultCode'] = 31
else:
# put something in the tickets container, for debug
response['tickets'] = "Error connecting to RT<!--" + bibcat_resp + "-->"
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_rt_connection']
# closeTicket usecase
elif request_type == 'closeTicket':
if not CFG_BIBCATALOG_SYSTEM or not CFG_CAN_SEARCH_FOR_TICKET:
response['ticket_closed_description'] = "<!--No ticket system configured-->"
response['ticket_closed_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_closed']
elif uid:
bibcat_resp = BIBCATALOG_SYSTEM.check_system(uid)
if bibcat_resp == "":
un, pw = get_bibcat_from_prefs(uid)
if un and pw:
BIBCATALOG_SYSTEM.ticket_steal(uid, data['ticketid'])
ticket_closed = BIBCATALOG_SYSTEM.ticket_set_attribute(uid, data['ticketid'], 'status', 'resolved')
if ticket_closed == 1:
response['ticket_closed_description'] = 'Ticket resolved'
response['ticket_closed_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['ticket_closed']
else:
response['ticket_closed_description'] = 'Ticket could not be resolved.Try again'
response['ticket_closed_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_closed']
else:
response['ticket_closed_description'] = 'RT user does not exist'
response['ticket_closed_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_closed']
else:
#put something in the tickets container, for debug
response['ticket_closed_description'] = "Error connecting to RT<!--" + bibcat_resp + "-->"
response['ticket_closed_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_rt_connection']
response['ticketid'] = data['ticketid']
elif request_type == 'openTicket':
if not CFG_BIBCATALOG_SYSTEM or not CFG_CAN_SEARCH_FOR_TICKET:
response['ticket_opened_description'] = "<!--No ticket system configured-->"
response['ticket_opened_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_opened']
elif uid:
bibcat_resp = BIBCATALOG_SYSTEM.check_system(uid)
if bibcat_resp == "":
un, pw = get_bibcat_from_prefs(uid)
if un and pw:
ticket_opened = BIBCATALOG_SYSTEM.ticket_set_attribute(uid, data['ticketid'], 'status', 'open')
if ticket_opened == 1:
response['ticket_opened_description'] = 'Ticket opened'
response['ticket_opened_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['ticket_opened']
else:
response['ticket_opened_description'] = 'Ticket could not be opened.Try again'
response['ticket_opened_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_opened']
else:
response['ticket_opened_description'] = 'RT user does not exist'
response['ticket_opened_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_ticket_opened']
else:
#put something in the tickets container, for debug
response['ticket_opened_description'] = "Error connecting to RT<!--" + bibcat_resp + "-->"
response['ticket_opened_code'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['error_rt_connection']
response['ticketid'] = data['ticketid']
elif request_type == 'createTicket':
if BIBCATALOG_SYSTEM is None:
response['ticket_created_description'] = "<!--No ticket system configured-->"
response['ticket_created_code'] = CFG_BIBEDIT_AJAX_RESULT_CODES_REV['error_ticket_created']
elif BIBCATALOG_SYSTEM and uid:
bibcat_resp = BIBCATALOG_SYSTEM.check_system(uid)
if bibcat_resp == "":
un, pw = get_bibcat_from_prefs(uid)
if un and pw:
ticket_created = BIBCATALOG_SYSTEM.ticket_submit(uid, data['subject'], data['recID'],
data['text'], data['queue'], data['priority'], data['owner'], data['requestor'])
if ticket_created:
response['ticket_created_description'] = ticket_created
response['ticket_created_code'] = CFG_BIBEDIT_AJAX_RESULT_CODES_REV['ticket_created']
else:
response['ticket_created_description'] = 'Ticket could not be created.Try again'
response['ticket_created_code'] = CFG_BIBEDIT_AJAX_RESULT_CODES_REV['error_ticket_created']
else:
response['ticket_created_description'] = 'RT user does not exist'
response['ticket_created_code'] = CFG_BIBEDIT_AJAX_RESULT_CODES_REV['error_ticket_created']
else:
#put something in the tickets container, for debug
response['ticket_created_description'] = "Error connecting to RT<!--" + bibcat_resp + "-->"
response['ticket_created_code'] = CFG_BIBEDIT_AJAX_RESULT_CODES_REV['error_rt_connection']
elif request_type == 'getNewTicketRTInfo':
# Insert the tickets data in the response, if possible
response = get_new_ticket_RT_info(uid, data['recID'])
return response
def _add_curated_references_to_record(recid, uid, bibrec):
"""
Adds references from the cache that have been curated (contain $$9CURATOR)
to the bibrecord object
@param recid: record id, used to retrieve cache
@param uid: id of the current user, used to retrieve cache
@param bibrec: bibrecord object to add references to
"""
dummy1, dummy2, record, dummy3, dummy4, dummy5, dummy6 = get_cache_contents(recid, uid)
for field_instance in record_get_field_instances(record, "999", "C", "5"):
for subfield_instance in field_instance[0]:
if subfield_instance[0] == '9' and subfield_instance[1] == 'CURATOR':
# Add reference field on top of references, removing first $$o
field_instance = ([subfield for subfield in field_instance[0]
if subfield[0] != 'o'], field_instance[1],
field_instance[2], field_instance[3],
field_instance[4])
record_add_fields(bibrec, '999', [field_instance],
field_position_local=0)
def _xml_to_textmarc_references(bibrec):
"""
Convert XML record to textmarc and return the lines related to references
@param bibrec: bibrecord object to be converted
@return: textmarc lines with references
@rtype: string
"""
sysno = ""
options = {"aleph-marc": 0, "correct-mode": 1, "append-mode": 0,
"delete-mode": 0, "insert-mode": 0, "replace-mode": 0,
"text-marc": 1}
# Using deepcopy as function create_marc_record() modifies the record passed
textmarc_references = [line.strip() for line
in xmlmarc2textmarc.create_marc_record(copy.deepcopy(bibrec),
sysno, options).split('\n')
if '999C5' in line]
return textmarc_references
def perform_request_ref_extract_url(recid, uid, url):
"""
Making use of the refextractor API, extract references from the url
received from the client
@param recid: opened record id
@param uid: active user id
@param url: URL to extract references from
@return response to be returned to the client code
"""
response = {}
try:
recordExtended = replace_references(recid, uid, url=url)
except FullTextNotAvailable:
response['ref_xmlrecord'] = False
response['ref_msg'] = "File not found. Server returned code 404"
return response
except:
response['ref_xmlrecord'] = False
response['ref_msg'] = """Error while fetching PDF. Bad URL or file could
not be retrieved """
return response
if not recordExtended:
response['ref_msg'] = """No references were found in the given PDF """
return response
ref_bibrecord = create_record(recordExtended)[0]
_add_curated_references_to_record(recid, uid, ref_bibrecord)
response['ref_bibrecord'] = ref_bibrecord
response['ref_xmlrecord'] = record_xml_output(ref_bibrecord)
textmarc_references = _xml_to_textmarc_references(ref_bibrecord)
response['ref_textmarc'] = '<div class="refextracted">' + '<br />'.join(textmarc_references) + "</div>"
return response
def perform_request_ref_extract(recid, uid, txt=None):
""" Handle request to extract references in the given record
@param recid: record id from which the references should be extracted
@type recid: str
@param txt: string containing references
@type txt: str
@param uid: user id
@type uid: int
@return: xml record with references extracted
@rtype: dictionary
"""
text_no_references_found_msg = """ No references extracted. The automatic
extraction did not recognize any reference in the
pasted text.<br /><br />If you want to add the references
manually, an easily recognizable format is:<br/><br/>
&nbsp;&nbsp;&nbsp;&nbsp;[1] Phys. Rev A71 (2005) 42<br />
&nbsp;&nbsp;&nbsp;&nbsp;[2] ATLAS-CMS-2007-333
"""
pdf_no_references_found_msg = """ No references were found in the attached
PDF.
"""
response = {}
response['ref_xmlrecord'] = False
recordExtended = None
try:
if txt:
recordExtended = replace_references(recid, uid,
txt=txt.decode('utf-8'))
if not recordExtended:
response['ref_msg'] = text_no_references_found_msg
else:
recordExtended = replace_references(recid, uid)
if not recordExtended:
response['ref_msg'] = pdf_no_references_found_msg
except FullTextNotAvailable:
response['ref_msg'] = """ The fulltext is not available.
"""
except:
response['ref_msg'] = """ An error ocurred while extracting references.
"""
if not recordExtended:
return response
ref_bibrecord = create_record(recordExtended)[0]
_add_curated_references_to_record(recid, uid, ref_bibrecord)
response['ref_bibrecord'] = ref_bibrecord
response['ref_xmlrecord'] = record_xml_output(ref_bibrecord)
textmarc_references = _xml_to_textmarc_references(ref_bibrecord)
response['ref_textmarc'] = '<div class="refextracted">' + '<br />'.join(textmarc_references) + "</div>"
return response
def perform_request_preview_record(request_type, recid, uid, data):
""" Handle request to preview record with formatting
"""
response = {}
if request_type == "preview":
if data["submitMode"] == "textmarc":
textmarc_record = data['textmarc']
xml_conversion_status = get_xml_from_textmarc(recid, textmarc_record, uid)
if xml_conversion_status['resultMsg'] == 'textmarc_parsing_error':
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['textmarc_parsing_error']
response.update(xml_conversion_status)
return response
record = create_record(xml_conversion_status["resultXML"])[0]
elif cache_exists(recid, uid):
dummy1, dummy2, record, dummy3, dummy4, dummy5, dummy6 = get_cache_contents(recid, uid)
else:
record = get_bibrecord(recid)
# clean the record from unfilled volatile fields
record_strip_empty_volatile_subfields(record)
record_strip_empty_fields(record)
response['html_preview'] = _get_formated_record(record, data['new_window'])
# clean the record from unfilled volatile fields
record_strip_empty_volatile_subfields(record)
record_strip_empty_fields(record)
response['html_preview'] = _get_formated_record(record, data['new_window'])
return response
def perform_request_get_pdf_url(recid):
""" Handle request to get the URL of the attached PDF
"""
response = {}
doc = get_pdf_doc(recid)
if doc:
response['pdf_url'] = doc.get_url()
else:
response['pdf_url'] = ""
return response
def perform_request_get_textmarc(recid, uid):
""" Get record content from cache, convert it to textmarc and return it
"""
textmarc_options = {"aleph-marc": 0, "correct-mode": 1, "append-mode": 0,
"delete-mode": 0, "insert-mode": 0, "replace-mode": 0,
"text-marc": 1}
bibrecord = get_cache_contents(recid, uid)[2]
record_strip_empty_fields(bibrecord)
record_strip_controlfields(bibrecord)
textmarc = xmlmarc2textmarc.create_marc_record(
copy.deepcopy(bibrecord), sysno="", options=textmarc_options)
return {'textmarc': textmarc}
def perform_request_get_tableview(recid, uid, data):
""" Convert textmarc inputed by user to marcxml and if there are no
parsing errors, create cache file
"""
response = {}
textmarc_record = data['textmarc']
if not textmarc_record:
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['tableview_change_success']
xml_conversion_status = get_xml_from_textmarc(recid, textmarc_record, uid)
response.update(xml_conversion_status)
if xml_conversion_status['resultMsg'] == 'textmarc_parsing_error':
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['textmarc_parsing_error']
else:
create_cache(recid, uid,
create_record(xml_conversion_status['resultXML'])[0], data['recordDirty'],
disabled_hp_changes=data['disabled_hp_changes'])
response['resultCode'] = cfg['CFG_BIBEDIT_AJAX_RESULT_CODES_REV']['tableview_change_success']
response['cacheMTime'] = get_cache_mtime(recid, uid)
return response
def _get_formated_record(record, new_window):
"""Returns a record in a given format
@param record: BibRecord object
@param new_window: Boolean, indicates if it is needed to add all the headers
to the page (used when clicking Preview button)
"""
from invenio.config import CFG_WEBSTYLE_TEMPLATE_SKIN
xml_record = wash_for_xml(record_xml_output(record))
result = ''
if new_window:
result = """ <html><head><title>Record preview</title>
<script type="text/javascript" src="%(site_url)s/js/jquery.min.js"></script>
<link rel="stylesheet" href="%(site_url)s/img/invenio%(cssskin)s.css" type="text/css"></head>
""" % {'site_url': CFG_SITE_URL,
'cssskin': CFG_WEBSTYLE_TEMPLATE_SKIN != 'default' and '_' + CFG_WEBSTYLE_TEMPLATE_SKIN or ''
}
result += get_mathjax_header(True) + '<body>'
result += "<h2> Brief format preview </h2><br />"
result += bibformat.format_record(0,
of="hb",
xml_record=xml_record) + "<br />"
result += "<br /><h2> Detailed format preview </h2><br />"
result += bibformat.format_record(0,
of="hd",
xml_record=xml_record)
#Preview references
result += "<br /><h2> References </h2><br />"
result += bibformat.format_record(0,
'hdref',
xml_record=xml_record)
result += """<script>
$('#referenceinp_link').hide();
$('#referenceinp_link_span').hide();
</script>
"""
if new_window:
result += "</body></html>"
return result
########### Functions related to templates web interface #############
def perform_request_init_template_interface():
"""Handle a request to manage templates"""
errors = []
warnings = []
body = ''
# Add script data.
record_templates = get_record_templates()
record_templates.sort()
data = {'gRECORD_TEMPLATES': record_templates,
'gSITE_RECORD': '"' + CFG_SITE_RECORD + '"',
'gSITE_URL': '"' + CFG_SITE_URL + '"'}
body += '<script type="text/javascript">\n'
for key in data:
body += ' var %s = %s;\n' % (key, data[key])
body += ' </script>\n'
# Add scripts (the ordering is NOT irrelevant).
scripts = [
'vendors/json2/json2.js'
]
bibedit_scripts = ['display.js', 'template_interface.js']
for script in scripts:
body += ' <script type="text/javascript" src="%s">' \
'</script>\n' % (url_for('static', filename=script), )
for script in bibedit_scripts:
body += ' <script type="text/javascript" src="%s">' \
'</script>\n' % (url_for('editor.static',
filename='js/editor/' + script), )
body += ' <div id="bibEditTemplateList"></div>\n'
body += ' <div id="bibEditTemplateEdit"></div>\n'
return body, errors, warnings
def perform_request_ajax_template_interface(data):
"""Handle Ajax requests by redirecting to appropriate function."""
response = {}
request_type = data['requestType']
if request_type == 'editTemplate':
# Edit a template request.
response.update(perform_request_edit_template(data))
return response
def perform_request_edit_template(data):
""" Handle request to edit a template """
response = {}
template_filename = data['templateFilename']
template = get_record_template(template_filename)
if not template:
response['resultCode'] = 1
else:
response['templateMARCXML'] = template
return response
def perform_doi_search(doi):
"""Search for DOI on the dx.doi.org page
@return: the url returned by this page"""
response = {}
url = "http://dx.doi.org/"
val = {'hdl': doi}
url_data = urllib.urlencode(val)
cj = cookielib.CookieJar()
header = [('User-Agent', cfg['CFG_DOI_USER_AGENT'])]
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
opener.addheaders = header
try:
resp = opener.open(url, url_data)
except:
return response
else:
response['doi_url'] = resp.geturl()
return response
def check_hide_authors(record):
""" Check if authors should be hidden by default in the user interface """
return sum([len(record.get(tag, [])) for tag in cfg['CFG_BIBEDIT_DISPLAY_AUTHOR_TAGS']]) > cfg['CFG_BIBEDIT_AUTHOR_DISPLAY_THRESHOLD']
def perform_guess_affiliations(uid, data):
response = {}
recid = data["recID"]
record_revision, record, pending_changes, deactivated_hp_changes, \
undo_list, redo_list = get_cache_contents(recid, uid)[1:]
# Let's guess affiliations
result = {}
for tag in cfg['CFG_BIBEDIT_DISPLAY_AUTHOR_TAGS']:
result[tag] = {}
author_field_instances = record_get_field_instances(record, tag)
for field_pos, instance in enumerate(author_field_instances):
subfields_to_add = []
current_affilations = field_get_subfield_values(instance, code="u")
if not current_affilations or current_affilations[0].startswith("VOLATILE:"):
# This author does not have affiliation
try:
author_name = field_get_subfield_values(instance, code="a")[0]
except IndexError:
author_name = author_name[0]
aff_guess = get_affiliation_for_paper(recid, author_name)
if aff_guess:
for aff in aff_guess:
field_add_subfield(instance, code="u", value=aff)
subfields_to_add.append(["u", aff])
if subfields_to_add:
result[tag][field_pos] = subfields_to_add
response['cacheMTime'] = update_cache_contents(recid, uid, record_revision,
record, pending_changes,
deactivated_hp_changes,
undo_list, redo_list)
response['subfieldsToAdd'] = result
return response
def perform_request_submit(recid, uid, data, response):
if not cache_exists(recid, uid):
response['resultCode'] = 106
elif not get_cache_mtime(recid, uid) == data['cacheMTime']:
response['resultCode'] = 107
elif cache_expired(recid, uid) and \
record_locked_by_other_user(recid, uid):
response['resultCode'] = 104
elif record_locked_by_queue(recid):
response['resultCode'] = 105
else:
dummy_cache_dirty, record_revision, record, dummy_pending_changes, \
dummy_disabled_hp_changes, dummy_undo_list, dummy_redo_list \
= get_cache_contents(recid, uid)
xml_record = wash_for_xml(print_rec(record))
record, status_code, list_of_errors = create_record(xml_record)
# Simulate upload to catch errors
errors_upload = perform_upload_check(xml_record, '--replace')
if errors_upload:
response['resultCode'], response['errors'] = 113, \
errors_upload
return response
elif status_code == 0:
response['resultCode'], response['errors'] = 110, \
list_of_errors
if not data['force'] and not latest_record_revision(recid, record_revision):
response['cacheOutdated'] = True
else:
if record_is_conference(record):
new_cnum = add_record_cnum(recid, uid)
if new_cnum:
response["new_cnum"] = new_cnum
save_xml_record(recid, uid)
response['resultCode'] = 4

Event Timeline