diff --git a/.gitignore b/.gitignore index 272123ac3..8075cdf34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,110 +1,56 @@ +*.clisp.mem +*.cmucl.core +*.fas +*.fasl +*.kdevelop +*.kdevses +*.lib +*.pyc +*.sbcl.core +*.sse2f +*.x86f +*~ +.coverage +.noseids +.project +.pydevproject +.settings .version Invenio.egg-info Makefile Makefile.in +TAGS +aclocal.m4 +autom4te.cache +bower_components/ build compile -configure +compile config.cache +config.guess config.log -config.status config.nice -config.guess +config.status +config.status.lineno config.sub +configure +configure.lineno +dist docs/_build docs/db docs/static -dist install-sh -missing -compile -autom4te.cache -aclocal.m4 -TAGS +instance/ invenio-autotools.conf +invenio/base/translations/*/LC_MESSAGES/messages.mo +missing +node_modules/ +org.eclipse.core.resources.prefs +po/*.gmo +po/*.mo +po/*.sed po/POTFILES po/POTFILES-py po/POTFILES-webdoc po/stamp-po -po/*.gmo -po/*.mo -po/*.sed -*~ -*.pyc -*.clisp.mem -*.cmucl.core -*.sbcl.core -*.fas -*.fasl -*.sse2f -*.lib -*.x86f -invenio/base/translations/*/LC_MESSAGES/messages.mo -modules/webauthorprofile/bin/webauthorprofile -modules/bibauthorid/bin/bibauthorid -modules/bibcirculation/bin/bibcircd -modules/bibclassify/bin/bibclassify -modules/bibconvert/bin/bibconvert -modules/bibdocfile/bin/bibdocfile -modules/bibedit/bin/bibedit -modules/bibrecord/bin/textmarc2xmlmarc -modules/bibrecord/bin/xmlmarc2textmarc -modules/bibrecord/bin/xmlmarclint -modules/docextract/bin/refextract -modules/docextract/bin/docextract -modules/bibencode/bin/bibencode -modules/bibexport/bin/bibexport -modules/bibformat/bin/bibreformat -modules/oaiharvest/bin/oaiharvest -modules/oairepository/bin/oairepositoryupdater -modules/bibindex/bin/bibindex -modules/bibindex/bin/bibstat -modules/bibmatch/bin/bibmatch -modules/bibrank/bin/bibrank -modules/bibrank/bin/bibrankgkb -modules/bibrank/etc/bibrankgkb.cfg -modules/bibrank/etc/demo_jif.cfg -modules/bibrank/etc/template_single_tag_rank_method.cfg -modules/bibsched/bin/bibsched -modules/bibsched/bin/bibtaskex -modules/bibsched/bin/bibtasklet -modules/bibsort/bin/bibsort -modules/bibsword/bin/bibsword -modules/bibupload/bin/batchuploader -modules/bibupload/bin/bibupload -modules/elmsubmit/bin/elmsubmit -modules/elmsubmit/etc/elmsubmit.cfg -modules/miscutil/bin/dbdump -modules/miscutil/bin/dbexec -modules/miscutil/bin/inveniocfg -modules/miscutil/bin/plotextractor -modules/miscutil/etc/bash_completion.d/inveniocfg -modules/miscutil/lib/build -modules/webaccess/bin/authaction -modules/webaccess/bin/webaccessadmin -modules/webalert/bin/alertengine -modules/webmessage/bin/webmessageadmin -modules/websearch/bin/webcoll -modules/websession/bin/inveniogc -modules/webstat/bin/webstat -modules/webstat/bin/webstatadmin -modules/webstyle/bin/gotoadmin -modules/webstyle/bin/webdoc -modules/websubmit/bin/bibdocfile -modules/websubmit/bin/inveniounoconv -modules/websubmit/bin/websubmitadmin -modules/bibcirculation/bin/bibcircd tags -config.status.lineno -configure.lineno -*.kdevelop -*.kdevses -.project -.noseids -.settings -.pydevproject -org.eclipse.core.resources.prefs -modules/miscutil/bin/inveniomanage -bower_components/ -node_modules/ -instance/ diff --git a/docs/api.rst b/docs/api.rst index 0e0021db4..974ffa8e5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,41 +1,32 @@ .. _api: API === Base ---- .. automodule:: invenio.base.factory :members: -Core ----- - -.. automodule:: invenio.core.record - :members: - -.. automodule:: invenio.core.record.storage - :members: - Extensions ---------- .. automodule:: invenio.ext.sqlalchemy :members: Modules ------- .. automodule:: invenio.modules.tags :members: .. automodule:: invenio.modules.tags.views :members: Legacy ------ .. automodule:: invenio.legacy.bibrecord :members: diff --git a/invenio/base/factory.py b/invenio/base/factory.py index 040a07b4a..a1d22a653 100644 --- a/invenio/base/factory.py +++ b/invenio/base/factory.py @@ -1,182 +1,174 @@ # -*- coding: utf-8 -*- ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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. """ invenio.base.factory -------------------- Implements application factory. """ import warnings import sys import os #from invenio.ext.logging import register_exception from .helpers import with_app_context, unicodifier from .wrappers import Flask from invenio.ext.registry import Registry, ExtensionRegistry, \ PackageRegistry, ConfigurationRegistry, AutoDiscoverRegistry, \ ImportPathRegistry, BlueprintAutoDiscoveryRegistry from flask import Blueprint __all__ = ['create_app', 'with_app_context'] def cleanup_legacy_configuration(app): """ Cleanup legacy issue in configuration """ def language_list_long(): return [] ## ... and map certain common parameters app.config['CFG_LANGUAGE_LIST_LONG'] = [ (lang, longname.decode('utf-8')) for (lang, longname) in language_list_long() ] ## Invenio is all using str objects. Let's change them to unicode app.config.update(unicodifier(dict(app.config))) def register_legacy_blueprints(app): """ Register some legacy blueprints """ @app.route('/testing') def testing(): from flask import render_template return render_template('404.html') def register_secret_key(app): SECRET_KEY = app.config.get('SECRET_KEY') or \ app.config.get('CFG_SITE_SECRET_KEY', 'change_me') if not SECRET_KEY or SECRET_KEY == 'change_me': fill_secret_key = """ Set variable SECRET_KEY with random string in invenio.cfg. You can use following commands: $ %s """ % ('inveniomanage config create secret-key', ) warnings.warn(fill_secret_key, UserWarning) app.config["SECRET_KEY"] = SECRET_KEY def create_app(instance_path=None, **kwargs_config): """ Prepare WSGI Invenio application based on Flask. Invenio consists of a new Flask application with legacy support for the old WSGI legacy application and the old Python legacy scripts (URLs to *.py files). """ # Flask application name app_name = '.'.join(__name__.split('.')[0:2]) # Force instance folder to always be located in under system prefix instance_path = instance_path or os.path.join( sys.prefix, 'var', app_name + '-instance' ) # Create instance path try: if not os.path.exists(instance_path): os.makedirs(instance_path) except Exception: pass # Create the Flask application instance app = Flask( app_name, ## Static files are usually handled directly by the webserver (e.g. ## Apache) However in case WSGI is required to handle static files too ## (such as when running simple server), then this flag can be ## turned on (it is done automatically by wsgi_handler_test). ## We assume anything under '/' which is static to be server directly ## by the webserver from CFG_WEBDIR. In order to generate independent ## url for static files use func:`url_for('static', filename='test')`. static_url_path='', template_folder='templates', instance_relative_config=True, instance_path=instance_path, ) # Handle both URLs with and without trailing slashes by Flask. # @blueprint.route('/test') # @blueprint.route('/test/') -> not necessary when strict_slashes == False app.url_map.strict_slashes = False # # Configuration loading # # Load default configuration app.config.from_object('invenio.base.config') # Load invenio.cfg from instance folder app.config.from_pyfile('invenio.cfg', silent=True) ## Update application config from parameters. app.config.update(kwargs_config) # Ensure SECRET_KEY has a value in the application configuration register_secret_key(app) # ==================== # Application assembly # ==================== # Initialize application registry, used for discovery and loading of # configuration, extensions and Invenio packages Registry(app=app) - # Register core packages listed in invenio.cfg - # - invenio.core.* to be moved to invenio.modules.* and this line removed - app.extensions['registry']['core_packages'] = ImportPathRegistry( - initial=['invenio.core.*'] - ) # Register packages listed in invenio.cfg app.extensions['registry']['packages'] = PackageRegistry(app) # Register extensions listed in invenio.cfg app.extensions['registry']['extensions'] = ExtensionRegistry(app) # Extend application config with configuration from packages (app config # takes precedence) - ConfigurationRegistry( - app, registry_namespace='core_packages' - ) ConfigurationRegistry(app) # Leagcy conf cleanup cleanup_legacy_configuration(app) # ====================== # Blueprint registration # ====================== app.extensions['registry']['blueprints'] = BlueprintAutoDiscoveryRegistry( app=app ) register_legacy_blueprints(app) return app diff --git a/invenio/base/scripts/cache.py b/invenio/base/scripts/cache.py index 55c15b1f0..16231f05c 100644 --- a/invenio/base/scripts/cache.py +++ b/invenio/base/scripts/cache.py @@ -1,102 +1,102 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. from flask import current_app from invenio.ext.script import Manager, change_command_name manager = Manager(usage="Perform cache operations") def reset_rec_cache(output_format, get_record, split_by=1000): """It either stores or does not store the output_format. If CFG_BIBUPLOAD_SERIALIZE_RECORD_STRUCTURE is changed, this function will adapt the database to either store or not store the output_format.""" import sys try: import cPickle as pickle except: import pickle from itertools import islice from intbitset import intbitset from invenio.legacy.bibsched.cli import server_pid, pidfile from invenio.ext.sqlalchemy import db - from invenio.modules.record_editor.models import Bibrec + from invenio.modules.records.models import Record as Bibrec from invenio.modules.formatter.models import Bibfmt pid = server_pid(ping_the_process=False) if pid: print >> sys.stderr, "ERROR: bibsched seems to run with pid %d, according to %s." % (pid, pidfile) print >> sys.stderr, " Please stop bibsched before running this procedure." sys.exit(1) if current_app.config.get('CFG_BIBUPLOAD_SERIALIZE_RECORD_STRUCTURE'): print ">>> Searching records which need %s cache resetting; this may take a while..." % (output_format, ) all_recids = intbitset(db.session.query(Bibrec.id).all()) #TODO: prevent doing all records? recids = all_recids print ">>> Generating %s cache..." % (output_format, ) tot = len(recids) count = 0 it = iter(recids) while True: rec_group = tuple(islice(it, split_by)) if not len(rec_group): break Bibfmt.query.filter(db.and_( Bibfmt.id_bibrec.in_(rec_group), Bibfmt.format == output_format)).delete(synchronize_session=False) db.session.commit() #TODO: Update the cache or wait for the first access map(get_record, rec_group) count += len(rec_group) print " ... done records %s/%s" % (count, tot) if len(rec_group) < split_by or count >= tot: break print ">>> %s cache generated successfully." % (output_format, ) else: print ">>> Cleaning %s cache..." % (output_format, ) Bibfmt.query.filter(Bibfmt.format == output_format).delete(synchronize_session=False) db.session.commit() @manager.command @change_command_name def reset_recjson(split_by=1000): - """Reset record json structure cache.""" - from invenio.legacy.bibfield.bibfield_manager import reset - reset(split_by) + """Reset record json structure cache lazily""" + from invenio.modules.records.models import RecordMetadata + RecordMetadata.query.delete() @manager.command @change_command_name def reset_recstruct(split_by=1000): """Reset record structure cache.""" from invenio.legacy.bibrecord.bibrecord_manager import reset reset(split_by) def main(): from invenio.base.factory import create_app app = create_app() manager.app = app manager.run() if __name__ == '__main__': main() diff --git a/invenio/base/utils.py b/invenio/base/utils.py index cdf9383d5..887e1d2dc 100644 --- a/invenio/base/utils.py +++ b/invenio/base/utils.py @@ -1,157 +1,200 @@ # -*- coding: utf-8 -*- ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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. """ invenio.base.utils ------------------ Implements various utils. """ import os import pkg_resources import re from flask import has_app_context, current_app from werkzeug.utils import import_string, find_modules from functools import partial from itertools import chain def register_extensions(app): for ext_name in app.config.get('EXTENSIONS', []): ext = import_string(ext_name) ext = getattr(ext, 'setup_app', ext) #try: #except: # continue # try: ext(app) #except Exception as e: # app.logger.error('%s: %s' % (ext_name, str(e))) return app def import_module_from_packages(name, app=None, packages=None, silent=False): if packages is None: if app is None and has_app_context(): app = current_app if app is None: raise Exception('Working outside application context or provide app') #FIXME - packages = ['invenio.core.*'] + app.config.get('PACKAGES', []) + packages = app.config.get('PACKAGES', []) for package in packages: if package.endswith('.*'): for module in find_modules(package[:-2], include_packages=True): try: yield import_string(module + '.' + name, silent) except ImportError: pass except Exception as e: import traceback traceback.print_exc() app.logger.error('Could not import: "%s.%s: %s', module, name, str(e)) pass continue try: yield import_string(package + '.' + name, silent) except ImportError: pass except Exception as e: import traceback traceback.print_exc() app.logger.error('Could not import: "%s.%s: %s', package, name, str(e)) pass def import_submodules_from_packages(name, app=None, packages=None, silent=False): discover = partial(import_module_from_packages, name) out = [] for p in discover(app=app, packages=packages, silent=silent): if p is not None: for m in find_modules(p.__name__): try: out.append(import_string(m, silent)) except Exception as e: if not silent: raise e return out collect_blueprints = partial(import_module_from_packages, 'views') autodiscover_admin_views = partial(import_module_from_packages, 'admin') autodiscover_user_settings = partial(import_module_from_packages, 'user_settings') autodiscover_configs = partial(import_module_from_packages, 'config') autodiscover_facets = partial(import_submodules_from_packages, 'facets') autodiscover_managers = partial(import_module_from_packages, 'manage') autodiscover_workflows = partial(import_module_from_packages, 'workflows') autodiscover_redirect_methods = partial(import_submodules_from_packages, 'redirect_methods') autodiscover_celery_tasks = partial(import_module_from_packages, 'tasks') autodiscover_template_context_functions = partial( import_submodules_from_packages, 'template_context_functions') autodiscover_format_elements = partial( import_submodules_from_packages, 'format_elements') autodiscover_widgets = partial(import_module_from_packages, 'widgets') def autodiscover_non_python_files(file_name, package_name, app=None, packages=None): """ Helper function to autodiscover non python files that are included inside the package. (see MANIFEST.in) :param file_name: it could be a string or a regex to search for the files inside the package :type file_name: string or string regex :param package_name: :param app: :param packages: :return: List of full paths of non python files discovered """ return [os.path.join(os.path.dirname(m.__file__), f) for m in import_module_from_packages(package_name, app, packages) for f in pkg_resources.resource_listdir(m.__name__, '') if re.match(file_name, f)] def register_configurations(app): """Includes the configuration parameters of the config file. E.g. If the blueprint specify the config string `invenio.messages.config` any uppercase variable defined in the module `invenio.messages.config` is loaded into the system. """ from flask import Config new_config = Config(app.config.root_path) - for config in import_module_from_packages('config', app, ['invenio.core.*', ]): - new_config.from_object(config) for config in autodiscover_configs(app): new_config.from_object(config) new_config.update(app.config) app.config = new_config + + +def try_to_eval(string, context={}, **general_context): + """ + This method takes care of evaluating the python expression, and, if an + exception happens, it tries to import the needed module. + + @param string: String to evaluate + @param context: Context needed, in some cases, to evaluate the string + + @return: The value of the expression inside string + """ + if not string: + return None + + res = None + imports = [] + general_context.update(context) + + while (True): + try: + res = eval(string, globals().update(general_context), locals()) # kwalitee: disable=eval + except NameError, err: + #Try first to import using werkzeug import_string + try: + from werkzeug.utils import import_string + part = string.split('.')[0] + import_string(part) + for i in string.split('.')[1:]: + part += '.' + i + import_string(part) + continue + except: + pass + + import_name = str(err).split("'")[1] + if not import_name in imports: + if import_name in context: + globals()[import_name] = context[import_name] + else: + globals()[import_name] = __import__(import_name) + imports.append(import_name) + continue + raise ImportError("Can't import the needed module to evaluate %s" (string, )) + return res diff --git a/invenio/core/record/backends/mongodb_pymongo.py b/invenio/core/record/backends/mongodb_pymongo.py deleted file mode 100644 index d487b2657..000000000 --- a/invenio/core/record/backends/mongodb_pymongo.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- - -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" - invenio.core.record.backends.mongo_pymongo - ------------------------------------------ - - Configuration: - RECORD_BACKEND_MONGO_HOST = localhost - RECORD_BACKEND_MONGO_PORT = 27017 - RECORD_BACKEND_MONGO_DB_NAME = invenio - -""" - -import pymongo - -from flask import current_app - - -class Storage(object): - """ - Record storage interface - """ - #FIXME: manage errors and return values - - def __init__(self, *args, **kwards): - host = current_app.config.get('RECORD_BACKEND_MONGO_HOST', 'localhost') - port = current_app.config.get('RECORD_BACKEND_MONGO_PORT', 27017) - database = current_app.config.get('RECORD_BACKEND_MONGO_DB_NAME', 'invenio') - self.__connection = pymongo.MongoClient(host=host,port=port) - self.__database = self.__conection[database] - self.__collection = self.__database['record_metadata'] - #Create index on recid - self.__collection.create_index("recid") - - def save_one(self, json, recid=None): - """Stores one json record in the storage system""" - if not recid is None: - json['recid'] = recid - json['_id'] = recid - - self.__collection.insert(json) - - def save_many(self, jsons, recids=None): - """ - Stores many json records in the storage system, as elements on the - iterable jsons - """ - if not recids is None: - def add_id(t): - t[0]['recid'] = t[1] - t[0]['_id'] = t[1] - return t[0] - jsons = impa(add_id, izip(jsons, recids)) - - self.__collection.insert(jsons, continue_on_error=True) - - def update_one(self, json, recid=None): - """ - Updates one json record, if recid is None field recid is expected inside - the json object - """ - #FIXME: what if we get only the fields that have change - if not recid is None: - json['recid'] = recid - json['_id'] = recid - - self.__collection.save(json) - - def update_many(self, jsons, recids=None): - """Update many json records following the same rule as update_one""" - #FIXME: what if we get only the fields that have change - if not recids is None: - def add_id(t): - t[0]['recid'] = t[1] - t[0]['_id'] = t[1] - return t[0] - jsons = impa(add_id, izip(jsons, recids)) - - map(self.__collection.save, jsons) - - def get_one(self, recid): - """Returns the json record matching the recid""" - return self.__collection.find_one(recid) - - def get_many(self, recids): - """Returns an iterable of json records which recid is inside recids""" - return self.__collection.find({'_id': {'$in':recids}}) - - def get_field_values(recids, field, repetitive_values=True, count=False, - include_recid=False, split_by=0): - """ - Returns a list of field values for field for the given record ids. - - :param recids: list (or iterable) of integers - :param repetitive_values: if set to True, returns all values even if - they are doubled. If set to False, then return unique values only. - :param count: in combination with repetitive_values=False, adds to the - result the number of occurrences of the field. - :param split: specifies the size of the output. - """ - raise NotImplementedError() - - def get_fields_values(recids, fields, repetitive_values=True, count=False, - include_recid=False, split_by=0): - """ - As in :meth:`get_field_values` but in this case returns a dictionary with each - of the fields and the list of field values. - """ - raise NotImplementedError() - - - diff --git a/invenio/core/record/backends/sqlalchemy.py b/invenio/core/record/backends/sqlalchemy.py deleted file mode 100644 index da9b0f08d..000000000 --- a/invenio/core/record/backends/sqlalchemy.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" - invenio.core.record.backends.sqlalchemy - --------------------------------------- - - - RECORD_BACKEND_SQLALCHEMY = 'invenio.ext.sqlalchemy:db' - -""" -from itertools import izip, imap - -from flask import current_app -from flask.helpers import locked_cached_property -from werkzeug import import_string - -from ..storage import RecordStorage -from ..model import Record -from invenio.ext.sqlalchemy import db - - -class RecordMetadata(db.Model): - """Represents a json record inside the SQL database""" - - __tablename__ = 'record_metadata' - id = db.Column(db.MediumInteger(8, unsigned=True), - db.ForeignKey(Record.id), - primary_key=True, - nullable=False, - autoincrement=True) - json = db.Column(db.PickleBinary, - nullable=False) - - record = db.relationship(Record, backref='record_metadata') - - -class Storage(RecordStorage): - """ - Implements database backend for SQLAlchemy model storage. - """ - #FIXME: This storage engine should use transactions! - #FIXME: manage errors and return values - - def __init__(self, *args, **kwards): - if not self.db.engine.dialect.has_table(self.db.engine, - self.model.__tablename__): - self.model.__table__.create(bind=self.db.engine) - self.db.session.commit() - - @locked_cached_property - def db(self): - """Returns SQLAlchemy database object.""" - return import_string(current_app.config.get( - 'RECORD_BACKEND_SQLALCHEMY', 'invenio.ext.sqlalchemy:db')) - - @locked_cached_property - def model(self): - """Returns SQLAlchemy model.""" - return RecordMetadata - - def save_one(self, json_record, recid=None): - if recid is None: - recid = json_record['recid'] - - new_record_metadata = RecordMetadata(id=recid, json=json_record) - self.db.session.add(new_record_metadata) - self.db.session.commit() - - def save_many(self, json_records, recids=None): - if recids is None: - recids = imap(lambda j: j['recid'], json_records) - self.db.session.add_all([RecordMetadata(id=recid, json=json_record) - for recid, json_record in izip(recids, json_records)]) - self.db.session.commit() - - def update_one(self, json, recid=None): - #FIXME: what if we get only the fields that have change - if recid is None: - recid = json_record['recid'] - - new_record_metadata = RecordMetadata(id=recid, json=json_record) - self.db.session.merge(new_record_metadata) - self.db.session.commit() - - def update_many(self, jsons, recids=None): - #FIXME: what if we get only the fields that have change - if recids is None: - recids = imap(lambda j: j['recid'], json_records) - - for recid, json_record in izip(recids, json_records): - new_record_metadata = RecordMetadata(id=recid, json=json_record) - self.db.session.merge(new_record_metadata) - self.db.session.commit() - - def get_one(self, recid): - return self.db.session.query(RecordMetadata.json)\ - .filter_by(id=recid).one().json - - def get_many(self, recids): - for json in self.db.session.query(RecordMetadata.json)\ - .filter(RecordMetadata.id.in_(recids))\ - .all(): - yield json[0] - - def get_field_values(recids, field, repetitive_values=True, count=False, - include_recid=False, split_by=0): - raise NotImplementedError() - - def get_fields_values(recids, fields, repetitive_values=True, count=False, - include_recid=False, split_by=0): - raise NotImplementedError() diff --git a/invenio/core/record/config.py b/invenio/core/record/config.py deleted file mode 100644 index c8e5a3622..000000000 --- a/invenio/core/record/config.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" - invenio.core.record.config - -------------------------- - - Record confguration variables -""" - -# -# Allowed master formats in the installation -# -CFG_RECORD_MASTER_FORMATS= ['marc',] diff --git a/invenio/core/record/config_engine.py b/invenio/core/record/config_engine.py deleted file mode 100644 index a9901f36e..000000000 --- a/invenio/core/record/config_engine.py +++ /dev/null @@ -1,710 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" - invenio.core.record.config_engine - --------------------------------- - - Record fields and models configuration loader. - - This module uses `pyparsing ` to read from the - different configuration files the field and model definitions. -""" - -import os -import re - -from invenio.base.globals import cfg -from invenio.base.utils import autodiscover_non_python_files - -from pyparsing import ParseException, FollowedBy, Suppress, OneOrMore, Literal, \ - LineEnd, ZeroOrMore, Optional, Forward, Word, QuotedString, alphas, nums, \ - alphanums, originalTextFor, oneOf, nestedExpr, quotedString, removeQuotes, \ - lineEnd, empty, col, restOfLine, delimitedList, indentedBlock - -class FieldParserException(Exception): - """Exception raised when some error happens parsing field definitions""" - pass - - -class ModelParserException(Exception): - """Exception raised when some error happens parsing model definitions""" - pass - - -def _create_record_field_parser(): - """ - Creates a parser that can handle field definitions. - - BFN like grammar:: - - line ::= python_doc_string | python_comment | include | field_def - - include ::= "include(" PATH ")" - - field_def ::= [persitent_identifier] [inherit_from] [override] json_id ["[0]" | "[n]"] "," aliases":" INDENT field_def_body UNDENT - field_def_body ::= (creator | derived | calculated) [checker] [producer] [documentation] - aliases ::= json_id ["[0]" | "[n]"] ["," aliases] - json_id ::= (alphas + '_') (alphanums + '_') - - creator ::= "creator:" INDENT creator_body+ UNDENT - creator_body ::= [decorators] source_format "," source_tags "," python_allowed_expr - source_format ::= MASTER_FORMATS - source_tag ::= QUOTED_STRING #This quoted string can contain a space separated list - - derived ::= "derived" INDENT derived_calculated_body UNDENT - calculated ::= "calculated:" INDENT derived_calculated_body UNDENT - derived_calculated_body ::= [decorators] "," python_allowed_exp - - decorators ::= (legacy | do_not_cache | parse_first | depends_on | only_if | only_if_master_value)* - legacy ::= "@legacy(" correspondences+ ")" - correspondences ::= "(" source_tag [ "," tag_name ] "," json_id ")" - parse_first ::= "@parse_first(" json_id+ ")" - depends_on ::= "@depends_on(" json_id+ ")" - only_if ::= "@only_if(" python_condition+ ")" - only_if_master_value ::= "@only_if_master_value(" python_condition+ ")" - - persistent_identifier ::= @persistent_identifier( level ) - inherit_from ::= "@inherit_from(" json_id+ ")" - override ::= "@override" - extend ::= "@extend" - do_not_cache ::= "@do_not_cache" - - checker ::= "checker:" INDENT checker_function+ UNDENT - - documentation ::= INDENT doc_string UNDENT - doc_string ::= QUOTED_STRING #reStructuredText string - - producer ::= "producer:" INDENT producer_body UNDENT - producer_body ::= producer_code "," python_dictionary - producer_code ::= ident - - python_allowed_exp ::= ident | list_def | dict_def | list_access | dict_access | function_call | one_line_expr - """ - indent_stack = [1] - - def check_sub_indent(str, location, tokens): - cur_col = col(location, str) - if cur_col > indent_stack[-1]: - indent_stack.append(cur_col) - else: - raise ParseException(str, location, "not a subentry") - - def check_unindent(str, location, tokens): - if location >= len(str): - return - cur_col = col(location, str) - if not(cur_col < indent_stack[-1] and cur_col <= indent_stack[-2]): - raise ParseException(str, location, "not an unindent") - - def do_unindent(): - indent_stack.pop() - - INDENT = lineEnd.suppress() + empty + empty.copy().setParseAction(check_sub_indent) - UNDENT = FollowedBy(empty).setParseAction(check_unindent) - UNDENT.setParseAction(do_unindent) - - json_id = (Word(alphas + "_", alphanums + "_") + Optional(oneOf("[0] [n]")))\ - .setResultsName("json_id", listAllMatches=True)\ - .setParseAction(lambda tokens: "".join(tokens)) - aliases = delimitedList((Word(alphanums + "_") + Optional(oneOf("[0] [n]"))) - .setParseAction(lambda tokens: "".join(tokens)))\ - .setResultsName("aliases") - python_allowed_expr = Forward() - ident = Word(alphas + "_", alphanums + "_") - dict_def = originalTextFor(nestedExpr('{', '}')) - list_def = originalTextFor(nestedExpr('[', ']')) - dict_access = list_access = originalTextFor(ident + nestedExpr('[', ']')) - function_call = originalTextFor(ZeroOrMore(ident + ".") + ident + nestedExpr('(', ')')) - - python_allowed_expr << (ident ^ dict_def ^ list_def ^ dict_access ^ list_access ^ function_call ^ restOfLine)\ - .setResultsName("value", listAllMatches=True) - - persistent_identifier = (Suppress("@persistent_identifier") + nestedExpr("(", ")"))\ - .setResultsName("persistent_identifier") - legacy = (Suppress("@legacy") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("legacy", listAllMatches=True) - only_if = (Suppress("@only_if") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("only_if") - only_if_master_value = (Suppress("@only_if_value") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("only_if_master_value") - depends_on = (Suppress("@depends_on") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("depends_on") - parse_first = (Suppress("@parse_first") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("parse_first") - do_not_cache = (Suppress("@") + "do_not_cache")\ - .setResultsName("do_not_cache") - field_decorator = parse_first ^ depends_on ^ only_if ^ only_if_master_value ^ do_not_cache ^ legacy - - #Independent decorators - inherit_from = (Suppress("@inherit_from") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("inherit_from") - override = (Suppress("@") + "override")\ - .setResultsName("override") - extend = (Suppress("@") + "extend")\ - .setResultsName("extend") - - master_format = (Suppress("@master_format") + originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("master_format") - - derived_calculated_body = ZeroOrMore(field_decorator) + python_allowed_expr - - derived = "derived" + Suppress(":") + INDENT + derived_calculated_body + UNDENT - calculated = "calculated" + Suppress(":") + INDENT + derived_calculated_body + UNDENT - - source_tag = quotedString\ - .setParseAction(removeQuotes)\ - .setResultsName("source_tag", listAllMatches=True) - source_format = oneOf(cfg['CFG_RECORD_MASTER_FORMATS'])\ - .setResultsName("source_format", listAllMatches=True) - creator_body = (ZeroOrMore(field_decorator) + source_format + Suppress(",") + source_tag + Suppress(",") + python_allowed_expr)\ - .setResultsName("creator_def", listAllMatches=True) - creator = "creator" + Suppress(":") + INDENT + OneOrMore(creator_body) + UNDENT - - checker_function = (Optional(master_format) + ZeroOrMore(ident + ".") + ident + originalTextFor(nestedExpr('(', ')')))\ - .setResultsName("checker_function", listAllMatches=True) - checker = "checker" + Suppress(":") + INDENT + OneOrMore(checker_function) + UNDENT - - doc_string = QuotedString(quoteChar='"""', multiline=True) | quotedString.setParseAction(removeQuotes) - documentation = "documentation" + Suppress(":") + \ - INDENT + Optional(doc_string).setResultsName("documentation") + UNDENT - - producer_code = Word(alphas + "_", alphanums + "_")\ - .setResultsName("producer_code", listAllMatches=True) - producer_body = (producer_code + Suppress(",") + python_allowed_expr)\ - .setResultsName("producer_def", listAllMatches=True) - producer = "producer" + Suppress(":") + INDENT + OneOrMore(producer_body) + UNDENT - - field_def = (creator | derived | calculated)\ - .setResultsName("type_field", listAllMatches=True) - - body = Optional(field_def) + Optional(checker) + Optional(producer) + Optional(documentation) - comment = Literal("#") + restOfLine + LineEnd() - include = (Suppress("include") + quotedString)\ - .setResultsName("includes", listAllMatches=True) - rule = (Optional(persistent_identifier) + Optional(inherit_from) + Optional(override) + json_id + Optional(Suppress(",") + aliases) + Suppress(":") + INDENT + body + UNDENT)\ - .setResultsName("rules", listAllMatches=True) - - return OneOrMore(rule | include | comment.suppress()) - - -def _create_record_model_parser(): - """ - Creates a parser that can handle model definitions. - - BFN like grammar:: - - record_model ::= python_doc_string | python_comment | fields [documentation] [checker] - fields ::= "fields:" INDENT [inherit_from] [list_of_fields] - inherit_from ::= "@inherit_from(" json_id+ ")" - list_of_fields ::= json_id [ "=" json_id ] - - documentation ::= INDENT QUOTED_STRING UNDENT - - checker ::= "checker:" INDENT checker_function+ UNDENT - - Note: Unlike the field configuration files where you can specify more than - one field inside each file for the record models only one definition is - allowed by file. - """ - indentStack = [1] - - field_def = (Word(alphas + "_", alphanums + "_") + \ - Optional(Suppress("=") + \ - Word(alphas + "_", alphanums + "_")))\ - .setResultsName("field_definition") - inherit_from = (Suppress("@inherit_from") + \ - originalTextFor(nestedExpr("(", ")")))\ - .setResultsName("inherit_from") - - fields = (Suppress("fields:") + \ - indentedBlock(inherit_from | field_def, indentStack))\ - .setResultsName("fields") - - ident = Word(alphas + "_", alphanums + "_") - checker_function = (ZeroOrMore(ident + ".") +\ - ident + \ - originalTextFor(nestedExpr('(', ')')))\ - .setResultsName("checker_function", listAllMatches=True) - checker = (Suppress("checker:") + \ - indentedBlock(OneOrMore(checker_function), indentStack))\ - .setResultsName("checker") - - doc_string = QuotedString(quoteChar='"""', multiline=True) | \ - quotedString.setParseAction(removeQuotes) - documentation = (Suppress("documentation:") + \ - indentedBlock(doc_string, indentStack))\ - .setResultsName("documentation") - comment = Literal("#") + restOfLine + LineEnd() - - return OneOrMore(comment | fields + Optional(documentation) + Optional(checker)) - - -class FieldParser(object): - """Field definitions parser""" - def __init__(self, name): - #Autodiscover .cfg files - self.files = autodiscover_non_python_files('.*\.cfg', name) - self.field_definitions = {} - self.legacy_field_matchings = {} - self.__inherit_rules = [] - self.__unresolved_inheritence = [] - self.__override_rules = [] - self.__extend_rules = [] - - def create(self): - """ - Fills up field_definitions and legacy_field_matchings dictionary with - the rules defined inside the configuration files. - - It also resolve the includes present inside the configuration files and - recursively the ones in the other files. - """ - parser = _create_record_field_parser() - already_included = [os.path.basename(f) for f in self.files] - for field_file in self.files: - field_descs = parser.parseFile(field_file, parseAll=True) - for include in field_descs.includes: - if include[0] in already_included: - continue - if not os.path.exists(include[0]): - raise FieldParserException("Can't find file: %s" % (include[0], )) - self.files.append(include[0]) - for rule in field_descs.rules: - if rule.override: - self.__override_rules.append(rule) - elif rule.extend: - self.__extend_rules.append(rule) - elif rule.type_field[0] == 'creator': - self._create_creator_rule(rule) - elif rule.type_field[0] == "derived" or rule.type_field[0] == "calculated": - self._create_derived_calculated_rule(rule) - elif rule.inherit_from: - self.__inherit_rules.append(rule) - else: - assert False, 'Type creator, derived or calculated, or inherit field or overwrite field expected' - - self.__resolve_inherit_rules() - self.__resolve_override_rules() - self.__resolve_extend_rules() - - return (self.field_definitions, self.legacy_field_matchings) - - def _create_creator_rule(self, rule, override=False, extend=False): - """ - Creates the config_rule entries for the creator rules. - The result looks like this:: - - {'json_id':{'rules': { 'inherit_from' : (inherit_from_list), - 'source_format' : [translation_rules], - 'parse_first' : (parse_first_json_ids), - 'depends_on' : (depends_on_json_id), - 'only_if' : (only_if_boolean_expressions), - 'only_if_master_value': (only_if_master_value_boolean_expressions), - }, - 'checker': [(function_name, arguments), ...] - 'documentation' : {'doc_string': '...', - 'subfields' : .....}, - 'type' : 'real' - 'aliases' : [list_of_aliases_ids] - }, - .... - } - """ - json_id = rule.json_id[0] - #Chech duplicate names - if json_id in self.field_definitions and not override and not extend: - raise FieldParserException("Name error: '%s' field name already defined" - % (rule.json_id[0],)) - if not json_id in self.field_definitions and (override or extend): - raise FieldParserException("Name error: '%s' field name not defined" - % (rule.json_id[0],)) - - #Workaround to keep clean doctype files - #Just creates a dict entry with the main json field name and points it to - #the full one i.e.: 'authors' : ['authors[0]', 'authors[n]'] - if '[0]' in json_id or '[n]' in json_id: - main_json_id = re.sub('(\[n\]|\[0\])', '', json_id) - if not main_json_id in self.field_definitions: - self.field_definitions[main_json_id] = [] - self.field_definitions[main_json_id].append(json_id) - - aliases = [] - if rule.aliases: - aliases = rule.aliases.asList() - - persistent_id = None - if rule.persistent_identifier: - persistent_id = int(rule.persistent_identifier[0][0]) - - inherit_from = None - if rule.inherit_from: - self.__unresolved_inheritence.append(json_id) - inherit_from = eval(rule.inherit_from[0]) - - rules = {} - for creator in rule.creator_def: - - source_format = creator.source_format[0] - - if source_format not in rules: - #Allow several tags point to the same json id - rules[source_format] = [] - - (depends_on, only_if, only_if_master_value, parse_first) = self.__create_decorators_content(creator) - self._create_legacy_rules(creator.legacy, json_id, source_format) - - rules[source_format].append({'source_tag' : creator.source_tag[0].split(), - 'value' : creator.value[0], - 'depends_on' : depends_on, - 'only_if' : only_if, - 'only_if_master_value' : only_if_master_value, - 'parse_first' : parse_first}) - if not override and not extend: - self.field_definitions[json_id] = {'inherit_from' : inherit_from, - 'rules' : rules, - 'checker' : [], - 'documentation' : '', - 'producer' : {}, - 'type' : 'real', - 'aliases' : aliases, - 'persistent_identifier': persistent_id, - 'overwrite' : False} - elif override: - self.field_definitions[json_id]['overwrite'] = True - self.field_definitions[json_id]['rules'].update(rules) - self.field_definitions[json_id]['aliases'] = \ - aliases or self.field_definitions[json_id]['aliases'] - self.field_definitions[json_id]['persistent_identifier'] = \ - persistent_id or self.field_definitions[json_id]['persistent_identifier'] - self.field_definitions[json_id]['inherit_from'] = \ - inherit_from or self.field_definitions[json_id]['inherit_from'] - elif extend: - pass - - self._create_checkers(rule) - self._create_documentation(rule) - self._create_producer(rule) - - def _create_derived_calculated_rule(self, rule, override=False): - """ - Creates the field_definitions entries for the virtual fields - The result is similar to the one of real fields but in this case there is - only one rule. - """ - json_id = rule.json_id[0] - #Chech duplicate names - if json_id in self.field_definitions and not override: - raise FieldParserException("Name error: '%s' field name already defined" - % (rule.json_id[0],)) - if not json_id in self.field_definitions and override: - raise FieldParserException("Name error: '%s' field name not defined" - % (rule.json_id[0],)) - - aliases = [] - if rule.aliases: - aliases = rule.aliases.asList() - if re.search('^_[a-zA-Z0-9]', json_id): - aliases.append(json_id[1:]) - - do_not_cache = False - if rule.do_not_cache: - do_not_cache = True - - persistent_id = None - if rule.persistent_identifier: - persistent_id = int(rule.persistent_identifier[0][0]) - - (depends_on, only_if, only_if_master_value, parse_first) = self.__create_decorators_content(rule) - self._create_legacy_rules(rule.legacy, json_id) - - self.field_definitions[json_id] = {'rules' : {}, - 'checker' : [], - 'documentation': '', - 'producer' : {}, - 'aliases' : aliases, - 'type' : rule.type_field[0], - 'persistent_identifier' : persistent_id, - 'overwrite' : False} - - self.field_definitions[json_id]['rules'] = {'value' : rule.value[0], - 'depends_on' : depends_on, - 'only_if' : only_if, - 'only_if_master_value': only_if_master_value, - 'parse_first' : parse_first, - 'do_not_cache' : do_not_cache} - - self._create_checkers(rule) - self._create_documentation(rule) - self._create_producer(rule) - - def _create_legacy_rules(self, legacy_rules, json_id, source_format=None): - """ - Creates the legacy rules dictionary:: - - {'100' : ['authors[0]'], - '100__' : ['authors[0]'], - '100__%': ['authors[0]'], - '100__a': ['auhtors[0].full_name'], - ....... - } - """ - if not legacy_rules: - return - for legacy_rule in legacy_rules: - legacy_rule = eval(legacy_rule[0]) - - if source_format is None: - inner_source_format = legacy_rule[0] - legacy_rule = legacy_rule[1] - else: - inner_source_format = source_format - - if not inner_source_format in self.legacy_field_matchings: - self.legacy_field_matchings[inner_source_format] = {} - - for field_legacy_rule in legacy_rule: - #Allow string and tuple in the config file - legacy_fields = isinstance(field_legacy_rule[0], basestring) and (field_legacy_rule[0], ) or field_legacy_rule[0] - json_field = json_id - if field_legacy_rule[-1]: - json_field = '.'.join((json_field, field_legacy_rule[-1])) - for legacy_field in legacy_fields: - if not legacy_field in self.legacy_field_matchings[inner_source_format]: - self.legacy_field_matchings[inner_source_format][legacy_field] = [] - self.legacy_field_matchings[inner_source_format][legacy_field].append(json_field) - - - def _create_checkers(self, rule): - """ - Creates the list of checker functions and arguments for the given rule - """ - json_id = rule.json_id[0] - assert json_id in self.field_definitions - - if rule.checker_function: - if self.field_definitions[json_id]['overwrite']: - self.field_definitions[json_id]['checker'] = [] - for checker in rule.checker_function: - if checker.master_format: - master_format = eval(rule.master_format[0]) - checker_function_name = checker[1] - arguments = checker[2][1:-1] - else: - master_format = ('all',) - checker_function_name = checker[0] - arguments = checker[1][1:-1] - - #json_id : (master_format, checker_name, parameters) - self.field_definitions[json_id]['checker'].append((master_format, - checker_function_name, - arguments)) - - - def _create_documentation(self, rule): - """ - Creates the documentation dictionary for the given rule - """ - json_id = rule.json_id[0] - assert json_id in self.field_definitions - - if rule.documentation: - self.field_definitions[json_id]['documentation'] = rule.documentation - def _create_producer(self, rule): - """ - Creates the dictionary of possible producer formats for the given rule - """ - json_id = rule.json_id[0] - assert json_id in self.field_definitions - - if rule.producer_def: - if self.field_definitions[json_id]['overwrite']: - self.field_definitions[json_id]['producer'] = {} - for producer in rule.producer_def: - producer_code = producer.producer_code[0] - rule = producer.value[0] - if not producer_code in self.field_definitions[json_id]['producer']: - self.field_definitions[json_id]['producer'][producer_code] = [] - self.field_definitions[json_id]['producer'][producer_code].append(eval(rule)) - - def __create_decorators_content(self, rule): - """ - Extracts from the rule all the possible decorators. - """ - depends_on = only_if = only_if_master_value = parse_first = None - - if rule.depends_on: - depends_on = rule.depends_on[0] - if rule.only_if: - only_if = rule.only_if[0] - if rule.only_if_master_value: - only_if_master_value = rule.only_if_master_value[0] - if rule.parse_first: - parse_first = rule.parse_first[0] - - return (depends_on, only_if, only_if_master_value, parse_first) - - def __resolve_inherit_rules(self): - """ - Iterates over all the 'inherit' fields after all the normal field - creation to avoid problem when creating this rules. - """ - def resolve_inheritance(json_id): - rule = self.field_definitions[json_id] - inherit_from_list = self.field_definitions[json_id]['inherit_from'] - for inherit_json_id in inherit_from_list: - #Check if everithing is fine - if inherit_json_id == json_id: - raise FieldParserException("Inheritance from itself") - if inherit_json_id not in self.field_definitions: - raise FieldParserException("Unable to solve %s inheritance" % (inherit_json_id,)) - if inherit_json_id in self._unresolved_inheritence: - self._resolve_inheritance(inherit_json_id) - self._unresolved_inheritence.remove(inherit_json_id) - inherit_rule = self.field_definitions[inherit_json_id] - for format in inherit_rule['rules']: - if not format in rule['rules']: - rule['rules'][format] = [] - rule['rules'][format].extend(inherit_rule['rules'][format]) - rule['checker'].extend(inherit_rule['checker']) - - for rule in self.__inherit_rules: - if rule.type_field[0] == 'creator': - self._create_creator_rule(rule) - elif rule.type_field[0] == "derived" or rule.type_field[0] == "calculated": - self._create_derived_calculated_rule(rule) - - #Resolve inheritance - for i in xrange(len(self.__unresolved_inheritence) - 1, -1, -1): - resolve_inheritance(self.__unresolved_inheritence[i]) - del self.__unresolved_inheritence[i] - - - def __resolve_override_rules(self): - """ - Iterates over all the 'override' field to override the already created - fields. - """ - for rule in self.__override_rules: - if rule.type_field[0] == 'creator': - self._create_creator_rule(rule, override=True) - elif rule.type_field[0] == "derived" or rule.type_field[0] == "calculated": - self._create_derived_calculated_rule(rule, override=True) - - def __resolve_extend_rules(self): - """ - Iterates over all the 'extend' field to extend the rule definition of this - field. - """ - for rule in self.__extend_rules: - if rule.type_field[0] == 'creator': - self._create_creator_rule(rule, extend=True) - elif rule.type_field[0] == "derived" or rule.type_field[0] == "calculated": - self._create_derived_calculated_rule(rule, extend=True) - - - -class ModelParser(object): - """Record model parser""" - def __init__(self, name): - #Autodiscover .cfg files - self.files = autodiscover_non_python_files('.*\.cfg', name) - self.model_definitions = {} - - def create(self): - """ - Fills up model_definitions dictionary with what is written inside the - *.cfg present in the base directory / models - - It also resolve inheritance at creation time and name matching for the - field names present inside the model file - - The result looks like this:: - - {'model': {'fields': {json_id1: 'name_for_fieldfield1', - json_id2: 'name_for_field2', - .... - 'name_for_fieldN': fieldN }, - 'inherit_from: [(inherit_from_list), ...] - 'documentation': 'doc_string', - 'checker': [(functiona_name, arguments), ...] - }, - ... - - - :raises: ModelParserException in case of missing model definition - (helpful if we use inheritance) or in case of unknown field name. - """ - from .definitions import field_definitions - parser = _create_record_model_parser() - for model_file in self.files: - model_name = os.path.basename(model_file).split('.')[0] - if model_name in self.model_definitions: - raise ModelParserException("Already defined record model: %s" % (model_name,)) - self.model_definitions[model_name] = {'fields': {}, - 'super': [], - 'documentation': '', - 'checker': [] - } - model_definition = parser.parseFile(model_file, parseAll=True) - if model_definition.documentation: - self.model_definitions[model_name]['documentation'] = model_definition.documentation[0][0][0] - if model_definition.checker: - for checker in model_definition.checker[0][0].checker_function: - self.model_definitions[model_name]['checker'].append((checker[0], checker[1][1:-1])) - if not model_definition.fields: - raise ModelParserException("Field definition needed") - for field_def in model_definition.fields[0]: - if field_def.inherit_from: - self.model_definitions[model_name]['super'].extend(eval(field_def[0])) - else: - if len(field_def) == 1: - json_id = field_def[0] - else: - json_id = field_def[1] - if not json_id in field_definitions: - raise ModelParserException("Unknown field name: %s" % (json_id,)) - - self.model_definitions[model_name]['fields'][json_id] = field_def[0] - - for model, model_definition in self.model_definitions.iteritems(): - model_definition['fields'] = self.__resolve_inheritance(model) - - return self.model_definitions - - - def __resolve_inheritance(self, model): - """ - Resolves the inheritance - - :param model: name of the super model - :type model: string - - :return: List of new fields to be added to the son model - :raises: ModelParserException if the super model does not exist. - """ - try: - model_definition = self.model_definitions[model] - except KeyError: - raise ModelParserException("Missing model definition for %s" % (model,)) - fields = {} - for super_model in model_definition['super']: - fields.update(resolve_inheritance(super_model)) - fields.update(model_definition['fields']) - return fields diff --git a/invenio/core/record/definitions.py b/invenio/core/record/definitions.py deleted file mode 100644 index 4c122ed1a..000000000 --- a/invenio/core/record/definitions.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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.from invenio.ext.cache import cache - -#FIXME: add signal management and relation between them - -from invenio.utils.datastructures import LazyDict -from invenio.ext.cache import cache - -__all__ = ['field_definitions', 'legacy_field_matchings', 'model_definitions'] - - -def _rebuild_cache(): - from invenio.core.record.config_engine import FieldParser - field_definitions, legacy_field_matchings = FieldParser( - 'recordext.fields').create() - cache.set('RECORD_FIELD_DEFINITIONS', field_definitions) - cache.set('LEGACY_FIELD_MATCHINGS', legacy_field_matchings) - return field_definitions, legacy_field_matchings - - -def _field_definitions(): - field_definitions = cache.get('RECORD_FIELD_DEFINITIONS') - if field_definitions is None: - field_definitions, _ = _rebuild_cache() - return field_definitions - - -def _legacy_field_matchings(): - legacy_field_matchings = cache.get('LEGACY_FIELD_MATCHINGS') - if field_definitions is None: - _, legacy_field_matchings = _rebuild_cache() - return legacy_field_matchings - - -def _model_definitions(): - model_definitions = cache.get('RECORD_MODEL_DEFINITIONS') - if model_definitions is None: - print ">>> Recreating the cache for models" - from invenio.core.record.config_engine import ModelParser - model_definitions = ModelParser('recordext.models').create() - cache.set('RECORD_MODEL_DEFINITIONS', model_definitions) - return model_definitions - - -field_definitions = LazyDict(_field_definitions) -legacy_field_matchings = LazyDict(_legacy_field_matchings) -model_definitions = LazyDict(_model_definitions) diff --git a/invenio/core/record/models.py b/invenio/core/record/models.py deleted file mode 100644 index bae80fc87..000000000 --- a/invenio/core/record/models.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" -Record model -""" - -from invenio.ext.sqlalchemy import db - -class Record(db.Model): - """Represents a record object inside the SQL database""" - - __tablename__ = 'record' - id = db.Column(db.MediumInteger(8, unsigned=True), - primary_key=True, - nullable=False, - autoincrement=True) - creation_date = db.Column(db.DateTime, - nullable=False, - server_default='1900-01-01 00:00:00', - index=True) - modification_date = db.Column(db.DateTime, - nullable=False, - server_default='1900-01-01 00:00:00', - index=True) - additional_info = db.Column(db.JSON, - nullable=False, - server_default='{}') diff --git a/invenio/core/record/recordext/fields/__init__.py b/invenio/core/record/recordext/fields/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/core/record/recordext/fields/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/core/record/recordext/fields/core_fields.cfg b/invenio/core/record/recordext/fields/core_fields.cfg deleted file mode 100644 index 9d03ea31b..000000000 --- a/invenio/core/record/recordext/fields/core_fields.cfg +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -@persistent_identifier(0) -recid: - creator: - @legacy(("001", "record id", ""), ) - marc, "001", value - checker: - check_field_existence(1, continuable=False) - check_field_type('num', continuable=False) - producer: - json_for_marc, {"001": ""} - documentation: - """ - This is the main persistent identifier of a record and will be used - internally as this, therefore the pid important should always be '0'. - - IMPORTANT: this is a mandatory field and it shouldn't be remove neither - from this configuration file nor from the persistent identifier list. - """ - -_id: - calculated: - self.get('recid', -1) - documentation: - """ - This field is really useful when working with MongoDB to avoid new ObjectID - - See also: .recid - """ - -modification_date, version_id: - creator: - @legacy(("005", ""),) - marc, "005", datetime.datetime(*(time.strptime(value, '%Y%m%d%H%M%S.0')[0:6])) - checker: - check_field_existence(1) - check_field_type('datetime.datetime') - producer: - json_for_marc, {"005": "self.get('version_id').strftime('%Y%m%d%H%M%S.0')"} - json_for_dc, {"dc:date": "self.get('version_id').strftime('%Y-%m-%dT%H:%M:%SZ')"} - documentation: - """Modification date""" - -_modification_date, version_id: - calculated: - @only_if((not 'modification_date' in self, )) - invenio.core.record.models.Record.query.get(self['recid']).modification_date - producer: - json_for_marc, {"005": "self.get('version_id').strftime('%Y%m%d%H%M%S.0')"} - json_for_dc, {"dc:date": "self.get('version_id').strftime('%Y-%m-%dT%H:%M:%SZ')"} - documentation: - """ - Modification date - - See also: .modification_date - """ - -creation_date: - derived: - @parse_first(('recid', )) - @depends_on(('recid', )) - invenio.core.record.models.Record.query.get(self['recid']).creation_date - documentation: - """Creation date""" diff --git a/invenio/core/record/recordext/functions/__init__.py b/invenio/core/record/recordext/functions/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/core/record/recordext/functions/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/core/record/recordext/models/__init__.py b/invenio/core/record/recordext/models/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/core/record/recordext/models/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/core/record/recordext/producers/__init__.py b/invenio/core/record/recordext/producers/__init__.py deleted file mode 100644 index 49c78358e..000000000 --- a/invenio/core/record/recordext/producers/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. diff --git a/invenio/core/record/recordext/producers/dublin_core.py b/invenio/core/record/recordext/producers/dublin_core.py deleted file mode 100644 index 0c4fffdba..000000000 --- a/invenio/core/record/recordext/producers/dublin_core.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -def produce(self, fields=None): - """ - Export the record in dublin core format. - - @param tags: list of tags to include in the output, if None or - empty list all available tags will be included. - """ - from invenio.legacy.bibfield.bibfield_utils import get_producer_rules - - if not fields: - fields = self.keys() - - out = [] - - for field in fields: - if field.startswith('__'): - continue - try: - dc_rules = get_producer_rules(field, 'xd') - for rule in dc_rules: - field = self.get(rule[0], None) - if field is None: - continue - if not isinstance(field, list): - field = [field, ] - for f in field: - for r in rule[1]: - tmp_dict = {} - for key, subfield in r.iteritems(): - if not subfield: - tmp_dict[key] = f - else: - try: - tmp_dict[key] = f[subfield] - except: - try: - tmp_dict[key] = self._try_to_eval(subfield, value=f) - except Exception,e: - self['__error_messages.cerror[n]'] = 'Producer CError - Unable to produce %s - %s' % (field, str(e)) - if tmp_dict: - out.append(tmp_dict) - except KeyError: - self['__error_messages.cerror[n]'] = 'Producer CError - No producer rule for field %s' % field - return out diff --git a/invenio/core/record/storage.py b/invenio/core/record/storage.py deleted file mode 100644 index 0fc2d4cd1..000000000 --- a/invenio/core/record/storage.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- - -## This file is part of Invenio. -## Copyright (C) 2013 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. - -""" - invenio.core.record.storage - --------------------------- - - Record storage interface. -""" - - -class RecordStorage(object): - """ - Record storage interface - """ - #FIXME: set return values on success and error. - - def save_one(self, json, recid=None): - """Stores one json record in the storage system""" - raise NotImplementedError() - - def save_many(self, jsons, recids=None): - """ - Stores many json records in the storage system, as elements on the - iterable jsons - """ - raise NotImplementedError() - - def update_one(self, json, recid=None): - """ - Updates one json record, if recid is None field recid is expected inside - the json object - """ - raise NotImplementedError() - - def update_many(self, jsons, recids=None): - """Update many json records following the same rule as update_one""" - raise NotImplementedError() - - def get_one(self, recid): - """Returns the json record matching the recid""" - raise NotImplementedError() - - def get_many(self, recids): - """Returns an iterable of json records which recid is inside recids""" - raise NotImplementedError() - - def get_field_values(recids, field, repetitive_values=True, count=False, - include_recid=False, split_by=0): - """ - Returns a list of field values for field for the given record ids. - - :param recids: list (or iterable) of integers - :param repetitive_values: if set to True, returns all values even if - they are doubled. If set to False, then return unique values only. - :param count: in combination with repetitive_values=False, adds to the - result the number of occurrences of the field. - :param split: specifies the size of the output. - """ - raise NotImplementedError() - - def get_fields_values(recids, fields, repetitive_values=True, count=False, - include_recid=False, split_by=0): - """ - As in :meth:`get_field_values` but in this case returns a dictionary with each - of the fields and the list of field values. - """ - raise NotImplementedError() - - diff --git a/invenio/ext/registry/flask_registry.py b/invenio/ext/registry/flask_registry.py index 8cd9209ad..ccec7e51c 100644 --- a/invenio/ext/registry/flask_registry.py +++ b/invenio/ext/registry/flask_registry.py @@ -1,518 +1,517 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ Flask-Registry extension """ import os from werkzeug.utils import import_string, find_modules from werkzeug.local import LocalProxy from pkg_resources import iter_entry_points, resource_listdir, resource_isdir from flask import current_app, has_app_context class RegistryError(Exception): pass class Registry(object): """ Flask-Registry """ def __init__(self, app=None): self._registry = dict() self.app = app if app is not None: self.init_app(app) def init_app(self, app): if not hasattr(app, 'extensions'): app.extensions = {} app.extensions['registry'] = self def __iter__(self): return self._registry.__iter__() def __len__(self): return self._registry.__len__() def __contains__(self, item): return self._registry.__contains__(item) def __getitem__(self, key): return self._registry[key] def __setitem__(self, key, value): if key in self._registry: raise RegistryError("Namespace %s already taken." % key) self._registry[key] = value self._registry[key]._namespace = key def __delitem__(self, key): self._registry.__delitem__(key) def __missing__(self, key): return self._registry.__missing__(key) def items(self): return self._registry.items() class RegistryBase(object): """ Base class for all registries """ @property def namespace(self): return self._namespace def register(self, *args, **kwargs): raise NotImplementedError() def unregister(self, *args, **kwargs): raise NotImplementedError() class ListRegistry(RegistryBase): """ Basic registry that just keeps a list of items. """ def __init__(self): self.registry = [] def __iter__(self): return self.registry.__iter__() def __len__(self): return self.registry.__len__() def __contains__(self, item): return self.registry.__contains__(item) def register(self, item): self.registry.append(item) def unregister(self, item): self.registry.remove(item) class DictRegistry(RegistryBase): """ Basic registry that just keeps a key, value pairs. """ def __init__(self): self.registry = {} def __iter__(self): return self.registry.__iter__() def __len__(self): return self.registry.__len__() def __contains__(self, item): return self.registry.__contains__(item) def __getitem__(self, key): return self.registry[key] def __missing__(self, key): return self.registry.__missing__(key) class ExtensionRegistry(ListRegistry): """ Flask extensions registry Loads all extensions specified by EXTENSIONS configuration variable. The registry will look for a setup_app function in the extension and call it if it exists. Example:: EXTENSIONS = [ 'invenio.ext.debug_toolbar', 'flask.ext.menu:Menu', ] """ def __init__(self, app): """ :param app: Flask application to get configuration from. """ super(ExtensionRegistry, self).__init__() for ext_name in app.config.get('EXTENSIONS', []): self.register(app, ext_name) def register(self, app, ext_name): ext = import_string(ext_name) super(ExtensionRegistry, self).register(ext_name) ext = getattr(ext, 'setup_app', ext) ext(app) def unregister(self): raise NotImplementedError() class ImportPathRegistry(ListRegistry): """ Import path registry Example:: registry = ImportPathRegistry(initial=[ - 'invenio.core.*', 'invenio.modules.record', ]) for impstr in registry: print impstr """ def __init__(self, initial=None): super(ImportPathRegistry, self).__init__() if initial: for import_path in initial: self.register(import_path) def register(self, import_path): if import_path.endswith('.*'): for p in find_modules(import_path[:-2], include_packages=True): super(ImportPathRegistry, self).register(p) else: super(ImportPathRegistry, self).register(import_path) def unregister(self): raise NotImplementedError() class ModuleRegistry(ListRegistry): """ Registry for Python modules Each module may provide a ``setup()'' and ``teardown()'' function which will be called when the module is registered. The name of the methods can be customized by subclassing and setting the class attributes ``setup_func_name'' and ``teardown_func_name''. Any extra arguments and keyword arguments to ``register'' and ``unregister'' is passed to the setup and teardown functions. Example:: import mod registry = ModuleRegistry(with_setup=True) registry.register(mod, arg1, arg2, kw1=...) """ setup_func_name = 'setup' teardown_func_name = 'teardown' def __init__(self, with_setup=True): super(ModuleRegistry, self).__init__() self.with_setup = with_setup def register(self, module, *args, **kwargs): super(ModuleRegistry, self).register(module) if self.with_setup: setup_func = getattr(module, self.setup_func_name, None) if setup_func and callable(setup_func): setup_func(*args, **kwargs) def unregister(self, module, *args, **kwargs): super(ModuleRegistry, self).unregister(module) if self.with_setup: teardown_func = getattr(module, self.teardown_func_name, None) if teardown_func and callable(teardown_func): teardown_func(*args, **kwargs) class PackageRegistry(ImportPathRegistry): """ Specialized import path registry that takes the initial list of import paths from PACKAGES configuration variable. Example:: app.extensions['registry']['packages'] = PackageRegistry() for impstr in app.extensions['registry']['packages']: print impstr """ def __init__(self, app): super(PackageRegistry, self).__init__( initial=app.config.get('PACKAGES', []) ) class DiscoverRegistry(ModuleRegistry): """ Python module registry with discover capabilities. The registry will discover module with a given name from packages specified in a ``PackageRegistry''. Example:: app.config['PACKAGES'] = ['invenio.modules.*', ...] app.config['PACKAGES_VIEWS_EXCLUDE'] = ['invenio.modules.oldstuff'] app.extensions['registry']['packages'] = PackageRegistry() app.extensions['registry']['views'] = DiscoverRegistry('views') app.extensions['registry']['views'].discover(app) :param module_name: Name of module to look for in packages :param registry_namespace: Name of registry containing the package registry. Defaults to ``packages''. :param with_setup: Call ``setup'' and ``teardown'' functions on module. """ def __init__(self, module_name, registry_namespace=None, with_setup=False, silent=False): self.module_name = module_name self.silent = silent if registry_namespace is not None and \ isinstance(registry_namespace, (RegistryProxy, RegistryBase)): self.registry_namespace = registry_namespace.namespace else: self.registry_namespace = registry_namespace or 'packages' # Setup config variable prefix self.cfg_var_prefix = self.registry_namespace self.cfg_var_prefix.upper() self.cfg_var_prefix.replace('.', '_') super(DiscoverRegistry, self).__init__(with_setup=with_setup) def discover(self, app=None, *args, **kwargs): """ Discover modules Specific modules can be excluded with the configuration variable ``__EXCLUDE'' (e.g PACKAGES_VIEWS_EXCLUDE). The namespace name is capitalized and have dots replace by underscore. :param module_name: Name of module to look for in packages :param registry_namespace: Name of registry containing the package registry. Defaults to ``packages''. :param with_setup: Call ``setup'' and ``teardown'' functions on module. """ if app is None and has_app_context(): app = current_app if app is None and hasattr(self, 'app'): app = getattr(self, 'app') if app is None: RegistryError("You must provide a Flask application.") self.app = app blacklist = app.config.get( '%s_%s_EXCLUDE' % (self.cfg_var_prefix, self.module_name.upper()), [] ) for pkg in app.extensions['registry'][self.registry_namespace]: if not isinstance(pkg, basestring): pkg = pkg.__name__ if pkg in blacklist: continue self._discover_module(pkg) def _discover_module(self, pkg): import_str = pkg + '.' + self.module_name try: module = import_string(import_str, self.silent) self.register(module) except ImportError: pass except Exception as e: import traceback traceback.print_exc() self.app.logger.error('Could not import: "%s: %s', import_str, str(e)) class AutoDiscoverRegistry(DiscoverRegistry): def __init__(self, module_name, app=None, *args, **kwargs): super(AutoDiscoverRegistry, self).__init__(module_name, *args, **kwargs) self.app = app self.discover(app=app) class AutoDiscoverSubRegistry(AutoDiscoverRegistry): def _discover_module(self, pkg): import_str = pkg + '.' + self.module_name try: import_string(import_str) except ImportError: return for m in find_modules(import_str): try: module = import_string(m, silent=True) if module is not None: self.register(module) except ImportError: pass except Exception as e: import traceback traceback.print_exc() self.app.logger.error('Could not import: "%s: %s', import_str, str(e)) from flask import Blueprint class BlueprintAutoDiscoveryRegistry(AutoDiscoverRegistry): def __init__(self, app=None, module_name=None): super(BlueprintAutoDiscoveryRegistry, self).__init__( module_name or 'views', app=app ) def _discover_module(self, pkg): import_str = pkg + '.' + self.module_name try: view_module = import_string(import_str, self.silent) if 'blueprints' in dir(view_module): candidates = getattr(view_module, 'blueprints') elif 'blueprint' in dir(view_module): candidates = [getattr(view_module, 'blueprint')] else: candidates = [] for candidate in candidates: if isinstance(candidate, Blueprint): self.app.register_blueprint( candidate, url_prefix=self.app.config.get( 'BLUEPRINTS_URL_PREFIXES', {} ).get(candidate.name) ) self.register(candidate) except ImportError: pass except Exception as e: import traceback traceback.print_exc() self.app.logger.error('Could not import: "%s: %s', import_str, str(e)) class PkgResourcesDiscoverRegistry(AutoDiscoverRegistry): def _discover_module(self, pkg): if pkg is not None and resource_isdir(pkg, self.module_name): for f in resource_listdir(pkg, self.module_name): self.register(os.path.join( os.path.dirname(import_string(pkg).__file__), self.module_name, f)) class ConfigurationRegistry(DiscoverRegistry): """ Specialized import path registry that takes the initial list of import paths from PACKAGES configuration variable. Example:: app.extensions['registry']['packages'] = PackageRegistry() app.extendsions['registry']['config'] = ConfigurationRegistry( - _app, base_config='invenio.core.config' + _app, base_config='invenio.config' ) """ def __init__(self, app, registry_namespace=None): super(ConfigurationRegistry, self).__init__( 'config', registry_namespace=registry_namespace, with_setup=False, ) # Create a new configuration module to collect configuration in. from flask import Config self.new_config = Config(app.config.root_path) # Auto-discover configuration in packages self.discover(app) # Overwrite default configuration with user specified configuration self.new_config.update(app.config) app.config.update(self.new_config) def register(self, new_object): self.new_config.from_object(new_object) super(ConfigurationRegistry, self).register(new_object) def unregister(self, *args, **kwargs): raise NotImplementedError() class EntryPointRegistry(DictRegistry): """ Entry point registry Example:: setup( entry_points = { 'invenio.modules.pidstore.providers': [ 'doi = invenio.modules.pidstore.providers:DataCiteDOIProvider', 'doi = invenio.modules.pidstore.providers:LocalDOIProvider', ] } ) providers = RegistryProxy(__name__, EntryPointRegistry) for p in providers['doi']: myprovider = p() :param entry_point_ns: Namespace of entry points. :param load: Load entry points. Defaults to true. """ def __init__(self, entry_point_ns, load=True): super(EntryPointRegistry, self).__init__() self.load = load for entry_point_group in iter_entry_points(iter_entry_points): self.register(entry_point_group) def register(self, entry_point): if entry_point.name not in self.registry: self.registry[entry_point.name] = [] self.registry[entry_point.name].append( entry_point.load() if self.load else entry_point ) class RegistryProxy(LocalProxy): """ Proxy object to a registry in the current app. Allows you to define your registry in your module without needing to initialize it first (since you need the Flaks application). """ def __init__(self, namespace, registry_class, *args, **kwargs): def _lookup(): if not 'registry' in current_app.extensions: raise RegistryError('Registry is not initialized.') if namespace not in current_app.extensions['registry']: current_app.extensions['registry'][namespace] = registry_class( *args, **kwargs ) return current_app.extensions['registry'][namespace] super(RegistryProxy, self).__init__(_lookup) diff --git a/invenio/ext/template/__init__.py b/invenio/ext/template/__init__.py index 737a4f70a..875b549cd 100644 --- a/invenio/ext/template/__init__.py +++ b/invenio/ext/template/__init__.py @@ -1,221 +1,221 @@ # -*- coding: utf-8 -*- ## This file is part of Invenio. ## Copyright (C) 2012, 2013 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. """ invenio.ext.template -------------------- This module provides additional extensions and filters for `jinja2` module used in Invenio. """ from .bccache import BytecodeCacheWithConfig from .context_processor import setup_app as context_processor_setup_app from .loader import OrderAwareDispatchingJinjaLoader from flask import g, request, current_app, _request_ctx_stack, url_for from jinja2 import FileSystemLoader, ChoiceLoader ENV_PREFIX = '_collected_' def render_template_to_string(input, _from_string=False, **context): """Renders a template from the template folder with the given context and return the string. :param input: the string template, or name of the template to be rendered, or an iterable with template names the first one existing will be rendered :param context: the variables that should be available in the context of the template. :note: code based on [https://github.com/mitsuhiko/flask/blob/master/flask/templating.py] """ ctx = _request_ctx_stack.top ctx.app.update_template_context(context) if _from_string: template = ctx.app.jinja_env.from_string(input) else: template = ctx.app.jinja_env.get_or_select_template(input) return template.render(context) def inject_utils(): """ This will add some more variables and functions to the Jinja2 to execution context. In particular it will add: - `url_for`: an Invenio specific wrapper of Flask url_for, that will let you obtain URLs for non Flask-native handlers (i.e. not yet ported Invenio URLs) - `_`: this can be used to automatically translate a given string. - `is_language_rtl`: True if the chosen language should be read right to left. """ from werkzeug.routing import BuildError from invenio.base.i18n import is_language_rtl from flask.ext.babel import get_translations from flask.ext.login import current_user from invenio.utils.url import create_url, get_canonical_and_alternates_urls def invenio_url_for(endpoint, **values): try: return url_for(endpoint, **values) except BuildError: if endpoint.startswith('http://') or endpoint.startswith('https://'): return endpoint if endpoint.startswith('.'): endpoint = request.blueprint + endpoint return create_url('/' + '/'.join(endpoint.split('.')), values, False).decode('utf-8') user = current_user._get_current_object() canonical_url, alternate_urls = get_canonical_and_alternates_urls( request.environ['PATH_INFO']) alternate_urls = dict((ln.replace('_', '-'), alternate_url) for ln, alternate_url in alternate_urls.iteritems()) try: - from invenio.legacy.bibfield import get_record # should not be global due to bibfield_config + from invenio.modules.records.api import get_record # should not be global due to bibfield_config except: get_record = lambda *args, **kwargs: None # from invenio.modules.formatter.engine import TEMPLATE_CONTEXT_FUNCTIONS_CACHE return dict( #FIXME use just gettext when all strings use named arguments _=lambda s, *args, **kwargs: get_translations().gettext( s.encode('utf8'), *args, **kwargs).decode('utf8'), current_user=user, get_css_bundle=current_app.jinja_env.get_css_bundle, get_js_bundle=current_app.jinja_env.get_js_bundle, is_language_rtl=is_language_rtl, canonical_url=canonical_url, alternate_urls=alternate_urls, get_record=get_record, url_for=invenio_url_for, #**TEMPLATE_CONTEXT_FUNCTIONS_CACHE.template_context_functions ) def setup_app(app): """ Extends application template filters with custom filters and fixes. List of applied filters: ------------------------ * filesizeformat * path_join * quoted_txt2html * invenio_format_date * invenio_pretty_date * invenio_url_args """ import os from datetime import datetime from invenio.utils.date import convert_datetext_to_dategui, \ convert_datestruct_to_dategui, pretty_date from . import config app.config.from_object(config) context_processor_setup_app(app) app.context_processor(inject_utils) if app.config.get('JINJA2_BCCACHE', False): app.jinja_options = dict( app.jinja_options, auto_reload=app.config.get('JINJA2_BCCACHE_AUTO_RELOAD', False), cache_size=app.config.get('JINJA2_BCCACHE_SIZE', -1), bytecode_cache=BytecodeCacheWithConfig(app)) ## Let's customize the template loader to look into packages ## and application templates folders. jinja_loader = ChoiceLoader([ OrderAwareDispatchingJinjaLoader(app), app.jinja_loader, ]) app.jinja_loader = jinja_loader for ext in app.config.get('JINJA2_EXTENSIONS', []): try: app.jinja_env.add_extension(ext) except: app.logger.error('Problem with loading extension: "%s"' % (ext, )) test_not_empty = lambda v: v is not None and v != '' @app.template_filter('prefix') def _prefix(value, prefix=''): return prefix + value if test_not_empty(value) else '' @app.template_filter('suffix') def _suffix(value, suffix=''): return value + suffix if test_not_empty(value) else '' @app.template_filter('wrap') def _wrap(value, prefix='', suffix=''): return prefix + value + suffix if test_not_empty(value) else '' @app.template_filter('sentences') def _sentences(value, limit, separator='. '): """ Returns first `limit` number of sentences ending by `separator`. """ return separator.join(value.split(separator)[:limit]) @app.template_filter('path_join') def _os_path_join(d): """Shortcut for `os.path.join`.""" return os.path.join(*d) @app.template_filter('quoted_txt2html') def _quoted_txt2html(*args, **kwargs): from invenio.utils.mail import email_quoted_txt2html return email_quoted_txt2html(*args, **kwargs) @app.template_filter('invenio_format_date') def _format_date(date): """ This is a special Jinja2 filter that will call convert_datetext_to_dategui to print a human friendly date. """ if isinstance(date, datetime): return convert_datestruct_to_dategui(date.timetuple(), g.ln).decode('utf-8') return convert_datetext_to_dategui(date, g.ln).decode('utf-8') @app.template_filter('invenio_pretty_date') def _pretty_date(date): """ This is a special Jinja2 filter that will call pretty_date to print a human friendly timestamp. """ if isinstance(date, datetime) or isinstance(date, basestring): return pretty_date(date, ln=g.ln) return date @app.template_filter('invenio_url_args') def _url_args(d, append=u'?', filter=[]): from jinja2.utils import escape rv = append + u'&'.join( u'%s=%s' % (escape(key), escape(value)) for key, value in d.iteritems(True) if value is not None and key not in filter # and not isinstance(value, Undefined) ) return rv return app diff --git a/invenio/legacy/bibfield/__init__.py b/invenio/legacy/bibfield/__init__.py deleted file mode 100644 index 2aaaeaa57..000000000 --- a/invenio/legacy/bibfield/__init__.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -""" -BibField engine -""" - -__revision__ = "$Id$" - -import os - -try: - import cPickle as pickle -except: - import pickle - -from pprint import pformat -from werkzeug import import_string - -from invenio.utils.datastructures import LaziestDict -from invenio.legacy.dbquery import run_sql -from invenio.ext.logging import register_exception -from invenio.base.signals import record_after_update - -from .bibfield_jsonreader import JsonReader -from .bibfield_utils import BlobWrapper, BibFieldDict - -# Lazy loader of bibfield readers - -def reader_discover(key): - try: - candidate = import_string('invenio.legacy.bibfield.bibfield_%sreader:readers' % (key, )) - if issubclass(candidate, JsonReader): - return candidate - except: - register_exception() - raise KeyError(key) - -CFG_BIBFIELD_READERS = LaziestDict(reader_discover) - - -@record_after_update.connect -def delete_record_cache(sender, recid=None, **kwargs): - get_record(recid, reset_cache=True) - - -def create_record(blob, master_format='marc', verbose=0, **additional_info): - """ - Creates a record object from the blob description using the apropiate reader - for it. - - @return Record object - """ - blob_wrapper = BlobWrapper(blob=blob, master_format=master_format, **additional_info) - - return CFG_BIBFIELD_READERS[master_format](blob_wrapper, check=True) - - -def create_records(blob, master_format='marc', verbose=0, **additional_info): - """ - Creates a list of records from the blod descriptions using the split_records - function to divide then. - - @see create_record() - - @return List of record objects initiated by the functions create_record() - """ - record_blods = CFG_BIBFIELD_READERS[master_format].split_blob(blob, additional_info.get('schema', None)) - - return [create_record(record_blob, master_format, verbose=verbose, **additional_info) for record_blob in record_blods] - - -def get_record(recid, reset_cache=False, fields=()): - """ - Record factory, it retrieves the record from bibfmt table if it is there, - if not, or reset_cache is set to True, it searches for the appropriate - reader to create the representation of the record. - - @return: Bibfield object representing the record or None if the recid is not - present in the system - """ - record = None - #Search for recjson - if not reset_cache: - res = run_sql("SELECT value FROM bibfmt WHERE id_bibrec=%s AND format='recjson'", - (recid,)) - if res: - record = JsonReader(BlobWrapper(pickle.loads(res[0][0]))) - - #There is no version cached or we want to renew it - #Then retrieve information and blob - if not record or reset_cache: - blob_wrapper = _build_wrapper(recid) - if not blob_wrapper: - return None - record = CFG_BIBFIELD_READERS[blob_wrapper.master_format](blob_wrapper) - - #Update bibfmt for future uses - run_sql("REPLACE INTO bibfmt(id_bibrec, format, last_updated, value) VALUES (%s, 'recjson', NOW(), %s)", - (recid, pickle.dumps((record.rec_json)))) - - if fields: - chunk = BibFieldDict() - for key in fields: - chunk[key] = record.get(key) - record = chunk - return record - -def guess_legacy_field_names(fields, master_format='marc'): - """ - Using the legacy rules written in the config file (@legacy) tries to find - the equivalent json field for one or more legacy fields. - - >>> guess_legacy_fields(('100__a', '245'), 'marc') - {'100__a':['authors[0].full_name'], '245':['title']} - """ - from invenio.core.record.definitions import legacy_field_matchings - res = {} - if isinstance(fields, basestring): - fields = (fields, ) - for field in fields: - try: - res[field] = legacy_rules[master_format].get(field, []) - except: - res[field] = [] - return res - - -def _build_wrapper(recid): - #TODO: update to look inside mongoDB for the parameters and the blob - # Now is just working for marc and recstruct - try: - master_format = run_sql("SELECT master_format FROM bibrec WHERE id=%s", (recid,))[0][0] - except: - return None - - schema = 'recstruct' - - if master_format == 'marc': - from invenio.legacy.search_engine import get_record as se_get_record - blob = se_get_record(recid) - else: - return None - - return BlobWrapper(blob, master_format=master_format, schema=schema) diff --git a/invenio/legacy/bibfield/bibfield_jsonreader.py b/invenio/legacy/bibfield/bibfield_jsonreader.py deleted file mode 100644 index acef2c5f2..000000000 --- a/invenio/legacy/bibfield/bibfield_jsonreader.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -""" -BibField Json Reader -""" - -__revision__ = "$Id$" - -import re -import sys -if sys.version_info < (2,5): - def all(list): - for element in list: - if not element: - return False - return True - def any(list): - for element in list: - if element: - return True - return False - -from werkzeug import cached_property - -from .bibfield_utils import BibFieldDict, \ - InvenioBibFieldContinuableError, \ - InvenioBibFieldError -from invenio.core.record.definitions import field_definitions -from invenio.base.utils import import_submodules_from_packages - -class JsonReader(BibFieldDict): - """ - Base class inside the hierarchy that contains several method implementations - that will be shared, eventually, by all the *Reader classes. - In this particular case this class is expecting that the base format is json, - so no conversion is needed. - """ - - def __init__(self, blob_wrapper=None, check = False): - """ - blob -> _prepare_blob(...) -> rec_tree -> _translate(...) -> rec_json -> check_record(...) - """ - super(JsonReader, self).__init__() - self.blob_wrapper = blob_wrapper - self.rec_tree = None # all record information represented as a tree (intermediate structure) - - self.__parsed = [] - - if self.blob_wrapper: - try: - self['__master_format'] = self.blob_wrapper.master_format - except AttributeError: - pass # We are retrieving the cached version from the data base containing __master_format - - self._prepare_blob() - self._translate() - self._post_process_json() - if check: - self.check_record() - self.is_init_phase = False - else: - self['__master_format'] = 'json' - - @cached_property - def producers(self): - def _produce(module): - return module.__name__.split('.')[-1], module.produce - return dict(map( - _produce, import_submodules_from_packages('recordext.producers'))) - - @staticmethod - def split_blob(blob, schema): - """ - In case of several records inside the blob this method specify how to split - then and work one by one afterwards - """ - raise NotImplementedError("This method must be implemented by each reader") - - def get_persistent_identifiers(self): - """ - Using _persistent_identifiers_keys calculated fields gets a subset - of the record containing al persistent indentifiers - """ - return dict((key, self[key]) for key in self.get('_persistent_identifiers_keys', reset_cache=True)) - - def is_empty(self): - """ - One record is empty if there is nothing stored inside rec_json or there is - only '__key' - """ - if self.rec_json is None or len(self.rec_json.keys()) == 0: - return True - if all(key.startswith('_') for key in self.keys()): - return True - return False - - def check_record(self, reset=True): - """ - Using the checking rules defined inside bibfied configurations files checks - if the record is well build. If not it stores the problems inside - self['__error_messages'] splitting then by continuable errors and fatal/non-continuable - errors - """ - def check_rules(checker_functions, key): - """docstring for check_rule""" - for checker_function in checker_functions: - if 'all' in checker_function[0] or self['__master_format'] in checker_function[0]: - try: - self._try_to_eval("%s(self,'%s',%s)" % (checker_function[1], key, checker_function[2])) - except InvenioBibFieldContinuableError, err: - self['__error_messages']['cerror'].append('Checking CError - ' + str(err)) - except InvenioBibFieldError, err: - self['__error_messages']['error'].append('Checking Error - ' + str(err)) - - if reset or '__error_messages.error' not in self or '__error_messages.cerror' not in self: - self.rec_json['__error_messages'] = {'error': [], 'cerror': []} - - for key in self.keys(): - try: - check_rules(field_definitions[key]['checker'], key) - except TypeError: - for kkey in field_definitions[key]: - check_rules(field_definitions[kkey]['checker'], kkey) - except KeyError: - continue - - @property - def fatal_errors(self): - """@return All the fatal/non-continuable errors that check_record has found""" - return self.get('__error_messages.error', []) - - @property - def continuable_errors(self): - """@return All the continuable errors that check_record has found""" - return self.get('__error_messages.cerror', []) - - def legacy_export_as_marc(self): - """ - It creates a valid marcxml using the legacy rules defined in the config - file - """ - from collections import Iterable - def encode_for_marcxml(value): - from invenio.utils.text import encode_for_xml - if isinstance(value, unicode): - value = value.encode('utf8') - return encode_for_xml(str(value)) - - export = '' - marc_dicts = self.producers['marc'](self) - for marc_dict in marc_dicts: - content = '' - tag = '' - ind1 = '' - ind2 = '' - for key, value in marc_dict.iteritems(): - if isinstance(value, basestring) or not isinstance(value, Iterable): - value = [value] - for v in value: - if v is None: - continue - if key.startswith('00') and len(key) == 3: - # Control Field (No indicators no subfields) - export += '%s\n' % (key, encode_for_marcxml(v)) - elif len(key) == 6: - if not (tag == key[:3] and ind1 == key[3].replace('_', '') and ind2 == key[4].replace('_', '')): - tag = key[:3] - ind1 = key[3].replace('_', '') - ind2 = key[4].replace('_', '') - if content: - export += '%s\n' % (tag, ind1, ind2, content) - content = '' - content += '%s' % (key[5], encode_for_marcxml(v)) - else: - pass - - if content: - export += '%s\n' % (tag, ind1, ind2, content) - - export += '' - return export - - def get_legacy_recstruct(self): - """ - It creates the recstruct representation using the legacy rules defined in - the configuration file - - #CHECK: it might be a bit overkilling - """ - from invenio.legacy.bibrecord import create_record - return create_record(self.legacy_export_as_marc())[0] - - def _prepare_blob(self): - """ - This method might be overwritten by the *Reader and should take care of - transforming the blob into an homogeneous format that the _translate() - method understands - Overwriting this method is optional if there is no need of transforming - the blob into something that _translate understands - """ - #In this case no translation needed - self.rec_tree = self.rec_json = self.blob_wrapper.blob - - def _translate(self): - """ - Using the intermediate structure (self.rec_tree) that _prepare_blob has - created it transforms the record into a jsonic structure using the rules - present into the bibfield configuration file. - To apply this rules it takes into account the type of the reader (which - indeed means the type of source format) and the doctype. - """ - - if self.__class__.__name__ == 'JsonReader': - #No translation needed for json - pass - else: - #TODO: allow a list of doctypes and get the union of them - # fields = doctype_definition[blob.doctype]['fields'] - # Now just getting all the possible field from field_definitions - fields = dict(zip(field_definitions.keys(), field_definitions.keys())) - for json_id, field_name in fields.iteritems(): - self._unpack_rule(json_id, field_name) - - def _get_elements_from_rec_tree(self, regex_rules): - for regex_rule in regex_rules: - for element in self.rec_tree[re.compile(regex_rule)]: - yield element - - def _unpack_rule(self, json_id, field_name=None): - if not field_name: - field_name = json_id - - rule_def = field_definitions[json_id] - - if isinstance(rule_def, list): # Undo the workaround for [0] and [n] - return all([self._unpack_rule(json_id_rule) for json_id_rule in rule_def]) - if (json_id, field_name) in self.__parsed: - return field_name in self - - self.__parsed.append((json_id, field_name)) - - if rule_def['type'] == 'real': - try: - rules = rule_def['rules'][self['__master_format']] - except KeyError: - return False - return all(self._apply_rule(field_name, rule_def['aliases'], rule) for rule in rules) - else: - return self._apply_virtual_rule(field_name, rule_def['aliases'], rule_def['rules'], rule_def['type']) - - def _apply_rule(self, field_name, aliases, rule): - if 'entire_record' in rule['source_tag'] or any(key in self.rec_tree for key in rule['source_tag']): - if rule['parse_first']: - for json_id in self._try_to_eval(rule['parse_first']): - self._unpack_rule(json_id) - if rule['depends_on'] and not all(k in self for k in self._try_to_eval(rule['depends_on'])): - return False - if rule['only_if'] and not all(self._try_to_eval(rule['only_if'])): - return False - if 'entire_record' in rule['source_tag']: - self[field_name] = self._try_to_eval(rule['value'], value=self.rec_tree) - else: - for elements in self._get_elements_from_rec_tree(rule['source_tag']): - if isinstance(elements, list): - returned_value = False - for element in elements: - if rule['only_if_master_value'] and not all(self._try_to_eval(rule['only_if_master_value'], value=element)): - returned_value = returned_value or False - else: - try: - self[field_name] = self._try_to_eval(rule['value'], value=element) - returned_value = returned_value or True - except Exception, e: - self['__error_messages.error[n]'] = 'Rule Error - Unable to apply rule for field %s - %s' % (field_name, str(e)) - returned_value = returned_value or False - else: - if rule['only_if_master_value'] and not all(self._try_to_eval(rule['only_if_master_value'], value=elements)): - return False - else: - try: - self[field_name] = self._try_to_eval(rule['value'], value=elements) - except Exception, e: - self['__error_messages.error[n]'] = 'Rule Error - Unable to apply rule for field %s - %s' % (field_name, str(e)) - returned_value = returned_value or False - - for alias in aliases: - self['__aliases'][alias] = field_name - return True - else: - return False - - def _apply_virtual_rule(self, field_name, aliases, rule, rule_type): - if rule['parse_first']: - for json_id in self._try_to_eval(rule['parse_first']): - self._unpack_rule(json_id) - if rule['depends_on'] and not all(k in self for k in self._try_to_eval(rule['depends_on'])): - return False - if rule['only_if'] and not all(self._try_to_eval(rule['only_if'])): - return False - #Apply rule - if rule_type == 'derived': - try: - self[field_name] = self._try_to_eval(rule['value']) - except Exception, e: - self['__error_messages.cerror[n]'] = 'Virtual Rule CError - Unable to evaluate %s - %s' % (field_name, str(e)) - else: - self['__calculated_functions'][field_name] = rule['value'] - if rule['do_not_cache']: - self['__do_not_cache'].append(field_name) - self[field_name] = None - else: - try: - self[field_name] = self._try_to_eval(rule['value']) - except Exception, e: - self['__error_messages.cerror[n]'] = 'Virtual Rule CError - Unable to evaluate %s - %s' % (field_name, str(e)) - - for alias in aliases: - self['__aliases'][alias] = field_name - - return True - - def _post_process_json(self): - """ - If needed this method will post process the json structure, e.g. pruning - the json to delete None values - """ - def remove_none_values(d): - if d is None or not isinstance(d, dict): - return - for key, value in d.items(): - if value is None: - del d[key] - if isinstance(value, dict): - remove_none_values(value) - if isinstance(value, list): - for element in value: - if element is None: - value.remove(element) - else: - remove_none_values(element) - remove_none_values(self.rec_json) - - -## Compulsory plugin interface -readers = JsonReader diff --git a/invenio/legacy/bibfield/bibfield_manager.py b/invenio/legacy/bibfield/bibfield_manager.py deleted file mode 100644 index 5babc20f2..000000000 --- a/invenio/legacy/bibfield/bibfield_manager.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -from invenio.ext.script import Manager - -manager = Manager(usage="Perform BibField operations") - -# Define sub-managers -bibfield_config = Manager(usage="Manipulates BibField config.") -bibfield_cache = Manager(usage="Manipulates BibField cache.") - -# Add sub-managers -manager.add_command("config", bibfield_config) -manager.add_command("cache", bibfield_cache) - - -@bibfield_config.command -def load(): - """Loads BibField config.""" - print ">>> Going to load BibField config..." - print ">>> Deprecated: use rediscli flushdb until a new version is ready" - # from invenio.legacy.bibfield.config_engine import BibFieldParser - # BibFieldParser().write_to_file() - print ">>> BibField config load successfully." - - -@bibfield_cache.command -def reset(split_by=1000): - """Reset record json structure cache.""" - from . import get_record - from invenio.base.scripts.cache import reset_rec_cache - reset_rec_cache('recjson', get_record, split_by=split_by) - - -def main(): - from invenio.base.factory import create_app - app = create_app() - manager.app = app - manager.run() - -if __name__ == '__main__': - main() diff --git a/invenio/legacy/bibfield/bibfield_marcreader.py b/invenio/legacy/bibfield/bibfield_marcreader.py deleted file mode 100644 index ad46c118c..000000000 --- a/invenio/legacy/bibfield/bibfield_marcreader.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011 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. - -""" - -""" - -__revision__ = "$Id$" - - -from .bibfield_jsonreader import JsonReader -from .bibfield_utils import CoolDict, CoolList - - -class MarcReader(JsonReader): - """ - Reader class that understands MARC21 as base format - """ - - @staticmethod - def split_blob(blob, schema): - """ - Splits the blob using .*? as pattern. - - Note 1: Taken from invenio.legacy.bibrecord:create_records - Note 2: Use the DOTALL flag to include newlines. - """ - import re - regex = re.compile('.*?', re.DOTALL) - return regex.findall(blob) - - def _prepare_blob(self): - """ - Transforms the blob into rec_tree structure to use it in the standar - translation phase inside C{JsonReader} - """ - self.rec_tree = CoolDict() - try: - if self.blob_wrapper.schema.lower().startswith('file:'): - self.blob_wrapper.blob = open(self.blob_wrapper.blob_file_name, 'r').read() - if self.blob_wrapper.schema.lower() in ['recstruct']: - self.__create_rectree_from_recstruct() - elif self.blob_wrapper.schema.lower() in ['xml', 'file:xml']: - #TODO: Implement translation directrly from xml - from invenio.legacy.bibrecord import create_record - self.blob_wrapper.blob = create_record(self.blob_wrapper.blob)[0] - self.__create_rectree_from_recstruct() - except AttributeError: - #Assume marcxml - from invenio.legacy.bibrecord import create_record - self.blob_wrapper.blob = create_record(self.blob_wrapper.blob)[0] - self.__create_rectree_from_recstruct() - - def __create_rectree_from_recstruct(self): - """ - Using rectruct as base format it creates the intermediate structure that - _translate will use. - """ - for key, values in self.blob_wrapper.blob.iteritems(): - if key < '010' and key.isdigit(): - #Control field, it assumes controlfields are numeric only - self.rec_tree[key] = CoolList([value[3] for value in values]) - else: - for value in values: - field = CoolDict() - for subfield in value[0]: - field.extend(subfield[0], subfield[1]) - self.rec_tree.extend((key + value[1] + value[2]).replace(' ', '_'), field) - -## Compulsory plugin interface -readers = MarcReader diff --git a/invenio/legacy/bibfield/bibfield_utils.py b/invenio/legacy/bibfield/bibfield_utils.py deleted file mode 100644 index 9d90e20ee..000000000 --- a/invenio/legacy/bibfield/bibfield_utils.py +++ /dev/null @@ -1,689 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -""" -BibField Utils - -Helper classes and functions to work with BibField -""" - -import re - -__revision__ = "$Id$" - -import datetime -from werkzeug.utils import import_string - -from invenio.utils.datastructures import LaziestDict - -CFG_BIBFIELD_FUNCTIONS = LaziestDict(lambda key: import_string( - 'invenio.legacy.bibfield.functions.%s:%s' % (key, key))) - - -class BibFieldException(Exception): - """ - General exception to use within BibField - """ - pass - - -class InvenioBibFieldContinuableError(Exception): - """BibField continuable error""" - pass - - -class InvenioBibFieldError(Exception): - """BibField fatal error, @see CFG_BIBUPLOAD_BIBFIELD_STOP_ERROR_POLICY""" - - -class BibFieldDict(object): - """ - This class implements a I{dict} mostly and uses special key naming for - accessing as describe in __getitem__ - - >>> #Creating a dictionary - >>> d = BibFieldDict() - - >>> #Filling up the dictionary - >>> d['foo'] = {'a': 'world', 'b':'hello'} - >>> d['a'] = [ {'b':1}, {'b':2}, {'b':3} ] - >>> d['_c'] = random.randint(1,100) - >>> d['__calculated_functions']['_c'] = "random.randint(1,100)" - - >>> #Accessing data inside the dictionary - >>> d['a'] - >>> d['a[0]'] - >>> d['a.b'] - >>> d['a[1:]'] - >>> d['_c'] #this value will be calculated on the fly - """ - - def __init__(self): - self.rec_json = {} - self.rec_json['__aliases'] = {} - self.rec_json['__do_not_cache'] = [] - self.is_init_phase = True - self.rec_json['__calculated_functions'] = {} - - def __getitem__(self, key): - """ - As in C{dict.__getitem__} but using BibField name convention. - - @param key: String containing the name of the field and subfield. - For e.g. lest work with: - {'a': [ {'b':1}, {'b':2}, {'b':3} ], '_c': 42 } - - 'a' -> All the 'a' field info - [{'b': 1}, {'b': 2}, {'b': 3}] - - 'a[0]' -> All the info of the first element inside 'a' - {'b': 1} - - 'a[0].b' -> Field 'b' for the first element in 'a' - 1 - - 'a[1:]' -> All the 'a' field info but the first - [{'b': 2}, {'b': 3}] - - 'a.b' -> All the 'b' inside 'b' - [1, 2, 3] - - '_c- -> will give us the random number that is cached - 42 - - ... any other combination ... - - ... as deep as the dictionary is ... - - NOTE: accessing one value in a normal way, meaning d['a'], is almost as - fast as accessing a regular dictionary. But using the special name - convention is a bit slower than using the regular access. - d['a[0].b'] -> 10000 loops, best of 3: 18.4 us per loop - d['a'][0]['b'] -> 1000000 loops, best of 3: 591 ns per loop - - @return: The value of the field, this might be, a dictionary, a list, - a string, or any combination of the three depending on the value of - field - """ - if not self.is_cacheable(key): - dict_part = self._recalculate_field_value(key) - else: - dict_part = self.rec_json - - try: - if '.' not in key and '[' not in key: - dict_part = dict_part[key] - else: - for group in prepare_field_keys(key): - dict_part = self._get_intermediate_value(dict_part, group) - except KeyError, err: - return self[key.replace(err.args[0], self.rec_json['__aliases'][err.args[0]].replace('[n]', '[1:]'), 1)] - - return dict_part - - def __setitem__(self, key, value): - """ - As in C{dict.__setitem__} but using BibField name convention. - - @note: at creation time dict['a[-1]'] = 'something' will mean - dict['a'].append('something') and if the field already exists and is - not a list, then this method will create a list with the existing value - and append the new one, - dict['a'] = 'first value' -> {'a':'first value'} - dict['a'] = 'second value' -> {'a':['first value', 'second value']} - There is one class variable self.is_init_phase for that matter. - - @param key: String containing the name of the field and subfield. - @param value: The new value - """ - if self.is_init_phase: - if '.' not in key and '[' not in key: - if not key in self.rec_json: - self.rec_json[key] = value - return - tmp = self.rec_json[key] - if tmp is None: - self.rec_json[key] = value - else: - if not isinstance(tmp, list): - self.rec_json[key] = [tmp] - self.rec_json[key].append(value) - else: - try: - dict_part = eval("self.rec_json%s" % (''.join(prepare_field_keys(key)),)) # kwalitee: disable=eval - except: - build_data_structure(self.rec_json, key) - dict_part = eval("self.rec_json%s" % (''.join(prepare_field_keys(key)),)) - if dict_part: - exec("self.rec_json%s.append(value)" % (''.join(prepare_field_keys(key, write=True)[:-1]),)) - else: - exec("self.rec_json%s = value" % (''.join(prepare_field_keys(key)),)) - else: - if '.' not in key and '[' not in key: - self.rec_json[key] = value - else: - try: - exec("self.rec_json%s = value" % (''.join(prepare_field_keys(key)),)) - except: - build_data_structure(self.rec_json, key) - exec("self.rec_json%s = value" % (''.join(prepare_field_keys(key)),)) - - def __delitem__(self, key): - """ - As in C{dict.__delitem__}. - - @note: It only works with first keys - """ - del self.rec_json[key] - - def __contains__(self, key): - """ - As in C{dict.__contains__} but using BibField name convention. - - @param key: Name of the key - @return: True if the dictionary contains the special key - """ - if '.' not in key and '[' not in key: - return key in self.rec_json - try: - self[key] - except: - return False - return True - - def __eq__(self, other): - """@see C{dict.__eq__}""" - if not self.keys() == other.keys(): - return False - try: - for key in [k for k in self.keys() if not k in self['__do_not_cache']]: - if not self.get(k) == other.get(k): - return False - except: - return False - return True - - def __repr__(self): - """ - Hides the '__keys' from the dictionary representation, if those keys - are needed record.rec_json could be accessed. - @see C{dict.__repr__} - """ - info = dict((key, value) for key, value in self.rec_json.iteritems() if not re.search('^__[a-zA-Z0-9]', key)) - if not info: - info = {} - return repr(info) - - def __iter__(self): - """@see C{dict.__iter__}""" - return iter(self.rec_json) - - def __len__(self): - """@see C{dict.__len__}""" - return len(self.rec_json) - - def keys(self): - """@see C{dict.keys}""" - return self.rec_json.keys() - - def iteritems(self): - """@see C{dict.iteritems}""" - return self.rec_json.iteritems() - - def iterkeys(self): - """@see C{dict.iterkeys}""" - return self.rec_json.iterkeys() - - def itervalues(self): - """@see C{dict.itervalues}""" - return self.rec_json.itervalues() - - def has_key(self, key): - """ - As in C{dict.has_key} but using BibField name convention. - @see __contains__(self, key) - """ - return self.__contains__(key) - - def get(self, field=None, default=None, reset_cache=False, formatstring=None, formatfunction=None): - """ - As in C{dict.get} it Retrieves the value of field from the json structure - but using BibField name convention and also applies some formating if - present. - - @see __getitem__(self, key) - - @param field: Name of the field/s to retrieve. If it is None then it - will return the entire dictionary. - @param default: in case of error this value will be returned - @param formatstring: Optional parameter to format the output value. - This could be a format string, like this example: - >>> d['foo'] = {'a': 'world', 'b':'hello'} - >>> get('foo', formatstring="{0[b]} {0[a]}!") - >>> 'hello world!' - Note: Use this parameter only if you are running python 2.5 or higher. - @param formatfunction: Optional parameter to format the output value. - This parameter must be function and must handle all the possible - parameter types (str, dict or list) - - @return: The value of the field, this might be, a dictionary, a list, - a string, or any combination of the three depending on the value of - field. If any formating parameter is present, then the return value - will be the formated value. - """ - if reset_cache: - self.update_field_cache(field) - - value = self.rec_json - if field: - try: - value = self.__getitem__(field) - except: - return default - - if not value: - return default - - if formatstring: - value = self._apply_formatstring(value, formatstring) - - if formatfunction: - value = formatfunction(value) - - return value - - def is_cacheable(self, field): - """ - Check if a field is inside the __do_not_cache or not - - @return True if it is not in __do_not_cache - """ - return not get_main_field(field) in self.rec_json['__do_not_cache'] - - - def update_field_cache(self, field): - """ - Updates the value of the cache for the given calculated field - """ - field = get_main_field(field) - if re.search('^_[a-zA-Z0-9]', field) and not field in self.rec_json['__do_not_cache']: - self.rec_json[field] = self._recalculate_field_value(field)[field] - - def update_all_fields_cache(self): - """ - Update the cache of all the calculated fields - @see: update_field_cache() - """ - for field in [key for key in self.keys() if re.search('^_[a-zA-Z0-9]', key)]: - self.update_field_cache(field) - - def _recalculate_field_value(self, field): - """ - Obtains the new vaule of field using - """ - field = get_main_field(field) - return {field: self._try_to_eval(self['__calculated_functions'][field])} - - def _try_to_eval(self, string, bibfield_functions_only=False, **context): - """ - This method takes care of evaluating the python expression, and, if an - exception happens, it tries to import the needed module from bibfield_functions - or from the python path using plugin utils - - @param string: String to evaluate - @param context: Context needed, in some cases, to evaluate the string - - @return: The value of the expression inside string - """ - if not string: - return None - - res = None - imports = [] - - while (True): - try: - res = eval(string, globals().update(context), locals()) # kwalitee: disable=eval - except NameError, err: - import_name = str(err).split("'")[1] - if not import_name in imports: - if import_name in CFG_BIBFIELD_FUNCTIONS: - globals()[import_name] = CFG_BIBFIELD_FUNCTIONS[import_name] - elif not bibfield_functions_only: - globals()[import_name] = __import__(import_name) - imports.append(import_name) - continue - assert False, 'Error not expected when trying to import bibfield function module' - return res - - def _apply_formatstring(self, value, formatstring): - """ - Helper function that simply formats the result of get() using a - format string - - If the value is of type datetime it tries to apply the format using - strftime(formatstring). - - @see: get(self, field=None, formatstring=None, formatfunction=None) - - @param value: String, dict or list to apply the format string - @param formatstring: formatstring - - @return: Formated value of "value" - """ - if not value: - return '' - if isinstance(value, datetime.datetime): - if formatstring == value.strftime(formatstring): - value = value.isoformat() - else: - return value.strftime(formatstring) - if isinstance(value, list): - tmp = '' - for element in value: - tmp += self._apply_formatstring(element, formatstring) - return tmp - elif isinstance(value, dict) or isinstance(value, basestring): - return formatstring.format(value) - else: - assert False, 'String, Dictionay or List expected' - - def _get_intermediate_value(self, dict_part, field): - """ - Helper function that fetch the value of some field from dict_part - - @see: get(self, field=None, formatstring=None, formatfunction=None) - - @param dict_part: Dictionary or list containing all the information from - this method will fetch field. - @param field: Name or index of the field to fetch from dict_part - - @return: The value of the field, this might be, a dictionary, a list, - a string, or any combination of the three depending on the value of - field - """ - if isinstance(dict_part, dict): - return eval('dict_part%s' % field) # kwalitee: disable=eval - elif isinstance(dict_part, list): - tmp = [] - for element in dict_part: - tmp.append(self._get_intermediate_value(element, field)) - return tmp - else: - assert False, 'Dictionay or List expected get %s' % (type(dict_part),) - - -class BlobWrapper(object): - """ - Wrapper class to work easily with the blob and the information related to it - inside the *Reader - """ - def __init__(self, blob, **kw): - self.__info = kw - self.blob = blob - - def __getattr__(self, name): - """Trick to access the information inside self.__info using dot syntax""" - try: - return self.__info[name] - except KeyError: - raise AttributeError("%r object has no attribute %r" % (type(self).__name__, name)) - - -class CoolDict(dict): - """ - C{dict} but it keeps track of which elements has been consumed/accessed - and which not - """ - - def __init__(self, *args, **kwargs): - dict.__init__(self, *args, **kwargs) - self._consumed = {} - if self: - for key, value in dict.iteritems(self): - self[key] = value - - def __getitem__(self, key): - """ - As in C{dict} but in this case the key could be a compiled regular expression. - - Also update the consumed list in case the item is not a list or other - dictionary. - - @return: Like in C{dict.__getitem__} or, if a regular expression is used, - a list containing all the items inside the dictionary which key matches - the regular expression ([] if none) - """ - try: - keys = filter(key.match, self.keys()) - values = [] - for key in keys: - value = dict.get(self, key) - values.append(dict.get(self, key)) - if not isinstance(value, dict) and not isinstance(value, list): - self._consumed[key] = True - return values - except AttributeError: - try: - value = dict.get(self, key) - if not isinstance(value, dict) and not isinstance(value, list): - self._consumed[key] = True - return value - except: - return None - - def __setitem__(self, key, value): - """ - As in C{dict} but in this case it takes care of updating the consumed - value for each element inside value depending on its type. - """ - if isinstance(value, dict): - dict.__setitem__(self, key, CoolDict(value)) - self._consumed[key] = self[key]._consumed - elif isinstance(value, list): - dict.__setitem__(self, key, CoolList(value)) - self._consumed[key] = self[key]._consumed - else: - dict.__setitem__(self, key, value) - self._consumed[key] = False - - def extend(self, key, value): - """ - If the key is present inside the dictionary it creates a list (it not - present) and extends it with the new value. Almost as in C{list.extend} - """ - if key in self: - current_value = dict.get(self, key) - if not isinstance(current_value, list): - current_value = CoolList([current_value]) - current_value.append(value) - value = current_value - - self[key] = value - - def iteritems(self): - """ As in C{dict} but it updates the consumed value if needed""" - for key, value in dict.iteritems(self): - if not isinstance(value, dict) and not isinstance(value, list): - self._consumed[key] = True - yield key, value - - raise StopIteration - - @property - def consumed(self): - for key, value in self._consumed.iteritems(): - if not isinstance(value, dict) and not isinstance(value, list): - if not value: - return False - elif not dict.get(self, key).consumed: - return False - return True - - -class CoolList(list): - """ - C{list} but it keeps track of which elements has been consumed/accessed and - which not - """ - - def __init__(self, *args, **kwargs): - list.__init__(self, *args, **kwargs) - self._consumed = [] - if self: - for i, value in enumerate(list.__iter__(self)): - self._consumed.append(None) - self[i] = value - - def __getitem__(self, index): - """As in C{list}, also update the consumed list in case the item is not - a dictionary or other list. - - @return: Like in C{list.__getitem__} - """ - value = list.__getitem__(self, index) - if not isinstance(value, dict) and not isinstance(value, list): - self._consumed[index] = True - return value - - def __setitem__(self, index, value): - """ - As in C{list} but in this case it takes care of updating the consumed - value for each element inside value depending on its type - """ - - if isinstance(value, dict): - list.__setitem__(self, index, CoolDict(value)) - self._consumed[index] = self[index]._consumed - elif isinstance(value, list): - list.__setitem__(self, index, CoolList(value)) - self._consumed[index] = self[index]._consumed - else: - list.__setitem__(self, index, value) - self._consumed[index] = False - - def __iter__(self, *args, **kwargs): - """ As in C{dict} but it updates the consumed value if needed""" - for index, value in enumerate(list.__iter__(self)): - if not isinstance(value, dict) and not isinstance(value, list): - self._consumed[index] = True - yield value - - raise StopIteration - - def append(self, element): - """@see __setitem__() """ - self += [None] - self._consumed += [None] - self[len(self) - 1] = element - - @property - def consumed(self): - for index, value in enumerate(self._consumed): - if not isinstance(value, dict) and not isinstance(value, list): - if not value: - return False - elif not list.__getitem__(self, index).consumed: - return False - return True - - -def prepare_field_keys(field, write=False): - """ - Helper function to split the name of the fields and the indexes in a - proper way to be used by eval function - - @see: bibfield.get() - - @param field: String containing all the names and indexes - @param write: If the fields are use to write inside the record then the - granularity is lower for convenience - - @return: List of string that can be evaluated by eval function - """ - parts = field.split('.') - keys = [] - for part in parts: - if '[' in part: - if write: - keys.append('["%s"]' % (part[:part.find('[')])) - keys.append(part[part.find('['):].replace('n', '-1')) - else: - keys.append('["%s"]%s' % (part[:part.find('[')], part[part.find('['):].replace('n', '-1'))) - else: - keys.append('["%s"]' % part) - return keys - - -def build_data_structure(record, field): - """ - Helper functions that builds the record structure - - @param record: Existing data structure - @param field: New field to add to the structure - """ - eval_string = '' - for key in prepare_field_keys(field, write=True): - if key == '[-1]': - try: - eval("record%s.append(None)" % (eval_string,)) # kwalitee: disable=eval - except AttributeError: - exec("record%s=[None]" % (eval_string,)) - elif key == '[0]': - try: - eval("record%s" % (eval_string + key,)) # kwalitee: disable=eval - rec_part = eval("record%s" % (eval_string,)) # kwalitee: disable=eval - if not isinstance(rec_part, list): - pass - rec_part.insert(0, None) - except TypeError: - exec("record%s=list([None])" % (eval_string,)) - else: - try: - eval("record%s" % (eval_string + key,)) # kwalitee: disable=eval - except KeyError: - exec("record%s=None" % (eval_string + key,)) - except TypeError: - exec("record%s={}" % (eval_string,)) - exec("record%s=None" % (eval_string + key,)) - eval_string += key - - -def get_main_field(field): - """ - From a given field it gets the outer field of the tree. - - i.e.: 'a[0].b.c' returns 'a' - """ - if '.' in field: - field = field.split('.')[0] - if '[' in field: - field = field.split('[')[0] - return field - - -def get_producer_rules(field, code): - """docstring for get_producer_rules""" - from invenio.core.record.definitions import legacy_field_matchings - - rule = legacy_field_matchings[field] - if isinstance(rule, list): - if len(rule) == 1: - # case field[n] - return [(rule[0].replace('[n]', ''), legacy_field_matchings[rule[0]]['producer'].get(code, {}))] - else: - # case field[1], field[n] - rules = [] - for new_field in rule: - rules.append((new_field.replace('[n]', '[1:]'), legacy_field_matchings[new_field]['producer'].get(code, {}))) - return rules - else: - return [(field, rule['producer'].get(code, {}))] diff --git a/invenio/legacy/bibfield/functions/check_field_existence.py b/invenio/legacy/bibfield/functions/check_field_existence.py deleted file mode 100644 index 3b123d183..000000000 --- a/invenio/legacy/bibfield/functions/check_field_existence.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - - -def check_field_existence(record, field, min_value, max_value=None, subfield=None, continuable=True): - """ - Checks field.subfield existence inside the record according to max and min values - - @param record: BibFieldDict where the record is stored - @param field: Main json ID or field name to make test on - @param min_value: Minimum number of occurrences of field. - If max_value is not present then min_value represents the fix number of times that - field should be present. - @param max_value: Maximum number of occurrences of a field, this might be a fix number - or "n". - @param subfield: If this parameter is present, instead of applying the checker - to the field, it is applied to record['field.subfield'] - - @note: This checker also modify the record if the field is not repeatable, - meaning that min_value=1 or min_value=0,max_value=1 - """ - from invenio.legacy.bibfield.bibfield_utils import InvenioBibFieldContinuableError, \ - InvenioBibFieldError - - error = continuable and InvenioBibFieldContinuableError or InvenioBibFieldError - - field = '[n]' in field and field[:-3] or field - key = subfield and "%s.%s" % (field, subfield) or field - - if min_value == 0: # (0,1), (0,'n'), (0,n) - if not max_value: - raise error("Minimun value = 0 and no max value for '%s'" % (key,)) - if key in record: - value = record[key] - if max_value == 1 and isinstance(value, list) and len(value) != 1: - raise error("Field '%s' is not repeatable" % (key,)) - elif max_value != 'n': - if isinstance(value, list) and len(value) > max_value: - raise error("Field '%s' is repeatable only %s times" % (key, max_value)) - elif min_value == 1: # (1,-) (1,'n'), (1, n) - if not key in record: - raise error("Field '%s' is mandatory" % (key,)) - value = record[key] - if not value: - raise error("Field '%s' is mandatory" % (key,)) - if not max_value: - if isinstance(value, list) and len(value) != 1: - raise error("Field '%s' is mandatory and not repeatable" % (key,)) - elif max_value != 'n': - if isinstance(value, list) and len(value) > max_value: - raise error("Field '%s' is mandatory and repeatable only %s times" % (key, max_value)) - else: - if not key in record: - raise error("Field '%s' must be present inside the record %s times" % (key, min_value)) - value = record[key] - if not value: - raise error("Field '%s' must be present inside the record %s times" % (key, min_value)) - if not max_value: - if not isinstance(value, list) or len(value) != min_value: - raise error("Field '%s' must be present inside the record %s times" % (key, min_value)) - else: - if max_value != 'n' and (not isinstance(value, list) or len(value) < min_value or len(value) > max_value): - raise error("Field '%s' must be present inside the record between %s and %s times" % (key, min_value, max_value)) - elif not isinstance(value, list) or len(value) < min_value: - raise error("Field '%s' must be present inside the record between %s and 'n' times" % (key, min_value)) diff --git a/invenio/legacy/bibfield/functions/check_field_type.py b/invenio/legacy/bibfield/functions/check_field_type.py deleted file mode 100644 index e7eac5803..000000000 --- a/invenio/legacy/bibfield/functions/check_field_type.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -from werkzeug.utils import import_string -from invenio.utils.datastructures import LaziestDict - -CFG_BIBFIELD_TYPES = LaziestDict(lambda key: import_string('invenio.legacy.bibfield.functions.%s:%s' % (key, key))) - - -def check_field_type(record, field, field_type, subfield=None, continuable=True): - """ - Checks if record[field.subfield] is of type "field_type" - - @note: If record[field.subfield] is a list or a dictionary then it checks - every single element inside is type is a "system type" - - @param record: BibFieldDict where the record is stored - @param field: Main json ID or field name to make test on - @param field_type: Field_Type defined by the user inside bibfield_types or a system type - i.e.: "datetime.datetime" - @param subfield: If this parameter is present, instead of applying the checker - to the field, it is applied to record['field.subfield'] - - """ - field = '[n]' in field and field[:-3] or field - key = subfield and "%s.%s" % (field, subfield) or field - if not key in record: - return - - from invenio.legacy.bibfield.bibfield_utils import InvenioBibFieldContinuableError, \ - InvenioBibFieldError - - error = continuable and InvenioBibFieldContinuableError or InvenioBibFieldError - - new_type = 'is_type_%s' % (field_type, ) - - if new_type in CFG_BIBFIELD_TYPES: - globals()[new_type] = CFG_BIBFIELD_TYPES[new_type] - if not eval('%s(record[key])' % (new_type,)): - raise error("Field %s should be of type '%s'" % (key, field_type)) - else: - if not check_field_sys_type(record[key], field_type): - raise error("Field %s should be of type '%s'" % (key, field_type)) - - -def check_field_sys_type(value, field_type): - """ - Helper function to check if value is of field_type - """ - if isinstance(value, list): - for element in value: - if not check_field_sys_type(element, field_type): - return False - elif isinstance(value, dict): - for element in value.itervalues(): - if not check_field_sys_type(element, field_type): - return False - elif value: - new_type = field_type.split('.')[0] - globals()[new_type] = __import__(new_type) - if not isinstance(value, eval(field_type)): - return False - return True diff --git a/invenio/legacy/bibfield/functions/get_creation_date.py b/invenio/legacy/bibfield/functions/get_creation_date.py deleted file mode 100644 index 8929008fc..000000000 --- a/invenio/legacy/bibfield/functions/get_creation_date.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - - -def get_creation_date(recid): - """ - Returns creation date for given record. - - @param recid: - - @return: Creation date - """ - from invenio.modules.record_editor.models import Bibrec - return Bibrec.query.get(recid).creation_date diff --git a/invenio/legacy/bibfield/functions/get_doi.py b/invenio/legacy/bibfield/functions/get_doi.py deleted file mode 100644 index a67c1152a..000000000 --- a/invenio/legacy/bibfield/functions/get_doi.py +++ /dev/null @@ -1,18 +0,0 @@ -## 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. - - -def get_doi(dic): - if dic.get('2') in ['doi', 'DOI', '']: - return dic.get('a') diff --git a/invenio/legacy/bibfield/functions/get_files_from_bibdoc.py b/invenio/legacy/bibfield/functions/get_files_from_bibdoc.py deleted file mode 100644 index f4e84088f..000000000 --- a/invenio/legacy/bibfield/functions/get_files_from_bibdoc.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -def get_files_from_bibdoc(recid): - """ - Retrieves using BibDoc all the files related with a given record - - @param recid - - @return List of dictionaries containing all the information stored - inside BibDoc if the current record has files attached, the - empty list otherwise - """ - if not recid or recid < 0: - return [] - - from invenio.legacy.bibdocfile.api import BibRecDocs, InvenioBibDocFileError - files = [] - try: - bibrecdocs = BibRecDocs(int(recid)) - except InvenioBibDocFileError: - return [] - latest_files = bibrecdocs.list_latest_files() - for afile in latest_files: - file_dict = {} - file_dict['comment'] = afile.get_comment() - file_dict['description'] = afile.get_description() - file_dict['eformat'] = afile.get_format() - file_dict['full_name'] = afile.get_full_name() - file_dict['full_path'] = afile.get_full_path() - file_dict['magic'] = afile.get_magic() - file_dict['name'] = afile.get_name() - file_dict['path'] = afile.get_path() - file_dict['size'] = afile.get_size() - file_dict['status'] = afile.get_status() - file_dict['subformat'] = afile.get_subformat() - file_dict['superformat'] = afile.get_superformat() - file_dict['type'] = afile.get_type() - file_dict['url'] = afile.get_url() - file_dict['version'] = afile.get_version() - files.append(file_dict) - return files diff --git a/invenio/legacy/bibfield/functions/get_filetypes.py b/invenio/legacy/bibfield/functions/get_filetypes.py deleted file mode 100644 index 8a5b83a53..000000000 --- a/invenio/legacy/bibfield/functions/get_filetypes.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -from invenio.legacy.bibdocfile.api import BibRecDocs - - -def get_filetypes(recid): - """ - Returns filetypes extensions associated with given record. - - Takes as a parameter the recid of a record. - @param url_field: recid of a record - """ - docs = BibRecDocs(recid) - return [_get_filetype(d.format) for d in docs.list_latest_files()] - - -def _get_filetype(pre_ext): - ext = pre_ext.split(";")[0] - return ext[1:] diff --git a/invenio/legacy/bibfield/functions/get_number_of_copies.py b/invenio/legacy/bibfield/functions/get_number_of_copies.py deleted file mode 100644 index 195c109e3..000000000 --- a/invenio/legacy/bibfield/functions/get_number_of_copies.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011 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. - - -def get_number_of_copies(recid): - """ - Searches inside crcITEM for the number of appearances of recid - - @param recid: - - @return: Number of copies - """ - from invenio.legacy.dbquery import run_sql - if recid: - try: - return run_sql('SELECT COUNT(*) FROM crcITEM WHERE id_bibrec=%s', (recid,))[0][0] - except: - return -1 diff --git a/invenio/legacy/bibfield/functions/get_number_of_reviews.py b/invenio/legacy/bibfield/functions/get_number_of_reviews.py deleted file mode 100644 index ce4481995..000000000 --- a/invenio/legacy/bibfield/functions/get_number_of_reviews.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - - -def get_number_of_reviews(recid): - """ - Returns number of reviews for given record. - - @param recid: - - @return: Number of reviews - """ - from invenio.legacy.webcomment.adminlib import get_nb_reviews - if recid: - return get_nb_reviews(recid) diff --git a/invenio/legacy/bibfield/functions/get_persistent_identifiers_keys.py b/invenio/legacy/bibfield/functions/get_persistent_identifiers_keys.py deleted file mode 100644 index 83f0053bb..000000000 --- a/invenio/legacy/bibfield/functions/get_persistent_identifiers_keys.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - - -def get_persistent_identifiers_keys(keys): - """ - Acording with @persistent_identifier it recollects all the fields that - could be considered as persistent identifiers - """ - from invenio.core.record.definitions import field_definitions - - def smart_set_element(the_list, index, value): - try: - the_list[index] = value - except IndexError: - for i in xrange(len(the_list), index+1): - the_list.append(None) - the_list[index] = value - - tmp = [] - for key in keys: - try: - if not field_definitions[key]['persistent_identifier'] is None: - smart_set_element(tmp, field_definitions[key]['persistent_identifier'], key) - except TypeError: - # Work arround for [0] and [n] - for kkey in field_definitions[key]: - if field_definitions[kkey]['persistent_identifier']: - smart_set_element(tmp, field_definitions[key]['persistent_identifier'], key) - except: - continue - - return filter(None, tmp) diff --git a/invenio/legacy/bibfield/functions/is_type_isbn.py b/invenio/legacy/bibfield/functions/is_type_isbn.py deleted file mode 100644 index 67df41ef1..000000000 --- a/invenio/legacy/bibfield/functions/is_type_isbn.py +++ /dev/null @@ -1,60 +0,0 @@ -## This file is part of Invenio. -## Copyright (C) 2013 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. - - -def _convert_x_to_10(x): - if x != 'X': - return int(x) - else: - return 10 - - -def is_type_isbn10(val): - """ - Test if argument is an ISBN-10 number - - Courtesy Wikipedia: - http://en.wikipedia.org/wiki/International_Standard_Book_Number - """ - val = val.replace("-", "").replace(" ", "") - if len(val) != 10: - return False - r = sum([(10 - i) * (_convert_x_to_10(x)) for i, x in enumerate(val)]) - return not (r % 11) - - -def is_type_isbn13(val): - """ - Test if argument is an ISBN-13 number - - Courtesy Wikipedia: - http://en.wikipedia.org/wiki/International_Standard_Book_Number - """ - val = val.replace("-", "").replace(" ", "") - if len(val) != 13: - return False - total = sum([int(num) * weight for num, weight in zip(val, (1, 3) * 6)]) - ck = (10 - total) % 10 - return ck == int(val[-1]) - - -def is_type_isbn(val): - """ Test if argument is an ISBN-10 or ISBN-13 number """ - try: - return is_type_isbn10(val) or is_type_isbn13(val) - except: - return False diff --git a/invenio/legacy/bibfield/functions/is_type_isbn_issn_unit_tests.py b/invenio/legacy/bibfield/functions/is_type_isbn_issn_unit_tests.py deleted file mode 100644 index 9f9763de1..000000000 --- a/invenio/legacy/bibfield/functions/is_type_isbn_issn_unit_tests.py +++ /dev/null @@ -1,74 +0,0 @@ -## This file is part of Invenio. -## Copyright (C) 2012, 2013 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. - -from invenio.base.wrappers import lazy_import -from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase - -is_type_isbn10 = lazy_import('invenio.legacy.bibfield.functions.is_type_isbn:is_type_isbn10') -is_type_isbn13 = lazy_import('invenio.legacy.bibfield.functions.is_type_isbn:is_type_isbn13') -is_type_isbn = lazy_import('invenio.legacy.bibfield.functions.is_type_isbn:is_type_isbn') -is_type_issn = lazy_import('invenio.legacy.bibfield.functions.is_type_issn:is_type_issn') - - -class ISBNISSNValidationTest(InvenioTestCase): - valid_isbn10 = ['99921-58-10-7', '9971-5-0210-0', '960-425-059-0', - '80-902734-1-6', '85-359-0277-5', '1-84356-028-3', - '0-684-84328-5', '0-8044-2957-X', '0-85131-041-9', - '0-943396-04-2', '0-9752298-0-X', '9814401196', - '1847922570', ] - valid_isbn13 = ["978-158488-540-5", "9781576754993", - "9789814401197", "9781847922571", "978-0307886279"] - invalid_isbn = ["1-58488-540-5", "1-8758488-540-8", - "975-0307886279", "978-0307886274", "0307886272"] - - valid_issn = ['0378-5955', '00280836', '0369 3392', '1523-388X', - '0065 132X'] - invalid_issn = ['14317732', '0028-0838'] - - def _test_isbn_func(self, func, tests): - for isbns, expected in tests: - for n in isbns: - if expected: - msg = "%s did not validate with %s" % (n, func.__name__) - else: - msg = "%s was not expected to validate with %s" % ( - n, func.__name__) - self.assertEqual(func(n), expected, msg) - - def test_isbn10(self): - tests = [(self.valid_isbn10, True), (self.valid_isbn13, - False), (self.invalid_isbn, False), ] - self._test_isbn_func(is_type_isbn10, tests) - - def test_isbn13(self): - tests = [(self.valid_isbn10, False), (self.valid_isbn13, - True), (self.invalid_isbn, False), ] - self._test_isbn_func(is_type_isbn13, tests) - - def test_isbn(self): - tests = [(self.valid_isbn10, True), (self.valid_isbn13, - True), (self.invalid_isbn, False), ] - self._test_isbn_func(is_type_isbn, tests) - - def test_issn(self): - tests = [(self.valid_issn, True), (self.invalid_issn, False), ] - self._test_isbn_func(is_type_issn, tests) - -TEST_SUITE = make_test_suite(ISBNISSNValidationTest,) - -if __name__ == "__main__": - run_test_suite(TEST_SUITE) diff --git a/invenio/legacy/bibfield/functions/is_type_issn.py b/invenio/legacy/bibfield/functions/is_type_issn.py deleted file mode 100644 index a774fc091..000000000 --- a/invenio/legacy/bibfield/functions/is_type_issn.py +++ /dev/null @@ -1,32 +0,0 @@ -## This file is part of Invenio. -## Copyright (C) 2013 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. - - -def _convert_x_to_10(x): - if x != 'X': - return int(x) - else: - return 10 - - -def is_type_issn(val): - """ Test if argument is an ISSN number """ - val = val.replace("-", "").replace(" ", "") - if len(val) != 8: - return False - r = sum([(8 - i) * (_convert_x_to_10(x)) for i, x in enumerate(val)]) - return not (r % 11) diff --git a/invenio/legacy/bibfield/functions/is_type_num.py b/invenio/legacy/bibfield/functions/is_type_num.py deleted file mode 100644 index d931df52a..000000000 --- a/invenio/legacy/bibfield/functions/is_type_num.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011 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. - - -def is_type_num(field): - if isinstance(field, list): - for value in field: - if not is_type_num(value): - return False - elif isinstance(field, dict): - for value in field.itervalues(): - if not is_type_num(value): - return False - elif field: - try: - int(field) - except: - return False - return True diff --git a/invenio/legacy/bibfield/functions/is_type_str.py b/invenio/legacy/bibfield/functions/is_type_str.py deleted file mode 100644 index 71ca87844..000000000 --- a/invenio/legacy/bibfield/functions/is_type_str.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011 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. - - -def is_type_str(field): - if isinstance(field, list): - for value in field: - if not is_type_str(value): - return False - elif isinstance(field, dict): - for value in field.itervalues(): - if not is_type_str(value): - return False - elif field and not isinstance(field, str): - return False - return True diff --git a/invenio/legacy/bibfield/functions/list_tags.py b/invenio/legacy/bibfield/functions/list_tags.py deleted file mode 100644 index 61d402622..000000000 --- a/invenio/legacy/bibfield/functions/list_tags.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. - -from flask.ext.login import current_user -from invenio.webtag_queries import tags_for_html_brief - -def tag_list(recid): - """ - @param recid: Id of document - @return List of tags atached to this document visible by current user - """ - - if recid: - tags = tags_for_html_brief(recid, current_user.get_id()).all() - response = [] - for tag in tags: - response.append(tag.serializable_fields({'name'})) - - return response diff --git a/invenio/legacy/bibindex/engine.py b/invenio/legacy/bibindex/engine.py index a07d499a5..bf115989f 100644 --- a/invenio/legacy/bibindex/engine.py +++ b/invenio/legacy/bibindex/engine.py @@ -1,1984 +1,1984 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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. """ BibIndex indexing engine implementation. See bibindex executable for entry point. """ __revision__ = "$Id$" import re import sys import time import fnmatch from datetime import datetime from time import strptime from invenio.config import CFG_SOLR_URL from invenio.legacy.bibindex.engine_config import CFG_MAX_MYSQL_THREADS, \ CFG_MYSQL_THREAD_TIMEOUT, \ CFG_CHECK_MYSQL_THREADS, \ CFG_BIBINDEX_COLUMN_VALUE_SEPARATOR, \ CFG_BIBINDEX_INDEX_TABLE_TYPE, \ CFG_BIBINDEX_ADDING_RECORDS_STARTED_STR, \ CFG_BIBINDEX_UPDATE_MESSAGE from invenio.legacy.bibauthority.config import \ CFG_BIBAUTHORITY_CONTROLLED_FIELDS_BIBLIOGRAPHIC, \ CFG_BIBAUTHORITY_RECORD_CONTROL_NUMBER_FIELD from invenio.legacy.bibauthority.engine import get_index_strings_by_control_no,\ get_control_nos_from_recID from invenio.legacy.bibindex.adminlib import get_idx_remove_html_markup, \ get_idx_remove_latex_markup, \ get_idx_remove_stopwords from invenio.legacy.bibdocfile.api import BibRecDocs from invenio.legacy.search_engine import perform_request_search, \ get_index_stemming_language, \ get_synonym_terms, \ search_pattern, \ search_unit_in_bibrec from invenio.legacy.dbquery import run_sql, DatabaseError, serialize_via_marshal, \ deserialize_via_marshal, wash_table_column_name from invenio.legacy.bibindex.engine_washer import wash_index_term from invenio.legacy.bibsched.bibtask import task_init, write_message, get_datetime, \ task_set_option, task_get_option, task_get_task_param, \ task_update_progress, task_sleep_now_if_required from intbitset import intbitset from invenio.ext.logging import register_exception from invenio.legacy.bibrank.adminlib import get_def_name from invenio.legacy.miscutil.solrutils_bibindex_indexer import solr_commit from invenio.modules.indexer.tokenizers.BibIndexJournalTokenizer import \ CFG_JOURNAL_TAG, \ CFG_JOURNAL_PUBINFO_STANDARD_FORM, \ CFG_JOURNAL_PUBINFO_STANDARD_FORM_REGEXP_CHECK from invenio.legacy.bibindex.engine_utils import load_tokenizers, \ get_all_index_names_and_column_values, \ get_idx_indexer, \ get_index_tags, \ get_field_tags, \ get_tag_indexes, \ get_all_indexes, \ get_all_virtual_indexes, \ get_index_virtual_indexes, \ is_index_virtual, \ get_virtual_index_building_blocks, \ get_index_id_from_index_name, \ get_index_name_from_index_id, \ run_sql_drop_silently, \ get_min_last_updated, \ remove_inexistent_indexes from invenio.legacy.bibrecord import get_fieldvalues -from invenio.legacy.bibfield import get_record +from invenio.modules.records.api import get_record from invenio.utils.memoise import Memoise if sys.hexversion < 0x2040000: # pylint: disable=W0622 from sets import Set as set # pylint: enable=W0622 ## precompile some often-used regexp for speed reasons: re_subfields = re.compile('\$\$\w') re_datetime_shift = re.compile("([-\+]{0,1})([\d]+)([dhms])") nb_char_in_line = 50 # for verbose pretty printing chunksize = 1000 # default size of chunks that the records will be treated by base_process_size = 4500 # process base size _last_word_table = None _TOKENIZERS = load_tokenizers() def list_union(list1, list2): "Returns union of the two lists." union_dict = {} for e in list1: union_dict[e] = 1 for e in list2: union_dict[e] = 1 return union_dict.keys() def list_unique(_list): """Returns a _list with duplicates removed.""" _dict = {} for e in _list: _dict[e] = 1 return _dict.keys() ## safety function for killing slow DB threads: def kill_sleepy_mysql_threads(max_threads=CFG_MAX_MYSQL_THREADS, thread_timeout=CFG_MYSQL_THREAD_TIMEOUT): """Check the number of DB threads and if there are more than MAX_THREADS of them, lill all threads that are in a sleeping state for more than THREAD_TIMEOUT seconds. (This is useful for working around the the max_connection problem that appears during indexation in some not-yet-understood cases.) If some threads are to be killed, write info into the log file. """ res = run_sql("SHOW FULL PROCESSLIST") if len(res) > max_threads: for row in res: r_id, dummy, dummy, dummy, r_command, r_time, dummy, dummy = row if r_command == "Sleep" and int(r_time) > thread_timeout: run_sql("KILL %s", (r_id,)) write_message("WARNING: too many DB threads, killing thread %s" % r_id, verbose=1) return def get_associated_subfield_value(recID, tag, value, associated_subfield_code): """Return list of ASSOCIATED_SUBFIELD_CODE, if exists, for record RECID and TAG of value VALUE. Used by fulltext indexer only. Note: TAG must be 6 characters long (tag+ind1+ind2+sfcode), otherwise en empty string is returned. FIXME: what if many tag values have the same value but different associated_subfield_code? Better use bibrecord library for this. """ out = "" if len(tag) != 6: return out bibXXx = "bib" + tag[0] + tag[1] + "x" bibrec_bibXXx = "bibrec_" + bibXXx query = """SELECT bb.field_number, b.tag, b.value FROM %s AS b, %s AS bb WHERE bb.id_bibrec=%%s AND bb.id_bibxxx=b.id AND tag LIKE %%s%%""" % (bibXXx, bibrec_bibXXx) res = run_sql(query, (recID, tag[:-1])) field_number = -1 for row in res: if row[1] == tag and row[2] == value: field_number = row[0] if field_number > 0: for row in res: if row[0] == field_number and row[1] == tag[:-1] + associated_subfield_code: out = row[2] break return out def get_author_canonical_ids_for_recid(recID): """ Return list of author canonical IDs (e.g. `J.Ellis.1') for the given record. Done by consulting BibAuthorID module. """ from invenio.legacy.bibauthorid.dbinterface import get_persons_from_recids lwords = [] res = get_persons_from_recids([recID]) if res is None: ## BibAuthorID is not enabled return lwords else: dpersons, dpersoninfos = res for aid in dpersoninfos.keys(): author_canonical_id = dpersoninfos[aid].get('canonical_id', '') if author_canonical_id: lwords.append(author_canonical_id) return lwords def swap_temporary_reindex_tables(index_id, reindex_prefix="tmp_"): """Atomically swap reindexed temporary table with the original one. Delete the now-old one.""" is_virtual = is_index_virtual(index_id) if is_virtual: write_message("Removing %s index tables for id %s" % (reindex_prefix, index_id)) query = """DROP TABLE IF EXISTS %%sidxWORD%02dR, %%sidxWORD%02dF, %%sidxPAIR%02dR, %%sidxPAIR%02dF, %%sidxPHRASE%02dR, %%sidxPHRASE%02dF """ % ((index_id,)*6) query = query % ((reindex_prefix,)*6) run_sql(query) else: write_message("Putting new tmp index tables for id %s into production" % index_id) run_sql( "RENAME TABLE " + "idxWORD%02dR TO old_idxWORD%02dR," % (index_id, index_id) + "%sidxWORD%02dR TO idxWORD%02dR," % (reindex_prefix, index_id, index_id) + "idxWORD%02dF TO old_idxWORD%02dF," % (index_id, index_id) + "%sidxWORD%02dF TO idxWORD%02dF," % (reindex_prefix, index_id, index_id) + "idxPAIR%02dR TO old_idxPAIR%02dR," % (index_id, index_id) + "%sidxPAIR%02dR TO idxPAIR%02dR," % (reindex_prefix, index_id, index_id) + "idxPAIR%02dF TO old_idxPAIR%02dF," % (index_id, index_id) + "%sidxPAIR%02dF TO idxPAIR%02dF," % (reindex_prefix, index_id, index_id) + "idxPHRASE%02dR TO old_idxPHRASE%02dR," % (index_id, index_id) + "%sidxPHRASE%02dR TO idxPHRASE%02dR," % (reindex_prefix, index_id, index_id) + "idxPHRASE%02dF TO old_idxPHRASE%02dF," % (index_id, index_id) + "%sidxPHRASE%02dF TO idxPHRASE%02dF;" % (reindex_prefix, index_id, index_id) ) write_message("Dropping old index tables for id %s" % index_id) run_sql_drop_silently("DROP TABLE old_idxWORD%02dR, old_idxWORD%02dF, old_idxPAIR%02dR, old_idxPAIR%02dF, old_idxPHRASE%02dR, old_idxPHRASE%02dF" % (index_id, index_id, index_id, index_id, index_id, index_id)) # kwalitee: disable=sql def init_temporary_reindex_tables(index_id, reindex_prefix="tmp_"): """Create reindexing temporary tables.""" write_message("Creating new tmp index tables for id %s" % index_id) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxWORD%02dF""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxWORD%02dF ( id mediumint(9) unsigned NOT NULL auto_increment, term varchar(50) default NULL, hitlist longblob, PRIMARY KEY (id), UNIQUE KEY term (term) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxWORD%02dR""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxWORD%02dR ( id_bibrec mediumint(9) unsigned NOT NULL, termlist longblob, type enum('CURRENT','FUTURE','TEMPORARY') NOT NULL default 'CURRENT', PRIMARY KEY (id_bibrec,type) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxPAIR%02dF""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxPAIR%02dF ( id mediumint(9) unsigned NOT NULL auto_increment, term varchar(100) default NULL, hitlist longblob, PRIMARY KEY (id), UNIQUE KEY term (term) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxPAIR%02dR""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxPAIR%02dR ( id_bibrec mediumint(9) unsigned NOT NULL, termlist longblob, type enum('CURRENT','FUTURE','TEMPORARY') NOT NULL default 'CURRENT', PRIMARY KEY (id_bibrec,type) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxPHRASE%02dF""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxPHRASE%02dF ( id mediumint(9) unsigned NOT NULL auto_increment, term text default NULL, hitlist longblob, PRIMARY KEY (id), KEY term (term(50)) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) run_sql_drop_silently("""DROP TABLE IF EXISTS %sidxPHRASE%02dR""" % (wash_table_column_name(reindex_prefix), index_id)) # kwalitee: disable=sql run_sql("""CREATE TABLE %sidxPHRASE%02dR ( id_bibrec mediumint(9) unsigned NOT NULL default '0', termlist longblob, type enum('CURRENT','FUTURE','TEMPORARY') NOT NULL default 'CURRENT', PRIMARY KEY (id_bibrec,type) ) ENGINE=MyISAM""" % (reindex_prefix, index_id)) def remove_subfields(s): "Removes subfields from string, e.g. 'foo $$c bar' becomes 'foo bar'." return re_subfields.sub(' ', s) def get_field_indexes(field): """Returns indexes names and ids corresponding to the given field""" if field[0:3].isdigit(): #field is actually a tag return get_tag_indexes(field, virtual=False) else: #future implemeptation for fields return [] get_field_indexes_memoised = Memoise(get_field_indexes) def get_all_synonym_knowledge_bases(): """Returns a dictionary of name key and knowledge base name and match type tuple value information of all defined words indexes that have knowledge base information. Returns empty dictionary in case there are no tags indexed. Example: output['global'] = ('INDEX-SYNONYM-TITLE', 'exact'), output['title'] = ('INDEX-SYNONYM-TITLE', 'exact').""" res = get_all_index_names_and_column_values("synonym_kbrs") out = {} for row in res: kb_data = row[1] # ignore empty strings if len(kb_data): out[row[0]] = tuple(kb_data.split(CFG_BIBINDEX_COLUMN_VALUE_SEPARATOR)) return out def get_index_remove_stopwords(index_id): """Returns value of a remove_stopword field from idxINDEX database table if it's not 'No'. If it's 'No' returns False. Just for consistency with WordTable. @param index_id: id of the index """ result = get_idx_remove_stopwords(index_id) if isinstance(result, tuple): return False if result == 'No' or result == '': return False return result def get_index_remove_html_markup(index_id): """ Gets remove_html_markup parameter from database ('Yes' or 'No') and changes it to True, False. Just for consistency with WordTable.""" result = get_idx_remove_html_markup(index_id) if result == 'Yes': return True return False def get_index_remove_latex_markup(index_id): """ Gets remove_latex_markup parameter from database ('Yes' or 'No') and changes it to True, False. Just for consistency with WordTable.""" result = get_idx_remove_latex_markup(index_id) if result == 'Yes': return True return False def get_index_tokenizer(index_id): """Returns value of a tokenizer field from idxINDEX database table @param index_id: id of the index """ query = "SELECT tokenizer FROM idxINDEX WHERE id=%s" % index_id out = None try: res = run_sql(query) if res: out = _TOKENIZERS[res[0][0]] except DatabaseError: write_message("Exception caught for SQL statement: %s; column tokenizer might not exist" % query, sys.stderr) except KeyError: write_message("Exception caught: there is no such tokenizer") out = None return out def get_last_updated_all_indexes(): """Returns last modification date for all defined indexes""" query= """SELECT name, last_updated FROM idxINDEX""" res = run_sql(query) return res def split_ranges(parse_string): """Parse a string a return the list or ranges.""" recIDs = [] ranges = parse_string.split(",") for arange in ranges: tmp_recIDs = arange.split("-") if len(tmp_recIDs) == 1: recIDs.append([int(tmp_recIDs[0]), int(tmp_recIDs[0])]) else: if int(tmp_recIDs[0]) > int(tmp_recIDs[1]): # sanity check tmp = tmp_recIDs[0] tmp_recIDs[0] = tmp_recIDs[1] tmp_recIDs[1] = tmp recIDs.append([int(tmp_recIDs[0]), int(tmp_recIDs[1])]) return recIDs def get_word_tables(tables): """ Given a list of table names it return a list of tuples (index_id, index_name, index_tags). """ wordTables = [] if tables: for index in tables: index_id = get_index_id_from_index_name(index) if index_id: wordTables.append((index_id, index, get_index_tags(index))) else: write_message("Error: There is no %s words table." % index, sys.stderr) return wordTables def get_date_range(var): "Returns the two dates contained as a low,high tuple" limits = var.split(",") if len(limits) == 1: low = get_datetime(limits[0]) return low, None if len(limits) == 2: low = get_datetime(limits[0]) high = get_datetime(limits[1]) return low, high return None, None def create_range_list(res): """Creates a range list from a recID select query result contained in res. The result is expected to have ascending numerical order.""" if not res: return [] row = res[0] if not row: return [] else: range_list = [[row, row]] for row in res[1:]: row_id = row if row_id == range_list[-1][1] + 1: range_list[-1][1] = row_id else: range_list.append([row_id, row_id]) return range_list def beautify_range_list(range_list): """Returns a non overlapping, maximal range list""" ret_list = [] for new in range_list: found = 0 for old in ret_list: if new[0] <= old[0] <= new[1] + 1 or new[0] - 1 <= old[1] <= new[1]: old[0] = min(old[0], new[0]) old[1] = max(old[1], new[1]) found = 1 break if not found: ret_list.append(new) return ret_list def truncate_index_table(index_name): """Properly truncate the given index.""" index_id = get_index_id_from_index_name(index_name) if index_id: write_message('Truncating %s index table in order to reindex.' % index_name, verbose=2) run_sql("UPDATE idxINDEX SET last_updated='0000-00-00 00:00:00' WHERE id=%s", (index_id,)) run_sql("TRUNCATE idxWORD%02dF" % index_id) # kwalitee: disable=sql run_sql("TRUNCATE idxWORD%02dR" % index_id) # kwalitee: disable=sql run_sql("TRUNCATE idxPHRASE%02dF" % index_id) # kwalitee: disable=sql run_sql("TRUNCATE idxPHRASE%02dR" % index_id) # kwalitee: disable=sql def update_index_last_updated(indexes, starting_time=None): """Update last_updated column of the index table in the database. Puts starting time there so that if the task was interrupted for record download, the records will be reindexed next time. @param indexes: list of indexes names """ if starting_time is None: return None for index_name in indexes: write_message("updating last_updated to %s...for %s index" % (starting_time, index_name), verbose=9) run_sql("UPDATE idxINDEX SET last_updated=%s WHERE name=%s", (starting_time, index_name,)) def get_percentage_completed(num_done, num_total): """ Return a string containing the approx. percentage completed """ percentage_remaining = 100.0 * float(num_done) / float(num_total) if percentage_remaining: percentage_display = "(%.1f%%)" % (percentage_remaining,) else: percentage_display = "" return percentage_display def _fill_dict_of_indexes_with_empty_sets(): """find_affected_records internal function. Creates dict: {'index_name1':set([]), ...} """ index_dict = {} tmp_all_indexes = get_all_indexes(virtual=False) for index in tmp_all_indexes: index_dict[index] = set([]) return index_dict def find_affected_records_for_index(indexes=[], recIDs=[], force_all_indexes=False): """ Function checks which records need to be changed/reindexed for given index/indexes. Makes use of hstRECORD table where different revisions of record are kept. If parameter force_all_indexes is set function will assign all recIDs to all indexes. @param indexes: names of indexes for reindexation separated by coma @param recIDs: recIDs for reindexation in form: [[range1_down, range1_up],[range2_down, range2_up]..] @param force_all_indexes: should we index all indexes? """ tmp_dates = dict(get_last_updated_all_indexes()) modification_dates = dict([(date, tmp_dates[date] or datetime(1000,1,1,1,1,1)) for date in tmp_dates]) tmp_all_indexes = get_all_indexes(virtual=False) indexes = remove_inexistent_indexes(indexes, leave_virtual=False) if not indexes: return {} def _should_reindex_for_revision(index_name, revision_date): try: if modification_dates[index_name] < revision_date and index_name in indexes: return True return False except KeyError: return False if force_all_indexes: records_for_indexes = {} all_recIDs = [] for recIDs_range in recIDs: all_recIDs.extend(range(recIDs_range[0], recIDs_range[1]+1)) for index in indexes: records_for_indexes[index] = all_recIDs return records_for_indexes min_last_updated = get_min_last_updated(indexes)[0][0] or datetime(1000,1,1,1,1,1) indexes_to_change = _fill_dict_of_indexes_with_empty_sets() recIDs_info = [] for recIDs_range in recIDs: query = """SELECT id_bibrec,job_date,affected_fields FROM hstRECORD WHERE id_bibrec BETWEEN %s AND %s AND job_date > '%s'""" % (recIDs_range[0], recIDs_range[1], min_last_updated) res = run_sql(query) if res: recIDs_info.extend(res) for recID_info in recIDs_info: recID, revision, affected_fields = recID_info affected_fields = affected_fields.split(",") indexes_for_recID = set() for field in affected_fields: if field: field_indexes = get_field_indexes_memoised(field) or [] indexes_names = set([idx[1] for idx in field_indexes]) indexes_for_recID |= indexes_names else: #record was inserted, all fields were changed, no specific affected fields indexes_for_recID |= set(tmp_all_indexes) indexes_for_recID_filtered = [ind for ind in indexes_for_recID if _should_reindex_for_revision(ind, revision)] for index in indexes_for_recID_filtered: indexes_to_change[index].add(recID) indexes_to_change = dict((k, list(sorted(v))) for k, v in indexes_to_change.iteritems() if v) return indexes_to_change #def update_text_extraction_date(first_recid, last_recid): #"""for all the bibdoc connected to the specified recid, set #the text_extraction_date to the task_starting_time.""" #run_sql("UPDATE bibdoc JOIN bibrec_bibdoc ON id=id_bibdoc SET text_extraction_date=%s WHERE id_bibrec BETWEEN %s AND %s", (task_get_task_param('task_starting_time'), first_recid, last_recid)) class WordTable: "A class to hold the words table." def __init__(self, index_name, index_id, fields_to_index, table_name_pattern, wordtable_type, tag_to_tokenizer_map, wash_index_terms=50): """Creates words table instance. @param index_name: the index name @param index_id: the index integer identificator @param fields_to_index: a list of fields to index @param table_name_pattern: i.e. idxWORD%02dF or idxPHRASE%02dF @parm wordtable_type: type of the wordtable: Words, Pairs, Phrases @param tag_to_tokenizer_map: a mapping to specify particular tokenizer to extract words from particular metdata (such as 8564_u) @param wash_index_terms: do we wash index terms, and if yes (when >0), how many characters do we keep in the index terms; see max_char_length parameter of wash_index_term() """ self.index_name = index_name self.index_id = index_id self.tablename = table_name_pattern % index_id self.virtual_tablename_pattern = table_name_pattern[table_name_pattern.find('idx'):-1] self.humanname = get_def_name('%s' % (str(index_id),), "idxINDEX")[0][1] self.recIDs_in_mem = [] self.fields_to_index = fields_to_index self.value = {} try: self.stemming_language = get_index_stemming_language(index_id) except KeyError: self.stemming_language = '' self.remove_stopwords = get_index_remove_stopwords(index_id) self.remove_html_markup = get_index_remove_html_markup(index_id) self.remove_latex_markup = get_index_remove_latex_markup(index_id) self.tokenizer = get_index_tokenizer(index_id)(self.stemming_language, self.remove_stopwords, self.remove_html_markup, self.remove_latex_markup) self.default_tokenizer_function = self.tokenizer.get_tokenizing_function(wordtable_type) self.wash_index_terms = wash_index_terms self.is_virtual = is_index_virtual(self.index_id) self.virtual_indexes = get_index_virtual_indexes(self.index_id) # tagToTokenizer mapping. It offers an indirection level necessary for # indexing fulltext. self.tag_to_words_fnc_map = {} for k in tag_to_tokenizer_map.keys(): special_tokenizer_for_tag = _TOKENIZERS[tag_to_tokenizer_map[k]](self.stemming_language, self.remove_stopwords, self.remove_html_markup, self.remove_latex_markup) special_tokenizer_function = special_tokenizer_for_tag.get_tokenizing_function(wordtable_type) self.tag_to_words_fnc_map[k] = special_tokenizer_function if self.stemming_language and self.tablename.startswith('idxWORD'): write_message('%s has stemming enabled, language %s' % (self.tablename, self.stemming_language)) def turn_off_virtual_indexes(self): self.virtual_indexes = [] def turn_on_virtual_indexes(self): self.virtual_indexes = get_index_virtual_indexes(self.index_id) def get_field(self, recID, tag): """Returns list of values of the MARC-21 'tag' fields for the record 'recID'.""" out = [] bibXXx = "bib" + tag[0] + tag[1] + "x" bibrec_bibXXx = "bibrec_" + bibXXx query = """SELECT value FROM %s AS b, %s AS bb WHERE bb.id_bibrec=%%s AND bb.id_bibxxx=b.id AND tag LIKE %%s""" % (bibXXx, bibrec_bibXXx) res = run_sql(query, (recID, tag)) for row in res: out.append(row[0]) return out def clean(self): "Cleans the words table." self.value = {} def put_into_db(self, mode="normal"): """Updates the current words table in the corresponding DB idxFOO table. Mode 'normal' means normal execution, mode 'emergency' means words index reverting to old state. """ write_message("%s %s wordtable flush started" % (self.tablename, mode)) write_message('...updating %d words into %s started' % \ (len(self.value), self.tablename)) task_update_progress("(%s:%s) flushed %d/%d words" % (self.tablename, self.humanname, 0, len(self.value))) self.recIDs_in_mem = beautify_range_list(self.recIDs_in_mem) all_indexes = [(self.index_id, self.humanname)] if self.virtual_indexes: all_indexes.extend(self.virtual_indexes) for ind_id, ind_name in all_indexes: tab_name = self.tablename[:-1] + "R" if ind_id != self.index_id: tab_name = self.virtual_tablename_pattern % ind_id + "R" if mode == "normal": for group in self.recIDs_in_mem: query = """UPDATE %s SET type='TEMPORARY' WHERE id_bibrec BETWEEN %%s AND %%s AND type='CURRENT'""" % tab_name write_message(query % (group[0], group[1]), verbose=9) run_sql(query, (group[0], group[1])) nb_words_total = len(self.value) nb_words_report = int(nb_words_total / 10.0) nb_words_done = 0 for word in self.value.keys(): self.put_word_into_db(word, ind_id) nb_words_done += 1 if nb_words_report != 0 and ((nb_words_done % nb_words_report) == 0): write_message('......processed %d/%d words' % (nb_words_done, nb_words_total)) percentage_display = get_percentage_completed(nb_words_done, nb_words_total) task_update_progress("(%s:%s) flushed %d/%d words %s" % (tab_name, ind_name, nb_words_done, nb_words_total, percentage_display)) write_message('...updating %d words into %s ended' % \ (nb_words_total, tab_name)) write_message('...updating reverse table %s started' % tab_name) if mode == "normal": for group in self.recIDs_in_mem: query = """UPDATE %s SET type='CURRENT' WHERE id_bibrec BETWEEN %%s AND %%s AND type='FUTURE'""" % tab_name write_message(query % (group[0], group[1]), verbose=9) run_sql(query, (group[0], group[1])) query = """DELETE FROM %s WHERE id_bibrec BETWEEN %%s AND %%s AND type='TEMPORARY'""" % tab_name write_message(query % (group[0], group[1]), verbose=9) run_sql(query, (group[0], group[1])) #if self.is_fulltext_index: #update_text_extraction_date(group[0], group[1]) write_message('End of updating wordTable into %s' % tab_name, verbose=9) elif mode == "emergency": for group in self.recIDs_in_mem: query = """UPDATE %s SET type='CURRENT' WHERE id_bibrec BETWEEN %%s AND %%s AND type='TEMPORARY'""" % tab_name write_message(query % (group[0], group[1]), verbose=9) run_sql(query, (group[0], group[1])) query = """DELETE FROM %s WHERE id_bibrec BETWEEN %%s AND %%s AND type='FUTURE'""" % tab_name write_message(query % (group[0], group[1]), verbose=9) run_sql(query, (group[0], group[1])) write_message('End of emergency flushing wordTable into %s' % tab_name, verbose=9) write_message('...updating reverse table %s ended' % tab_name) self.clean() self.recIDs_in_mem = [] write_message("%s %s wordtable flush ended" % (self.tablename, mode)) task_update_progress("(%s:%s) flush ended" % (self.tablename, self.humanname)) def load_old_recIDs(self, word, index_id=None): """Load existing hitlist for the word from the database index files.""" tab_name = self.tablename if index_id != self.index_id: tab_name = self.virtual_tablename_pattern % index_id + "F" query = "SELECT hitlist FROM %s WHERE term=%%s" % tab_name res = run_sql(query, (word,)) if res: return intbitset(res[0][0]) else: return None def merge_with_old_recIDs(self, word, set): """Merge the system numbers stored in memory (hash of recIDs with value +1 or -1 according to whether to add/delete them) with those stored in the database index and received in set universe of recIDs for the given word. Return False in case no change was done to SET, return True in case SET was changed. """ oldset = intbitset(set) set.update_with_signs(self.value[word]) return set != oldset def put_word_into_db(self, word, index_id): """Flush a single word to the database and delete it from memory""" tab_name = self.tablename if index_id != self.index_id: tab_name = self.virtual_tablename_pattern % index_id + "F" set = self.load_old_recIDs(word, index_id) if set is not None: # merge the word recIDs found in memory: if not self.merge_with_old_recIDs(word, set): # nothing to update: write_message("......... unchanged hitlist for ``%s''" % word, verbose=9) pass else: # yes there were some new words: write_message("......... updating hitlist for ``%s''" % word, verbose=9) run_sql("UPDATE %s SET hitlist=%%s WHERE term=%%s" % wash_table_column_name(tab_name), (set.fastdump(), word)) # kwalitee: disable=sql else: # the word is new, will create new set: write_message("......... inserting hitlist for ``%s''" % word, verbose=9) set = intbitset(self.value[word].keys()) try: run_sql("INSERT INTO %s (term, hitlist) VALUES (%%s, %%s)" % wash_table_column_name(tab_name), (word, set.fastdump())) # kwalitee: disable=sql except Exception, e: ## We send this exception to the admin only when is not ## already reparing the problem. register_exception(prefix="Error when putting the term '%s' into db (hitlist=%s): %s\n" % (repr(word), set, e), alert_admin=(task_get_option('cmd') != 'repair')) if not set: # never store empty words run_sql("DELETE FROM %s WHERE term=%%s" % wash_table_column_name(tab_name), (word,)) # kwalitee: disable=sql def display(self): "Displays the word table." keys = self.value.keys() keys.sort() for k in keys: write_message("%s: %s" % (k, self.value[k])) def count(self): "Returns the number of words in the table." return len(self.value) def info(self): "Prints some information on the words table." write_message("The words table contains %d words." % self.count()) def lookup_words(self, word=""): "Lookup word from the words table." if not word: done = 0 while not done: try: word = raw_input("Enter word: ") done = 1 except (EOFError, KeyboardInterrupt): return if self.value.has_key(word): write_message("The word '%s' is found %d times." \ % (word, len(self.value[word]))) else: write_message("The word '%s' does not exist in the word file."\ % word) def add_recIDs(self, recIDs, opt_flush): """Fetches records which id in the recIDs range list and adds them to the wordTable. The recIDs range list is of the form: [[i1_low,i1_high],[i2_low,i2_high], ..., [iN_low,iN_high]]. """ if self.is_virtual: return global chunksize, _last_word_table flush_count = 0 records_done = 0 records_to_go = 0 for arange in recIDs: records_to_go = records_to_go + arange[1] - arange[0] + 1 time_started = time.time() # will measure profile time for arange in recIDs: i_low = arange[0] chunksize_count = 0 while i_low <= arange[1]: task_sleep_now_if_required() # calculate chunk group of recIDs and treat it: i_high = min(i_low + opt_flush - flush_count - 1, arange[1]) i_high = min(i_low + chunksize - chunksize_count - 1, i_high) try: self.chk_recID_range(i_low, i_high) except StandardError: if self.index_name == 'fulltext' and CFG_SOLR_URL: solr_commit() raise write_message(CFG_BIBINDEX_ADDING_RECORDS_STARTED_STR % \ (self.tablename, i_low, i_high)) if CFG_CHECK_MYSQL_THREADS: kill_sleepy_mysql_threads() percentage_display = get_percentage_completed(records_done, records_to_go) task_update_progress("(%s:%s) adding recs %d-%d %s" % (self.tablename, self.humanname, i_low, i_high, percentage_display)) self.del_recID_range(i_low, i_high) just_processed = self.add_recID_range(i_low, i_high) flush_count = flush_count + i_high - i_low + 1 chunksize_count = chunksize_count + i_high - i_low + 1 records_done = records_done + just_processed write_message(CFG_BIBINDEX_ADDING_RECORDS_STARTED_STR % \ (self.tablename, i_low, i_high)) if chunksize_count >= chunksize: chunksize_count = 0 # flush if necessary: if flush_count >= opt_flush: self.put_into_db() self.clean() if self.index_name == 'fulltext' and CFG_SOLR_URL: solr_commit() write_message("%s backing up" % (self.tablename)) flush_count = 0 self.log_progress(time_started, records_done, records_to_go) # iterate: i_low = i_high + 1 if flush_count > 0: self.put_into_db() if self.index_name == 'fulltext' and CFG_SOLR_URL: solr_commit() self.log_progress(time_started, records_done, records_to_go) def add_recID_range(self, recID1, recID2): """Add records from RECID1 to RECID2.""" wlist = {} self.recIDs_in_mem.append([recID1, recID2]) # special case of author indexes where we also add author # canonical IDs: if self.index_name in ('author', 'firstauthor', 'exactauthor', 'exactfirstauthor'): for recID in range(recID1, recID2 + 1): if not wlist.has_key(recID): wlist[recID] = [] wlist[recID] = list_union(get_author_canonical_ids_for_recid(recID), wlist[recID]) if len(self.fields_to_index) == 0: #'no tag' style of indexing - use bibfield instead of directly consulting bibrec tokenizing_function = self.default_tokenizer_function for recID in range(recID1, recID2 + 1): record = get_record(recID) if record: new_words = tokenizing_function(record) if not wlist.has_key(recID): wlist[recID] = [] wlist[recID] = list_union(new_words, wlist[recID]) # case of special indexes: elif self.index_name in ('authorcount', 'journal'): for tag in self.fields_to_index: tokenizing_function = self.tag_to_words_fnc_map.get(tag, self.default_tokenizer_function) for recID in range(recID1, recID2 + 1): new_words = tokenizing_function(recID) if not wlist.has_key(recID): wlist[recID] = [] wlist[recID] = list_union(new_words, wlist[recID]) # usual tag-by-tag indexing for the rest: else: for tag in self.fields_to_index: tokenizing_function = self.tag_to_words_fnc_map.get(tag, self.default_tokenizer_function) phrases = self.get_phrases_for_tokenizing(tag, recID1, recID2) for row in sorted(phrases): recID, phrase = row if not wlist.has_key(recID): wlist[recID] = [] new_words = tokenizing_function(phrase) wlist[recID] = list_union(new_words, wlist[recID]) # lookup index-time synonyms: synonym_kbrs = get_all_synonym_knowledge_bases() if synonym_kbrs.has_key(self.index_name): if len(wlist) == 0: return 0 recIDs = wlist.keys() for recID in recIDs: for word in wlist[recID]: word_synonyms = get_synonym_terms(word, synonym_kbrs[self.index_name][0], synonym_kbrs[self.index_name][1], use_memoise=True) if word_synonyms: wlist[recID] = list_union(word_synonyms, wlist[recID]) # were there some words for these recIDs found? recIDs = wlist.keys() for recID in recIDs: # was this record marked as deleted? if "DELETED" in self.get_field(recID, "980__c"): wlist[recID] = [] write_message("... record %d was declared deleted, removing its word list" % recID, verbose=9) write_message("... record %d, termlist: %s" % (recID, wlist[recID]), verbose=9) self.index_virtual_indexes_reversed(wlist, recID1, recID2) if len(wlist) == 0: return 0 # put words into reverse index table with FUTURE status: for recID in recIDs: run_sql("INSERT INTO %sR (id_bibrec,termlist,type) VALUES (%%s,%%s,'FUTURE')" % wash_table_column_name(self.tablename[:-1]), (recID, serialize_via_marshal(wlist[recID]))) # kwalitee: disable=sql # ... and, for new records, enter the CURRENT status as empty: try: run_sql("INSERT INTO %sR (id_bibrec,termlist,type) VALUES (%%s,%%s,'CURRENT')" % wash_table_column_name(self.tablename[:-1]), (recID, serialize_via_marshal([]))) # kwalitee: disable=sql except DatabaseError: # okay, it's an already existing record, no problem pass # put words into memory word list: put = self.put for recID in recIDs: for w in wlist[recID]: put(recID, w, 1) return len(recIDs) def get_phrases_for_tokenizing(self, tag, first_recID, last_recID): """Gets phrases for later tokenization for a range of records and specific tag. @param tag: MARC tag @param first_recID: first recID from the range of recIDs to index @param last_recID: last recID from the range of recIDs to index """ bibXXx = "bib" + tag[0] + tag[1] + "x" bibrec_bibXXx = "bibrec_" + bibXXx query = """SELECT bb.id_bibrec,b.value FROM %s AS b, %s AS bb WHERE bb.id_bibrec BETWEEN %%s AND %%s AND bb.id_bibxxx=b.id AND tag LIKE %%s""" % (bibXXx, bibrec_bibXXx) phrases = run_sql(query, (first_recID, last_recID, tag)) if tag == '8564_u': ## FIXME: Quick hack to be sure that hidden files are ## actually indexed. phrases = set(phrases) for recid in xrange(int(first_recID), int(last_recID) + 1): for bibdocfile in BibRecDocs(recid).list_latest_files(): phrases.add((recid, bibdocfile.get_url())) #authority records pattern = tag.replace('%', '*') matches = fnmatch.filter(CFG_BIBAUTHORITY_CONTROLLED_FIELDS_BIBLIOGRAPHIC.keys(), pattern) if not len(matches): return phrases phrases = set(phrases) for tag_match in matches: authority_tag = tag_match[0:3] + "__0" for recID in xrange(int(first_recID), int(last_recID) + 1): control_nos = get_fieldvalues(recID, authority_tag) for control_no in control_nos: new_strings = get_index_strings_by_control_no(control_no) for string_value in new_strings: phrases.add((recID, string_value)) return phrases def index_virtual_indexes_reversed(self, wlist, recID1, recID2): """Inserts indexed words into all virtual indexes connected to this index""" #first: need to take old values from given index to remove #them from virtual indexes query = """SELECT id_bibrec, termlist FROM %sR WHERE id_bibrec BETWEEN %%s AND %%s""" % wash_table_column_name(self.tablename[:-1]) old_index_values = run_sql(query, (recID1, recID2)) if old_index_values: zipped = zip(*old_index_values) old_index_values = dict(zip(zipped[0], map(deserialize_via_marshal, zipped[1]))) else: old_index_values = dict() recIDs = wlist.keys() for vindex_id, vindex_name in self.virtual_indexes: #second: need to take old values from virtual index #to have a list of words from which we can remove old values from given index tab_name = self.virtual_tablename_pattern % vindex_id + "R" query = """SELECT id_bibrec, termlist FROM %s WHERE type='CURRENT' AND id_bibrec BETWEEN %%s AND %%s""" % tab_name old_virtual_index_values = run_sql(query, (recID1, recID2)) if old_virtual_index_values: zipped = zip(*old_virtual_index_values) old_virtual_index_values = dict(zip(zipped[0], map(deserialize_via_marshal, zipped[1]))) else: old_virtual_index_values = dict() for recID in recIDs: to_serialize = list((set(old_virtual_index_values.get(recID) or []) - set(old_index_values.get(recID) or [])) | set(wlist[recID])) run_sql("INSERT INTO %s (id_bibrec,termlist,type) VALUES (%%s,%%s,'FUTURE')" % wash_table_column_name(tab_name), (recID, serialize_via_marshal(to_serialize))) # kwalitee: disable=sql try: run_sql("INSERT INTO %s (id_bibrec,termlist,type) VALUES (%%s,%%s,'CURRENT')" % wash_table_column_name(tab_name), (recID, serialize_via_marshal([]))) # kwalitee: disable=sql except DatabaseError: pass if len(recIDs) != (recID2 - recID1 + 1): #for records in range(recID1, recID2) which weren't updated: #need to prevent them from being deleted by function: 'put_into_db' #which deletes all records with 'CURRENT' status query = """INSERT INTO %s (id_bibrec, termlist, type) SELECT id_bibrec, termlist, 'FUTURE' FROM %s WHERE id_bibrec BETWEEN %%s AND %%s AND type='CURRENT' AND id_bibrec IN ( SELECT id_bibrec FROM %s WHERE id_bibrec BETWEEN %%s AND %%s GROUP BY id_bibrec HAVING COUNT(id_bibrec) = 1 ) """ % ((wash_table_column_name(tab_name),)*3) run_sql(query, (recID1, recID2, recID1, recID2)) def log_progress(self, start, done, todo): """Calculate progress and store it. start: start time, done: records processed, todo: total number of records""" time_elapsed = time.time() - start # consistency check if time_elapsed == 0 or done > todo: return time_recs_per_min = done / (time_elapsed / 60.0) write_message("%d records took %.1f seconds to complete.(%1.f recs/min)"\ % (done, time_elapsed, time_recs_per_min)) if time_recs_per_min: write_message("Estimated runtime: %.1f minutes" % \ ((todo - done) / time_recs_per_min)) def put(self, recID, word, sign): """Adds/deletes a word to the word list.""" try: if self.wash_index_terms: word = wash_index_term(word, self.wash_index_terms) if self.value.has_key(word): # the word 'word' exist already: update sign self.value[word][recID] = sign else: self.value[word] = {recID: sign} except: write_message("Error: Cannot put word %s with sign %d for recID %s." % (word, sign, recID)) def del_recIDs(self, recIDs): """Fetches records which id in the recIDs range list and adds them to the wordTable. The recIDs range list is of the form: [[i1_low,i1_high],[i2_low,i2_high], ..., [iN_low,iN_high]]. """ count = 0 for arange in recIDs: task_sleep_now_if_required() self.del_recID_range(arange[0], arange[1]) count = count + arange[1] - arange[0] self.put_into_db() if self.index_name == 'fulltext' and CFG_SOLR_URL: solr_commit() def del_recID_range(self, low, high): """Deletes records with 'recID' system number between low and high from memory words index table.""" write_message("%s fetching existing words for records #%d-#%d started" % \ (self.tablename, low, high), verbose=3) self.recIDs_in_mem.append([low, high]) query = """SELECT id_bibrec,termlist FROM %sR as bb WHERE bb.id_bibrec BETWEEN %%s AND %%s""" % (self.tablename[:-1]) recID_rows = run_sql(query, (low, high)) for recID_row in recID_rows: recID = recID_row[0] wlist = deserialize_via_marshal(recID_row[1]) for word in wlist: self.put(recID, word, -1) write_message("%s fetching existing words for records #%d-#%d ended" % \ (self.tablename, low, high), verbose=3) def report_on_table_consistency(self): """Check reverse words index tables (e.g. idxWORD01R) for interesting states such as 'TEMPORARY' state. Prints small report (no of words, no of bad words). """ # find number of words: query = """SELECT COUNT(*) FROM %s""" % (self.tablename) res = run_sql(query, None, 1) if res: nb_words = res[0][0] else: nb_words = 0 # find number of records: query = """SELECT COUNT(DISTINCT(id_bibrec)) FROM %sR""" % (self.tablename[:-1]) res = run_sql(query, None, 1) if res: nb_records = res[0][0] else: nb_records = 0 # report stats: write_message("%s contains %d words from %d records" % (self.tablename, nb_words, nb_records)) # find possible bad states in reverse tables: query = """SELECT COUNT(DISTINCT(id_bibrec)) FROM %sR WHERE type <> 'CURRENT'""" % (self.tablename[:-1]) res = run_sql(query) if res: nb_bad_records = res[0][0] else: nb_bad_records = 999999999 if nb_bad_records: write_message("EMERGENCY: %s needs to repair %d of %d index records" % \ (self.tablename, nb_bad_records, nb_records)) else: write_message("%s is in consistent state" % (self.tablename)) return nb_bad_records def repair(self, opt_flush): """Repair the whole table""" # find possible bad states in reverse tables: query = """SELECT COUNT(DISTINCT(id_bibrec)) FROM %sR WHERE type <> 'CURRENT'""" % (self.tablename[:-1]) res = run_sql(query, None, 1) if res: nb_bad_records = res[0][0] else: nb_bad_records = 0 if nb_bad_records == 0: return query = """SELECT id_bibrec FROM %sR WHERE type <> 'CURRENT'""" \ % (self.tablename[:-1]) res = intbitset(run_sql(query)) recIDs = create_range_list(list(res)) flush_count = 0 records_done = 0 records_to_go = 0 for arange in recIDs: records_to_go = records_to_go + arange[1] - arange[0] + 1 time_started = time.time() # will measure profile time for arange in recIDs: i_low = arange[0] chunksize_count = 0 while i_low <= arange[1]: task_sleep_now_if_required() # calculate chunk group of recIDs and treat it: i_high = min(i_low + opt_flush - flush_count - 1, arange[1]) i_high = min(i_low + chunksize - chunksize_count - 1, i_high) self.fix_recID_range(i_low, i_high) flush_count = flush_count + i_high - i_low + 1 chunksize_count = chunksize_count + i_high - i_low + 1 records_done = records_done + i_high - i_low + 1 if chunksize_count >= chunksize: chunksize_count = 0 # flush if necessary: if flush_count >= opt_flush: self.put_into_db("emergency") self.clean() flush_count = 0 self.log_progress(time_started, records_done, records_to_go) # iterate: i_low = i_high + 1 if flush_count > 0: self.put_into_db("emergency") self.log_progress(time_started, records_done, records_to_go) write_message("%s inconsistencies repaired." % self.tablename) def chk_recID_range(self, low, high): """Check if the reverse index table is in proper state""" ## check db query = """SELECT COUNT(*) FROM %sR WHERE type <> 'CURRENT' AND id_bibrec BETWEEN %%s AND %%s""" % self.tablename[:-1] res = run_sql(query, (low, high), 1) if res[0][0] == 0: write_message("%s for %d-%d is in consistent state" % (self.tablename, low, high)) return # okay, words table is consistent ## inconsistency detected! write_message("EMERGENCY: %s inconsistencies detected..." % self.tablename) error_message = "Errors found. You should check consistency of the " \ "%s - %sR tables.\nRunning 'bibindex --repair' is " \ "recommended." % (self.tablename, self.tablename[:-1]) write_message("EMERGENCY: " + error_message, stream=sys.stderr) raise StandardError(error_message) def fix_recID_range(self, low, high): """Try to fix reverse index database consistency (e.g. table idxWORD01R) in the low,high doc-id range. Possible states for a recID follow: CUR TMP FUT: very bad things have happened: warn! CUR TMP : very bad things have happened: warn! CUR FUT: delete FUT (crash before flushing) CUR : database is ok TMP FUT: add TMP to memory and del FUT from memory flush (revert to old state) TMP : very bad things have happened: warn! FUT: very bad things have happended: warn! """ state = {} query = "SELECT id_bibrec,type FROM %sR WHERE id_bibrec BETWEEN %%s AND %%s"\ % self.tablename[:-1] res = run_sql(query, (low, high)) for row in res: if not state.has_key(row[0]): state[row[0]] = [] state[row[0]].append(row[1]) ok = 1 # will hold info on whether we will be able to repair for recID in state.keys(): if not 'TEMPORARY' in state[recID]: if 'FUTURE' in state[recID]: if 'CURRENT' not in state[recID]: write_message("EMERGENCY: Index record %d is in inconsistent state. Can't repair it." % recID) ok = 0 else: write_message("EMERGENCY: Inconsistency in index record %d detected" % recID) query = """DELETE FROM %sR WHERE id_bibrec=%%s""" % self.tablename[:-1] run_sql(query, (recID,)) write_message("EMERGENCY: Inconsistency in record %d repaired." % recID) else: if 'FUTURE' in state[recID] and not 'CURRENT' in state[recID]: self.recIDs_in_mem.append([recID, recID]) # Get the words file query = """SELECT type,termlist FROM %sR WHERE id_bibrec=%%s""" % self.tablename[:-1] write_message(query, verbose=9) res = run_sql(query, (recID,)) for row in res: wlist = deserialize_via_marshal(row[1]) write_message("Words are %s " % wlist, verbose=9) if row[0] == 'TEMPORARY': sign = 1 else: sign = -1 for word in wlist: self.put(recID, word, sign) else: write_message("EMERGENCY: %s for %d is in inconsistent " "state. Couldn't repair it." % (self.tablename, recID), stream=sys.stderr) ok = 0 if not ok: error_message = "Unrepairable errors found. You should check " \ "consistency of the %s - %sR tables. Deleting affected " \ "TEMPORARY and FUTURE entries from these tables is " \ "recommended; see the BibIndex Admin Guide." % \ (self.tablename, self.tablename[:-1]) write_message("EMERGENCY: " + error_message, stream=sys.stderr) raise StandardError(error_message) def remove_dependent_index(self, id_dependent): """Removes terms found in dependent index from virtual index. Function finds words for removal and then removes them from forward and reversed tables term by term. @param id_dependent: id of an index which we want to remove from this virtual index """ if not self.is_virtual: write_message("Index is not virtual...") return global chunksize terms_current_counter = 0 terms_done = 0 terms_to_go = 0 for_full_removal, for_partial_removal = self.get_words_to_remove(id_dependent, misc_lookup=False) query = """SELECT t.term, m.hitlist FROM %s%02dF as t INNER JOIN %s%02dF as m ON t.term=m.term""" % (self.tablename[:-3], self.index_id, self.tablename[:-3], id_dependent) terms_and_hitlists = dict(run_sql(query)) terms_to_go = len(for_full_removal) + len(for_partial_removal) task_sleep_now_if_required() #full removal for term in for_full_removal: terms_current_counter += 1 hitlist = intbitset(terms_and_hitlists[term]) for recID in hitlist: self.remove_single_word_reversed_table(term, recID) self.remove_single_word_forward_table(term) if terms_current_counter % chunksize == 0: terms_done += terms_current_counter terms_current_counter = 0 write_message("removed %s/%s terms..." % (terms_done, terms_to_go)) task_sleep_now_if_required() terms_done += terms_current_counter terms_current_counter = 0 #partial removal for term, indexes in for_partial_removal.iteritems(): self.value = {} terms_current_counter += 1 hitlist = intbitset(terms_and_hitlists[term]) if len(indexes) > 0: hitlist -= self._find_common_hitlist(term, id_dependent, indexes) for recID in hitlist: self.remove_single_word_reversed_table(term, recID) if self.value.has_key(term): self.value[term][recID] = -1 else: self.value[term] = {recID: -1} if self.value: self.put_word_into_db(term, self.index_id) if terms_current_counter % chunksize == 0: terms_done += terms_current_counter terms_current_counter = 0 write_message("removed %s/%s terms..." % (terms_done, terms_to_go)) task_sleep_now_if_required() def remove_single_word_forward_table(self, word): """Immediately and irreversibly removes a word from forward table""" run_sql("""DELETE FROM %s WHERE term=%%s""" % self.tablename, (word, )) # kwalitee: disable=sql def remove_single_word_reversed_table(self, word, recID): """Removes single word from temlist for given recID""" old_set = run_sql("""SELECT termlist FROM %sR WHERE id_bibrec=%%s""" % \ wash_table_column_name(self.tablename[:-1]), (recID, )) new_set = [] if old_set: new_set = deserialize_via_marshal(old_set[0][0]) if word in new_set: new_set.remove(word) if new_set: run_sql("""UPDATE %sR SET termlist=%%s WHERE id_bibrec=%%s AND type='CURRENT'""" % \ wash_table_column_name(self.tablename[:-1]), (serialize_via_marshal(new_set), recID)) def _find_common_hitlist(self, term, id_dependent, indexes): """Checks 'indexes' for records that have 'term' indexed and returns intersection between found records and records that have a 'term' inside index defined by id_dependent parameter""" query = """SELECT m.hitlist FROM idxWORD%02dF as t INNER JOIN idxWORD%02dF as m ON t.term=m.term WHERE t.term='%s'""" common_hitlist = intbitset([]) for _id in indexes: res = run_sql(query % (id_dependent, _id, term)) if res: common_hitlist |= intbitset(res[0][0]) return common_hitlist def get_words_to_remove(self, id_dependent, misc_lookup=False): """Finds words in dependent index which should be removed from virtual index. Example: Virtual index 'A' consists of 'B' and 'C' dependent indexes and we want to remove 'B' from virtual index 'A'. First we need to check if 'B' and 'C' have common words. If they have we need to be careful not to remove common words from 'A', because we want to remove only words from 'B'. Then we need to check common words for 'A' and 'B'. These are potential words for removal. We need to substract common words for 'B' and 'C' from common words for 'A' and 'B' to be sure that correct words are removed. @return: (list, dict), list contains terms/words for full removal, dict contains words for partial removal together with ids of indexes in which given term/word also exists """ query = """SELECT t.term FROM %s%02dF as t INNER JOIN %s%02dF as m ON t.term=m.term""" dependent_indexes = get_virtual_index_building_blocks(self.index_id) other_ids = list(dependent_indexes and zip(*dependent_indexes)[0] or []) if id_dependent in other_ids: other_ids.remove(id_dependent) if not misc_lookup: misc_id = get_index_id_from_index_name('miscellaneous') if misc_id in other_ids: other_ids.remove(misc_id) #intersections between dependent indexes left_in_other_indexes = {} for _id in other_ids: intersection = zip(*run_sql(query % (self.tablename[:-3], id_dependent, self.tablename[:-3], _id))) # kwalitee: disable=sql terms = bool(intersection) and intersection[0] or [] for term in terms: if left_in_other_indexes.has_key(term): left_in_other_indexes[term].append(_id) else: left_in_other_indexes[term] = [_id] #intersection between virtual index and index we want to remove main_intersection = zip(*run_sql(query % (self.tablename[:-3], self.index_id, self.tablename[:-3], id_dependent))) # kwalitee: disable=sql terms_main = set(bool(main_intersection) and main_intersection[0] or []) return list(terms_main - set(left_in_other_indexes.keys())), left_in_other_indexes def main(): """Main that construct all the bibtask.""" task_init(authorization_action='runbibindex', authorization_msg="BibIndex Task Submission", description="""Examples: \t%s -a -i 234-250,293,300-500 -u admin@localhost \t%s -a -w author,fulltext -M 8192 -v3 \t%s -d -m +4d -A on --flush=10000\n""" % ((sys.argv[0],) * 3), help_specific_usage=""" Indexing options: -a, --add\t\tadd or update words for selected records -d, --del\t\tdelete words for selected records -i, --id=low[-high]\t\tselect according to doc recID -m, --modified=from[,to]\tselect according to modification date -c, --collection=c1[,c2]\tselect according to collection -R, --reindex\treindex the selected indexes from scratch Repairing options: -k, --check\t\tcheck consistency for all records in the table(s) -r, --repair\t\ttry to repair all records in the table(s) Specific options: -w, --windex=w1[,w2]\tword/phrase indexes to consider (all) -M, --maxmem=XXX\tmaximum memory usage in kB (no limit) -f, --flush=NNN\t\tfull consistent table flush after NNN records (10000) --force\tforce indexing of all records for provided indexes -Z, --remove-dependent-index=w\tname of an index for removing from virtual index """, version=__revision__, specific_params=("adi:m:c:w:krRM:f:oZ:", [ "add", "del", "id=", "modified=", "collection=", "windex=", "check", "repair", "reindex", "maxmem=", "flush=", "force", "remove-dependent-index=" ]), task_stop_helper_fnc=task_stop_table_close_fnc, task_submit_elaborate_specific_parameter_fnc=task_submit_elaborate_specific_parameter, task_run_fnc=task_run_core, task_submit_check_options_fnc=task_submit_check_options) def task_submit_check_options(): """Check for options compatibility.""" if task_get_option("reindex"): if task_get_option("cmd") != "add" or task_get_option('id') or task_get_option('collection'): print >> sys.stderr, "ERROR: You can use --reindex only when adding modified record." return False return True def task_submit_elaborate_specific_parameter(key, value, opts, args): """ Given the string key it checks it's meaning, eventually using the value. Usually it fills some key in the options dict. It must return True if it has elaborated the key, False, if it doesn't know that key. eg: if key in ['-n', '--number']: self.options['number'] = value return True return False """ if key in ("-a", "--add"): task_set_option("cmd", "add") if ("-x", "") in opts or ("--del", "") in opts: raise StandardError("Can not have --add and --del at the same time!") elif key in ("-k", "--check"): task_set_option("cmd", "check") elif key in ("-r", "--repair"): task_set_option("cmd", "repair") elif key in ("-d", "--del"): task_set_option("cmd", "del") elif key in ("-i", "--id"): task_set_option('id', task_get_option('id') + split_ranges(value)) elif key in ("-m", "--modified"): task_set_option("modified", get_date_range(value)) elif key in ("-c", "--collection"): task_set_option("collection", value) elif key in ("-R", "--reindex"): task_set_option("reindex", True) elif key in ("-w", "--windex"): task_set_option("windex", value) elif key in ("-M", "--maxmem"): task_set_option("maxmem", int(value)) if task_get_option("maxmem") < base_process_size + 1000: raise StandardError("Memory usage should be higher than %d kB" % \ (base_process_size + 1000)) elif key in ("-f", "--flush"): task_set_option("flush", int(value)) elif key in ("-o", "--force"): task_set_option("force", True) elif key in ("-Z", "--remove-dependent-index",): task_set_option("remove-dependent-index", value) else: return False return True def task_stop_table_close_fnc(): """ Close tables to STOP. """ global _last_word_table if _last_word_table: _last_word_table.put_into_db() def get_recIDs_by_date_bibliographic(dates, index_name, force_all=False): """ Finds records that were modified between DATES[0] and DATES[1] for given index. If DATES is not set, then finds records that were modified since the last update of the index. @param wordtable_type: can be 'Words', 'Pairs' or 'Phrases' """ index_id = get_index_id_from_index_name(index_name) if not dates: query = """SELECT last_updated FROM idxINDEX WHERE id=%s""" res = run_sql(query, (index_id,)) if not res: return set([]) if not res[0][0] or force_all: dates = ("0000-00-00", None) else: dates = (res[0][0], None) if dates[1] is None: res = intbitset(run_sql("""SELECT b.id FROM bibrec AS b WHERE b.modification_date >= %s""", (dates[0],))) if index_name == 'fulltext': res |= intbitset(run_sql("""SELECT id_bibrec FROM bibrec_bibdoc JOIN bibdoc ON id_bibdoc=id WHERE text_extraction_date <= modification_date AND modification_date >= %s AND status<>'DELETED'""", (dates[0],))) elif dates[0] is None: res = intbitset(run_sql("""SELECT b.id FROM bibrec AS b WHERE b.modification_date <= %s""", (dates[1],))) if index_name == 'fulltext': res |= intbitset(run_sql("""SELECT id_bibrec FROM bibrec_bibdoc JOIN bibdoc ON id_bibdoc=id WHERE text_extraction_date <= modification_date AND modification_date <= %s AND status<>'DELETED'""", (dates[1],))) else: res = intbitset(run_sql("""SELECT b.id FROM bibrec AS b WHERE b.modification_date >= %s AND b.modification_date <= %s""", (dates[0], dates[1]))) if index_name == 'fulltext': res |= intbitset(run_sql("""SELECT id_bibrec FROM bibrec_bibdoc JOIN bibdoc ON id_bibdoc=id WHERE text_extraction_date <= modification_date AND modification_date >= %s AND modification_date <= %s AND status<>'DELETED'""", (dates[0], dates[1],))) # special case of author indexes where we need to re-index # those records that were affected by changed BibAuthorID attributions: if index_name in ('author', 'firstauthor', 'exactauthor', 'exactfirstauthor'): from invenio.legacy.bibauthorid.personid_maintenance import get_recids_affected_since # dates[1] is ignored, since BibAuthorID API does not offer upper limit search rec_list_author = intbitset(get_recids_affected_since(dates[0])) res = res | rec_list_author return set(res) def get_recIDs_by_date_authority(dates, index_name, force_all=False): """ Finds records that were modified between DATES[0] and DATES[1] for given index. If DATES is not set, then finds records that were modified since the last update of the index. Searches for bibliographic records connected to authority records that have been changed. """ index_id = get_index_id_from_index_name(index_name) index_tags = get_index_tags(index_name) if not dates: query = """SELECT last_updated FROM idxINDEX WHERE id=%s""" res = run_sql(query, (index_id,)) if not res: return set([]) if not res[0][0] or force_all: dates = ("0000-00-00", None) else: dates = (res[0][0], None) res = intbitset() for tag in index_tags: pattern = tag.replace('%', '*') matches = fnmatch.filter(CFG_BIBAUTHORITY_CONTROLLED_FIELDS_BIBLIOGRAPHIC.keys(), pattern) if not len(matches): continue for tag_match in matches: # get the type of authority record associated with this field auth_type = CFG_BIBAUTHORITY_CONTROLLED_FIELDS_BIBLIOGRAPHIC.get(tag_match) # find updated authority records of this type # dates[1] is ignored, needs dates[0] to find res now = datetime.now() auth_recIDs = search_pattern(p='980__a:' + auth_type) \ & search_unit_in_bibrec(str(dates[0]), str(now), type='m') # now find dependent bibliographic records for auth_recID in auth_recIDs: # get the fix authority identifier of this authority record control_nos = get_control_nos_from_recID(auth_recID) # there may be multiple control number entries! (the '035' field is repeatable!) for control_no in control_nos: # get the bibrec IDs that refer to AUTHORITY_ID in TAG tag_0 = tag_match[:5] + '0' # possibly do the same for '4' subfields ? fieldvalue = '"' + control_no + '"' res |= search_pattern(p=tag_0 + ':' + fieldvalue) return set(res) def get_not_updated_recIDs(modified_dates, indexes, force_all=False): """Finds not updated recIDs in database for indexes. @param modified_dates: between this dates we should look for modified records @type modified_dates: [date_old, date_new] @param indexes: list of indexes @type indexes: string separated by coma @param force_all: if True all records will be taken """ found_recIDs = set() write_message(CFG_BIBINDEX_UPDATE_MESSAGE) for index in indexes: found_recIDs |= get_recIDs_by_date_bibliographic(modified_dates, index, force_all) found_recIDs |= get_recIDs_by_date_authority(modified_dates, index, force_all) return list(sorted(found_recIDs)) def get_recIDs_from_cli(indexes=[]): """ Gets recIDs ranges from CLI for indexing when user specified 'id' or 'collection' option or search for modified recIDs for provided indexes when recIDs are not specified. @param indexes: it's a list of specified indexes, which can be obtained from CLI with use of: get_indexes_from_cli() function. @type indexes: list of strings """ # need to first update idxINDEX table to find proper recIDs for reindexing if task_get_option("reindex"): for index_name in indexes: run_sql("""UPDATE idxINDEX SET last_updated='0000-00-00 00:00:00' WHERE name=%s""", (index_name,)) if task_get_option("id"): return task_get_option("id") elif task_get_option("collection"): l_of_colls = task_get_option("collection").split(",") recIDs = perform_request_search(c=l_of_colls) recIDs_range = [] for recID in recIDs: recIDs_range.append([recID, recID]) return recIDs_range elif task_get_option("cmd") == "add": recs = get_not_updated_recIDs(task_get_option("modified"), indexes, task_get_option("force")) recIDs_range = beautify_range_list(create_range_list(recs)) return recIDs_range return [] def get_indexes_from_cli(): """ Gets indexes from CLI and checks if they are valid. If indexes weren't specified function will return all known indexes. """ indexes = task_get_option("windex") if not indexes: indexes = get_all_indexes() else: indexes = indexes.split(",") indexes = remove_inexistent_indexes(indexes, leave_virtual=True) return indexes def remove_dependent_index(virtual_indexes, dependent_index): """ Removes dependent index from virtual indexes. @param virtual_indexes: names of virtual_indexes @type virtual_indexes: list of strings @param dependent_index: name of dependent index @type dependent_index: string """ if not virtual_indexes: write_message("You should specify a name of a virtual index...") id_dependent = get_index_id_from_index_name(dependent_index) wordTables = get_word_tables(virtual_indexes) for index_id, index_name, index_tags in wordTables: wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxWORD%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Words"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=50) wordTable.remove_dependent_index(id_dependent) wordTable.report_on_table_consistency() task_sleep_now_if_required() wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxPAIR%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Pairs"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=50) wordTable.remove_dependent_index(id_dependent) wordTable.report_on_table_consistency() task_sleep_now_if_required() wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxPHRASE%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Words"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=50) wordTable.remove_dependent_index(id_dependent) wordTable.report_on_table_consistency() query = """DELETE FROM idxINDEX_idxINDEX WHERE id_virtual=%s AND id_normal=%s""" run_sql(query, (index_id, id_dependent)) def task_run_core(): """Runs the task by fetching arguments from the BibSched task queue. This is what BibSched will be invoking via daemon call. """ global _last_word_table indexes = get_indexes_from_cli() if len(indexes) == 0: write_message("Specified indexes can't be found.") return True # check tables consistency if task_get_option("cmd") == "check": wordTables = get_word_tables(indexes) for index_id, index_name, index_tags in wordTables: wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxWORD%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Words"], tag_to_tokenizer_map={'8564_u': "BibIndexFulltextTokenizer"}, wash_index_terms=50) _last_word_table = wordTable wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxPAIR%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Pairs"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=100) _last_word_table = wordTable wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern='idxPHRASE%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Phrases"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=0) _last_word_table = wordTable wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) _last_word_table = None return True #virtual index: remove dependent index if task_get_option("remove-dependent-index"): remove_dependent_index(indexes, task_get_option("remove-dependent-index")) return True #initialization for Words,Pairs,Phrases recIDs_range = get_recIDs_from_cli(indexes) recIDs_for_index = find_affected_records_for_index(indexes, recIDs_range, (task_get_option("force") or \ task_get_option("reindex") or \ task_get_option("cmd") == "del")) wordTables = get_word_tables(recIDs_for_index.keys()) if not wordTables: write_message("Selected indexes/recIDs are up to date.") # Let's work on single words! for index_id, index_name, index_tags in wordTables: reindex_prefix = "" if task_get_option("reindex"): reindex_prefix = "tmp_" init_temporary_reindex_tables(index_id, reindex_prefix) wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern=reindex_prefix + 'idxWORD%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Words"], tag_to_tokenizer_map={'8564_u': "BibIndexFulltextTokenizer"}, wash_index_terms=50) _last_word_table = wordTable wordTable.report_on_table_consistency() try: if task_get_option("cmd") == "del": if task_get_option("id") or task_get_option("collection"): wordTable.del_recIDs(recIDs_range) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Missing IDs of records to delete from " \ "index %s." % wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) elif task_get_option("cmd") == "add": final_recIDs = beautify_range_list(create_range_list(recIDs_for_index[index_name])) wordTable.add_recIDs(final_recIDs, task_get_option("flush")) task_sleep_now_if_required(can_stop_too=True) elif task_get_option("cmd") == "repair": wordTable.repair(task_get_option("flush")) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Invalid command found processing %s" % \ wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) except StandardError, e: write_message("Exception caught: %s" % e, sys.stderr) register_exception(alert_admin=True) if _last_word_table: _last_word_table.put_into_db() raise wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) # Let's work on pairs now wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern=reindex_prefix + 'idxPAIR%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Pairs"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=100) _last_word_table = wordTable wordTable.report_on_table_consistency() try: if task_get_option("cmd") == "del": if task_get_option("id") or task_get_option("collection"): wordTable.del_recIDs(recIDs_range) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Missing IDs of records to delete from " \ "index %s." % wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) elif task_get_option("cmd") == "add": final_recIDs = beautify_range_list(create_range_list(recIDs_for_index[index_name])) wordTable.add_recIDs(final_recIDs, task_get_option("flush")) task_sleep_now_if_required(can_stop_too=True) elif task_get_option("cmd") == "repair": wordTable.repair(task_get_option("flush")) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Invalid command found processing %s" % \ wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) except StandardError, e: write_message("Exception caught: %s" % e, sys.stderr) register_exception() if _last_word_table: _last_word_table.put_into_db() raise wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) # Let's work on phrases now wordTable = WordTable(index_name=index_name, index_id=index_id, fields_to_index=index_tags, table_name_pattern=reindex_prefix + 'idxPHRASE%02dF', wordtable_type=CFG_BIBINDEX_INDEX_TABLE_TYPE["Phrases"], tag_to_tokenizer_map={'8564_u': "BibIndexEmptyTokenizer"}, wash_index_terms=0) _last_word_table = wordTable wordTable.report_on_table_consistency() try: if task_get_option("cmd") == "del": if task_get_option("id") or task_get_option("collection"): wordTable.del_recIDs(recIDs_range) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Missing IDs of records to delete from " \ "index %s." % wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) elif task_get_option("cmd") == "add": final_recIDs = beautify_range_list(create_range_list(recIDs_for_index[index_name])) wordTable.add_recIDs(final_recIDs, task_get_option("flush")) if not task_get_option("id") and not task_get_option("collection"): update_index_last_updated([index_name], task_get_task_param('task_starting_time')) task_sleep_now_if_required(can_stop_too=True) elif task_get_option("cmd") == "repair": wordTable.repair(task_get_option("flush")) task_sleep_now_if_required(can_stop_too=True) else: error_message = "Invalid command found processing %s" % \ wordTable.tablename write_message(error_message, stream=sys.stderr) raise StandardError(error_message) except StandardError, e: write_message("Exception caught: %s" % e, sys.stderr) register_exception() if _last_word_table: _last_word_table.put_into_db() raise wordTable.report_on_table_consistency() task_sleep_now_if_required(can_stop_too=True) if task_get_option("reindex"): swap_temporary_reindex_tables(index_id, reindex_prefix) update_index_last_updated([index_name], task_get_task_param('task_starting_time')) task_sleep_now_if_required(can_stop_too=True) # update modification date also for indexes that were up to date if not task_get_option("id") and not task_get_option("collection") and \ task_get_option("cmd") == "add": up_to_date = set(indexes) - set(recIDs_for_index.keys()) update_index_last_updated(list(up_to_date), task_get_task_param('task_starting_time')) _last_word_table = None return True ### okay, here we go: if __name__ == '__main__': main() diff --git a/invenio/modules/circulation/models.py b/invenio/modules/circulation/models.py index 848ef4a48..638304fd6 100644 --- a/invenio/modules/circulation/models.py +++ b/invenio/modules/circulation/models.py @@ -1,279 +1,279 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ bibcirculation database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec class CrcBORROWER(db.Model): """Represents a CrcBORROWER record.""" def __init__(self): pass __tablename__ = 'crcBORROWER' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) ccid = db.Column(db.Integer(15, unsigned=True), nullable=True, unique=True, server_default=None) name = db.Column(db.String(255), nullable=False, server_default='', index=True) email = db.Column(db.String(255), nullable=False, server_default='', index=True) phone = db.Column(db.String(60), nullable=True) address = db.Column(db.String(60), nullable=True) mailbox = db.Column(db.String(30), nullable=True) borrower_since = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') borrower_until = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') notes = db.Column(db.Text, nullable=True) class CrcLIBRARY(db.Model): """Represents a CrcLIBRARY record.""" def __init__(self): pass __tablename__ = 'crcLIBRARY' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) name = db.Column(db.String(80), nullable=False, server_default='') address = db.Column(db.String(255), nullable=False, server_default='') email = db.Column(db.String(255), nullable=False, server_default='') phone = db.Column(db.String(30), nullable=False, server_default='') type = db.Column(db.String(30), nullable=False, server_default='main') notes = db.Column(db.Text, nullable=True) class CrcITEM(db.Model): """Represents a CrcITEM record.""" def __init__(self): pass __tablename__ = 'crcITEM' barcode = db.Column(db.String(30), nullable=False, server_default='', primary_key=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') id_crcLIBRARY = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcLIBRARY.id), nullable=False, server_default='0') collection = db.Column(db.String(60), nullable=True) location = db.Column(db.String(60), nullable=True) description = db.Column(db.String(60), nullable=True) loan_period = db.Column(db.String(30), nullable=False, server_default='') status = db.Column(db.String(20), nullable=False, server_default='') expected_arrival_date = db.Column(db.String(60), nullable=False, server_default='') creation_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') modification_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') number_of_requests = db.Column(db.Integer(3, unsigned=True), nullable=False,server_default='0') class CrcILLREQUEST(db.Model): """Represents a CrcILLREQUEST record.""" def __init__(self): pass __tablename__ = 'crcILLREQUEST' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_crcBORROWER = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcBORROWER.id), nullable=False, server_default='0') barcode = db.Column(db.String(30), db.ForeignKey(CrcITEM.barcode), nullable=False, server_default='') period_of_interest_from = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') period_of_interest_to = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') id_crcLIBRARY = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcLIBRARY.id), nullable=False, server_default='0') request_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') expected_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') arrival_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') due_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') return_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') status = db.Column(db.String(20), nullable=False, server_default='') cost = db.Column(db.String(30), nullable=False, server_default='') budget_code = db.Column(db.String(60), nullable=False, server_default='') item_info = db.Column(db.Text, nullable=True) request_type = db.Column(db.Text, nullable=True) borrower_comments = db.Column(db.Text, nullable=True) only_this_edition = db.Column(db.String(10), nullable=False, server_default='') library_notes = db.Column(db.Text, nullable=True) overdue_letter_number = db.Column(db.Integer(3, unsigned=True), nullable=False, server_default='0') overdue_letter_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') borrower = db.relationship(CrcBORROWER, backref='illrequests') item = db.relationship(CrcITEM, backref='illrequests') library = db.relationship(CrcLIBRARY, backref='illrequests') class CrcLOAN(db.Model): """Represents a CrcLOAN record.""" def __init__(self): pass __tablename__ = 'crcLOAN' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_crcBORROWER = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcBORROWER.id), nullable=False, server_default='0') id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') barcode = db.Column(db.String(30), db.ForeignKey(CrcITEM.barcode), nullable=False, server_default='') loaned_on = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') returned_on = db.Column(db.Date, nullable=False, server_default='0000-00-00') due_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') number_of_renewals = db.Column(db.Integer(3, unsigned=True), nullable=False, server_default='0') overdue_letter_number = db.Column(db.Integer(3, unsigned=True), nullable=False, server_default='0') overdue_letter_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') status = db.Column(db.String(20), nullable=False, server_default='') type = db.Column(db.String(20), nullable=False, server_default='') notes = db.Column(db.Text, nullable=True) borrower = db.relationship(CrcBORROWER, backref='loans') bibrec = db.relationship(Bibrec, backref='loans') item = db.relationship(CrcITEM, backref='loans') class CrcLOANREQUEST(db.Model): """Represents a CrcLOANREQUEST record.""" def __init__(self): pass __tablename__ = 'crcLOANREQUEST' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_crcBORROWER = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcBORROWER.id), nullable=False, server_default='0') id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') barcode = db.Column(db.String(30), db.ForeignKey(CrcITEM.barcode), nullable=False, server_default='') period_of_interest_from = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') period_of_interest_to = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') status = db.Column(db.String(20), nullable=False, server_default='') notes = db.Column(db.Text, nullable=True) request_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') borrower = db.relationship(CrcBORROWER, backref='loanrequests') bibrec = db.relationship(Bibrec, backref='loanrequests') item = db.relationship(CrcITEM, backref='loanrequests') class CrcVENDOR(db.Model): """Represents a CrcVENDOR record.""" def __init__(self): pass __tablename__ = 'crcVENDOR' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) name = db.Column(db.String(80), nullable=False, server_default='') address = db.Column(db.String(255), nullable=False, server_default='') email = db.Column(db.String(255), nullable=False, server_default='') phone = db.Column(db.String(30), nullable=False, server_default='') notes = db.Column(db.Text, nullable=True) class CrcPURCHASE(db.Model): """Represents a CrcPURCHASE record.""" def __init__(self): pass __tablename__ = 'crcPURCHASE' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') id_crcVENDOR = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CrcVENDOR.id), nullable=False, server_default='0') ordered_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') expected_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') price = db.Column(db.String(20), nullable=False, server_default='0') status = db.Column(db.String(20), nullable=False, server_default='') notes = db.Column(db.Text, nullable=True) bibrec = db.relationship(Bibrec, backref='purchases') vendor = db.relationship(CrcVENDOR, backref='purchases') __all__ = ['CrcBORROWER', 'CrcLIBRARY', 'CrcITEM', 'CrcILLREQUEST', 'CrcLOAN', 'CrcLOANREQUEST', 'CrcVENDOR', 'CrcPURCHASE'] diff --git a/invenio/modules/comments/models.py b/invenio/modules/comments/models.py index 9a96b609d..b1616b4da 100644 --- a/invenio/modules/comments/models.py +++ b/invenio/modules/comments/models.py @@ -1,184 +1,184 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ WebComment database models. """ # General imports. from sqlalchemy import event from invenio.ext.sqlalchemy import db from flask.ext.login import current_user # Create your models here. -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.accounts.models import User from invenio.base.signals import record_after_update class CmtRECORDCOMMENT(db.Model): """Represents a CmtRECORDCOMMENT record.""" __tablename__ = 'cmtRECORDCOMMENT' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False, server_default='0') title = db.Column(db.String(255), nullable=False, server_default='') body = db.Column(db.Text, nullable=False) date_creation = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') star_score = db.Column(db.TinyInteger(5, unsigned=True), nullable=False, server_default='0') nb_votes_yes = db.Column(db.Integer(10), nullable=False, server_default='0') nb_votes_total = db.Column(db.Integer(10, unsigned=True), nullable=False, server_default='0') nb_abuse_reports = db.Column(db.Integer(10), nullable=False, server_default='0') status = db.Column(db.Char(2), nullable=False, index=True, server_default='ok') round_name = db.Column(db.String(255), nullable=False, server_default='') restriction = db.Column(db.String(50), nullable=False, server_default='') in_reply_to_id_cmtRECORDCOMMENT = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(id), nullable=False, server_default='0') reply_order_cached_data = db.Column(db.Binary, nullable=True) bibrec = db.relationship(Bibrec, backref='recordcomments') user = db.relationship(User, backref='recordcomments') replies = db.relationship('CmtRECORDCOMMENT', backref=db.backref( 'parent', remote_side=[id], order_by=date_creation)) @property def is_deleted(self): return self.status != 'ok' def is_collapsed(self, id_user): """Returns true if the comment is collapsed by user.""" return CmtCOLLAPSED.query.filter(db.and_( CmtCOLLAPSED.id_bibrec == self.id_bibrec, CmtCOLLAPSED.id_cmtRECORDCOMMENT == self.id, CmtCOLLAPSED.id_user == id_user)).count() > 0 def collapse(self, id_user): """Collapses comment beloging to user.""" c = CmtCOLLAPSED(id_bibrec=self.id_bibrec, id_cmtRECORDCOMMENT=self.id, id_user=id_user) try: db.session.add(c) db.session.commit() except: db.session.rollback() def expand(self, id_user): """Expands comment beloging to user.""" CmtCOLLAPSED.query.filter(db.and_( CmtCOLLAPSED.id_bibrec == self.id_bibrec, CmtCOLLAPSED.id_cmtRECORDCOMMENT == self.id, CmtCOLLAPSED.id_user == id_user)).delete(synchronize_session=False) __table_args__ = (db.Index('cmtRECORDCOMMENT_reply_order_cached_data', reply_order_cached_data, mysql_length=[40]), db.Model.__table_args__) @event.listens_for(CmtRECORDCOMMENT, 'after_insert') def after_insert(mapper, connection, target): """Updates reply order cache and send record-after-update signal.""" record_after_update.send(CmtRECORDCOMMENT, recid=target.id_bibrec) from .api import get_reply_order_cache_data if target.in_reply_to_id_cmtRECORDCOMMENT > 0: parent = CmtRECORDCOMMENT.query.get( target.in_reply_to_id_cmtRECORDCOMMENT) if parent: trans = connection.begin() parent_reply_order = parent.reply_order_cached_data \ if parent.reply_order_cached_data else '' parent_reply_order += get_reply_order_cache_data(target.id) connection.execute(db.update(CmtRECORDCOMMENT.__table__). where(CmtRECORDCOMMENT.id == parent.id). values(reply_order_cached_data=parent_reply_order)) trans.commit() class CmtACTIONHISTORY(db.Model): """Represents a CmtACTIONHISTORY record.""" __tablename__ = 'cmtACTIONHISTORY' id_cmtRECORDCOMMENT = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CmtRECORDCOMMENT.id), nullable=True, primary_key=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=True, primary_key=True) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=True, primary_key=True) client_host = db.Column(db.Integer(10, unsigned=True), nullable=True) action_time = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') action_code = db.Column(db.Char(1), nullable=False, index=True) recordcomment = db.relationship(CmtRECORDCOMMENT, backref='actionhistory') bibrec = db.relationship(Bibrec) user = db.relationship(User) class CmtSUBSCRIPTION(db.Model): """Represents a CmtSUBSCRIPTION record.""" __tablename__ = 'cmtSUBSCRIPTION' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False, primary_key=True) creation_time = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') bibrec = db.relationship(Bibrec) user = db.relationship(User, backref='comment_subscriptions') user_commented_records = db.relationship( Bibrec, backref='user_comment_subscritions', primaryjoin=lambda: db.and_( CmtSUBSCRIPTION.id_bibrec == Bibrec.id, CmtSUBSCRIPTION.id_user == current_user.get_id() ), viewonly=True) class CmtCOLLAPSED(db.Model): """Represents a CmtCOLLAPSED record.""" __tablename__ = 'cmtCOLLAPSED' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) id_cmtRECORDCOMMENT = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(CmtRECORDCOMMENT.id), primary_key=True) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), primary_key=True) __all__ = ['CmtRECORDCOMMENT', 'CmtACTIONHISTORY', 'CmtSUBSCRIPTION', 'CmtCOLLAPSED'] diff --git a/invenio/modules/deposit/field_base.py b/invenio/modules/deposit/field_base.py index cb921cce4..a974a3f81 100644 --- a/invenio/modules/deposit/field_base.py +++ b/invenio/modules/deposit/field_base.py @@ -1,252 +1,252 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ Validators ---------- Following is a short overview over how validators may be defined for fields. Inline validators (always executed):: class MyForm(...): myfield = MyField() def validate_myfield(form, field): raise ValidationError("Message") External validators (always executed):: def my_validator(form, field): raise ValidationError("Message") class MyForm(...): myfield = MyField(validators=[my_validator]) Field defined validators (always executed):: class MyField(...): # ... def pre_validate(self, form): raise ValidationError("Message") Default field validators (executed only if external validators are not defined):: class MyField(...): def __init__(self, **kwargs): defaults = dict(validators=[my_validator]) defaults.update(kwargs) super(MyField, self).__init__(**defaults) See http://wtforms.simplecodes.com/docs/1.0.4/validators.html for how to write validators. Post-processors --------------- Post processors follows the same pattern as validators. You may thus specify:: * Inline processors: Form.post_process_(form, field) * External processors: def my_processor(form, field) ... myfield = MyField(processors=[my_processor]) * Field defined processors (please method documentation): Field.post_process(self, form, extra_processors=[]) Auto-complete ------------- * External auto-completion function: def my_autocomplete(form, field, limit=50) ... myfield = MyField(autocomplete=my_autocomplete) * Field defined auto-completion function (please method documentation): Field.autocomplete(self, form, limit=50) """ from wtforms import Field from .form import CFG_FIELD_FLAGS __all__ = ['WebDepositField'] class WebDepositField(Field): """ Base field that all webdeposit fields must inherit from. """ def __init__(self, *args, **kwargs): """ Initialize WebDeposit field. Every field is associated with a marc field. To define this association you - have to specify the `export_key` for the bibfield's `JsonReader` or + have to specify the `export_key` for the recordext `Reader` or the `cook_function` (for more complicated fields). @param placeholder: str, Placeholder text for input fields. @param icon: Name of icon (rendering of the icon is done by templates) @type icon: str @param autocomplete: callable, A function to auto-complete values for field. @param processors: list of callables, List of processors to run for field. @param validators: list of callables, List of WTForm validators. If no validators are provided, validators defined in webdeposit_config will be loaded. @param hidden: Set to true to hide field. Default: False @type hidden: bool @param disabled: Set to true to disable field. Default: False @type disabled: bool @param export_key: Name of key to use during export @type export_key: str or callable @see http://wtforms.simplecodes.com/docs/1.0.4/validators.html for how to write validators. @see http://wtforms.simplecodes.com/docs/1.0.4/fields.html for further keyword argument that can be provided on field initialization. """ # Pop WebDeposit specific kwargs before calling super() self.placeholder = kwargs.pop('placeholder', None) self.group = kwargs.pop('group', None) self.icon = kwargs.pop('icon', None) self.autocomplete = kwargs.pop('autocomplete', None) self.processors = kwargs.pop('processors', None) self.export_key = kwargs.pop('export_key', None) self.widget_classes = kwargs.pop('widget_classes', None) # Initialize empty message variables, which are usually modified # during the post-processing phases. self._messages = [] self._message_state = '' # Get flag values (e.g. hidden, disabled) before super() call. # See CFG_FIELD_FLAGS for all defined flags. flag_values = {} for flag in CFG_FIELD_FLAGS: flag_values[flag] = kwargs.pop(flag, False) # Call super-constructor. super(WebDepositField, self).__init__(*args, **kwargs) # Set flag values after super() call to ensure, flags set during # super() are overwritten. for flag, value in flag_values.items(): if value: setattr(self.flags, flag, True) def __call__(self, *args, **kwargs): """ Set custom keyword arguments when rendering field """ if 'placeholder' not in kwargs and self.placeholder: kwargs['placeholder'] = self.placeholder if 'disabled' not in kwargs and self.flags.disabled: kwargs['disabled'] = "disabled" if 'class_' in kwargs and self.widget_classes: kwargs['class_'] = kwargs['class_'] + self.widget_classes elif self.widget_classes: kwargs['class_'] = self.widget_classes if self.autocomplete: kwargs['data-autocomplete'] = "1" return super(WebDepositField, self).__call__(*args, **kwargs) def reset_field_data(self, exclude=[]): """ Reset the fields.data value to that of field.object_data. Usually not called directly, but rather through Form.reset_field_data() @param exclude: List of formfield names to exclude. """ if self.name not in exclude: self.data = self.object_data def post_process(self, form=None, formfields=[], extra_processors=[], submit=False): """ Post process form before saving. Usually you can do some of the following tasks in the post processing: * Set field flags (e.g. self.flags.hidden = True or form..flags.hidden = True). * Set messages (e.g. self.messages.append('text') and self.message_state = 'info'). * Set values of other fields (e.g. form..data = ''). Processors may stop the processing chain by raising StopIteration. IMPORTANT: By default the method will execute custom post processors defined in the webdeposit_config. If you override the method, be sure to call this method to ensure extra processors are called:: super(MyField, self).post_process( form, extra_processors=extra_processors ) """ # Check if post processing should run for this field if self.name in formfields or not formfields: stop = False for p in (self.processors or []): try: p(form, self, submit) except StopIteration: stop = True break if not stop: for p in (extra_processors or []): p(form, self, submit) def perform_autocomplete(self, form, name, term, limit=50): """ Run auto-complete method for field. This method should not be called directly, instead use Form.autocomplete(). """ if name == self.name and self.autocomplete: return self.autocomplete(form, self, term, limit=limit) return None def add_message(self, msg, state=None): """ Add a message @param msg: The message to set @param state: State of message; info, warning, error, success. """ self._messages.append(msg) if state: self._message_state = state def set_flags(self, flags): """ Set field flags """ field_flags = flags.get(self.name, []) for check_flag in CFG_FIELD_FLAGS: setattr(self.flags, check_flag, check_flag in field_flags) @property def messages(self): """ Retrieve field messages """ if self.errors: return {self.name: dict( state='error', messages=self.errors )} else: return {self.name: dict( state=getattr(self, '_message_state', ''), messages=self._messages )} diff --git a/invenio/modules/deposit/processor_utils.py b/invenio/modules/deposit/processor_utils.py index d5d38161b..1a7db0ff6 100644 --- a/invenio/modules/deposit/processor_utils.py +++ b/invenio/modules/deposit/processor_utils.py @@ -1,276 +1,276 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. # from wtforms.validators import ValidationError, StopValidation, Regexp from werkzeug import MultiDict from invenio.utils.datacite import DataciteMetadata from invenio.utils.sherpa_romeo import SherpaRomeoSearch -from invenio.legacy.bibfield import get_record +from invenio.modules.records.api import get_record from invenio.utils import persistentid as pidutils # # General purpose processors # def replace_field_data(field_name, getter=None): """ Returns a processor, which will replace the given field names value with the value from the field where the processor is installed. """ def _inner(form, field, submit=False): getattr(form, field_name).data = getter(field) if getter else field.data return _inner # # PID processors # class PidSchemeDetection(object): """ Detect the persistent identifier scheme and store it in another field. """ def __init__(self, set_field=None): self.set_field = set_field def __call__(self, form, field, submit=False): if field.data: schemes = pidutils.detect_identifier_schemes(field.data) if schemes: getattr(form, self.set_field).data = schemes[0] class PidNormalize(object): """ Normalize a persistent identifier """ def __init__(self, scheme_field=None): self.scheme_field = scheme_field def __call__(self, form, field, submit=False): scheme = getattr(form, self.scheme_field).data field.data = pidutils.normalize_pid(field.data, scheme=scheme) # # DOI-related processors # def datacite_dict_mapper(datacite, form, mapping): """ Helper function to map DataCite metadata to form fields based on a mapping """ for func_name, field_name in mapping.items(): setattr(form, field_name, getattr(datacite, func_name)()) class DataCiteLookup(object): """ Lookup DOI metadata in DataCite but only if DOI is not locally administered. """ def __init__(self, display_info=False, mapping=None, mapping_func=None, exclude_prefix='10.5072'): self.display_info = display_info self.mapping = mapping or dict( get_publisher='publisher', get_titles='title', get_dates='date', get_description='abstract', ) self.mapping_func = mapping_func or datacite_dict_mapper self.prefix = exclude_prefix def __call__(self, form, field, submit=False): if not field.errors and field.data and not field.data.startswith(self.prefix + '/'): try: datacite = DataciteMetadata(field.data) if datacite.error: if self.display_info: field.add_message("DOI metadata could not be retrieved.", state='info') return if self.mapping_func: self.mapping_func(datacite, form, self.mapping) if self.display_info: field.add_message("DOI metadata successfully imported from DataCite.", state='info') except Exception: # Ignore errors pass datacite_lookup = DataCiteLookup def sherpa_romeo_issn_process(form, field, submit=False): value = field.data or '' if value == "" or value.isspace(): return dict(error=0, error_message='') s = SherpaRomeoSearch() s.search_issn(value) if s.error: field.add_message(s.error_message, state='info') return if s.get_num_hits() == 1: journal = s.parser.get_journals(attribute='jtitle') journal = journal[0] publisher = s.parser.get_publishers(journal=journal) if publisher is not None and publisher != []: if hasattr(form, 'journal'): form.journal.data = journal if hasattr(form, 'publisher'): form.publisher.data = publisher['name'] return else: if hasattr(form, 'journal'): form.journal.data = journal return field.add_message("Couldn't find Journal.", state='info') def sherpa_romeo_publisher_process(form, field, submit=False): value = field.data or '' if value == "" or value.isspace(): return s = SherpaRomeoSearch() s.search_publisher(value) if s.error: field.add_message(s.error_message, state='info') conditions = s.parser.get_publishers(attribute='conditions') if conditions is not None and s.get_num_hits() == 1: conditions = conditions[0] else: conditions = [] if conditions != []: conditions_html = "Conditions
    " if isinstance(conditions['condition'], str): conditions_html += "
  1. " + conditions['condition'] + "
  2. " else: for condition in conditions['condition']: conditions_html += "
  3. " + condition + "
  4. " copyright_links = s.parser.get_publishers(attribute='copyrightlinks') if copyright_links is not None and copyright_links != []: copyright_links = copyright_links[0] else: copyright_links = None if isinstance(copyright_links, list): copyright_links_html = "" for copyright_link in copyright_links['copyrightlink']: copyright_links_html += '' + copyright_link['copyrightlinktext'] + "
    " elif isinstance(copyright_links, dict): if isinstance(copyright_links['copyrightlink'], list): for copyright_link in copyright_links['copyrightlink']: copyright_links_html = '' + copyright_link['copyrightlinktext'] + "
    " else: copyright_link = copyright_links['copyrightlink'] copyright_links_html = '' + copyright_link['copyrightlinktext'] + "
    " home_url = s.parser.get_publishers(attribute='homeurl') if home_url is not None and home_url != []: home_url = home_url[0] home_url = '' + home_url + "" else: home_url = None info_html = "" if home_url is not None: info_html += "

    " + home_url + "

    " if conditions is not None: info_html += "

    " + conditions_html + "

    " if copyright_links is not None: info_html += "

    " + copyright_links_html + "

    " if info_html != "": field.add_message(info_html, state='info') def sherpa_romeo_journal_process(form, field, submit=False): value = field.data or '' if value == "" or value.isspace(): return s = SherpaRomeoSearch() s.search_journal(value, 'exact') if s.error: field.add_message(s.error_message, state='info') return if s.get_num_hits() == 1: issn = s.parser.get_journals(attribute='issn') if issn != [] and issn is not None: issn = issn[0] publisher = s.parser.get_publishers(journal=value) if publisher is not None and publisher != []: if hasattr(form, 'issn'): form.issn.data = issn if hasattr(form, 'publisher'): form.publisher.data = publisher['name'] form.publisher.post_process(form) return field.add_message("Journal's Publisher not found", state='info') if hasattr(form, 'issn'): form.issn.data = issn if hasattr(form, 'publisher'): form.publisher.data = publisher form.publisher.post_process(form) else: field.add_message("Couldn't find ISSN.", state='info') def record_id_process(form, field, submit=False): value = field.data or '' if value == "" or value.isspace(): return def is_number(s): try: float(s) return True except ValueError: return False if is_number(field.data): json_reader = get_record(value) else: field.add_message("Record id must be a number!", state='error') return if json_reader is not None: webdeposit_json = form.uncook_json(json_reader, {}, value) #FIXME: update current json, past self, what do you mean?? :S field.add_message('Record was loaded successfully', state='info') form.process(MultiDict(webdeposit_json)) else: field.add_message("Record doesn't exist", state='info') diff --git a/invenio/modules/deposit/tasks.py b/invenio/modules/deposit/tasks.py index af71ff5cd..19d46b325 100644 --- a/invenio/modules/deposit/tasks.py +++ b/invenio/modules/deposit/tasks.py @@ -1,263 +1,264 @@ # -*- coding: utf-8 -*- # # This file is part of Invenio. # Copyright (C) 2012, 2013 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. """ """ import os from tempfile import mkstemp from flask import current_app, abort from flask.ext.login import current_user from invenio.legacy.bibsched.bibtask import task_low_level_submission, \ bibtask_allocate_sequenceid -from invenio.legacy.bibfield.bibfield_jsonreader import JsonReader +from invenio.modules.records.api import Record from invenio.config import CFG_TMPSHAREDDIR from invenio.legacy.dbquery import run_sql from invenio.modules.deposit.models import Deposition, Agent, \ DepositionDraftCacheManager from invenio.ext.logging import register_exception try: from invenio.pidstore_model import PersistentIdentifier HAS_PIDSUPPORT = True except ImportError: HAS_PIDSUPPORT = False def authorize_user(action, **params): """ Check if current user is authorized to perform the action. """ def _authorize_user(obj, dummy_eng): from invenio.modules.access.engine import acc_authorize_action auth, message = acc_authorize_action( current_user.get_id(), action, **dict((k, v() if callable(v) else v) for (k, v) in params.items())) if auth != 0: current_app.logger.info(message) abort(401) return _authorize_user def prefill_draft(form_class, draft_id='_default', clear=True): """ Fill draft values with values from pre-filled cache """ def _prefill_draft(obj, eng): draft_cache = DepositionDraftCacheManager.get() if draft_cache.has_data(): d = Deposition(obj) draft_cache.fill_draft( d, draft_id, form_class=form_class, clear=clear ) d.update() return _prefill_draft def render_form(form_class, draft_id='_default'): """ Renders a form if the draft associated with it has not yet been completed. :param form_class: The form class which should be rendered. :param draft_id: The name of the draft to create. Must be specified if you put more than two ``render_form'''s in your deposition workflow. """ def _render_form(obj, eng): d = Deposition(obj) draft = d.get_or_create_draft(draft_id, form_class=form_class) if draft.is_completed(): eng.jumpCallForward(1) else: form = draft.get_form(validate_draft=draft.validate) form.validate = True d.set_render_context(dict( template_name_or_list=form.get_template(), deposition=d, deposition_type=( None if d.type.is_default() else d.type.get_identifier() ), uuid=d.id, draft=draft, form=form, my_depositions=Deposition.get_depositions( current_user, type=d.type ), )) d.update() eng.halt('Wait for form submission.') return _render_form def create_recid(): """ Create a new record id. """ def _create_recid(obj, dummy_eng): d = Deposition(obj) sip = d.get_latest_sip(include_sealed=False) if sip is None: raise Exception("No submission information package found.") if 'recid' not in sip.metadata: sip.metadata['recid'] = run_sql( "INSERT INTO bibrec (creation_date, modification_date) " "VALUES (NOW(), NOW())" ) d.update() return _create_recid def mint_pid(pid_field='doi', pid_creator=None, pid_store_type='doi', existing_pid_checker=None): """ Register a persistent identifier internally. :param pid_field: The recjson key for where to look for a pre-reserved pid. Defaults to 'pid'. :param pid_creator: Callable taking one argument (the recjson) that when called will generate and return a pid string. :param pid_store_type: The PID store type. Defaults to 'doi'. :param existing_pid_checker: A callable taking two arguments (pid_str, recjson) that will check if an pid found using ``pid_field'' should be registered or not. """ if not HAS_PIDSUPPORT: def _mint_pid_dummy(dummy_obj, dummy_eng): pass return _mint_pid_dummy def _mint_pid(obj, dummy_eng): d = Deposition(obj) recjson = d.get_latest_sip(include_sealed=False).metadata if 'recid' not in recjson: raise Exception("'recid' not found in sip metadata.") pid_text = None pid = recjson.get(pid_field, None) if not pid: # No pid found in recjson, so create new pid with user supplied # function. current_app.logger.info("Registering pid %s" % pid_text) pid_text = recjson[pid_field] = pid_creator(recjson) else: # Pid found - check if it should be minted if existing_pid_checker and existing_pid_checker(pid, recjson): pid_text = pid # Create an assign pid internally - actually registration will happen # asynchronously later. if pid_text: current_app.logger.info("Registering pid %s" % pid_text) pid_obj = PersistentIdentifier.create(pid_store_type, pid_text) if pid_obj is None: pid_obj = PersistentIdentifier.get(pid_store_type, pid_text) try: pid_obj.assign("rec", recjson['recid']) except Exception: register_exception(alert_admin=True) d.update() return _mint_pid def prepare_sip(): """ Prepare a submission information package """ def _prepare_sip(obj, dummy_eng): d = Deposition(obj) sip = d.get_latest_sip(include_sealed=False) if sip is None: sip = d.create_sip() sip.metadata['fft'] = sip.metadata['files'] del sip.metadata['files'] sip.agents = [Agent(role='creator', from_request_context=True)] d.update() return _prepare_sip def finalize_record_sip(): """ Finalizes the SIP by generating the MARC and storing it in the SIP. """ def _finalize_sip(obj, dummy_eng): d = Deposition(obj) sip = d.get_latest_sip(include_sealed=False) - jsonreader = JsonReader() + json = Record() for k, v in sip.metadata.items(): - jsonreader[k] = v + json.set(k, v, extend=True) - sip.package = jsonreader.legacy_export_as_marc() + sip.package = json.legacy_export_as_marc() - current_app.logger.info(jsonreader['__error_messages']) + current_app.logger.info(json['__meta_metadata__']['__errors__']) + current_app.logger.info(json['__meta_metadata__']['__continuable_errors__']) current_app.logger.info(sip.package) d.update() return _finalize_sip def upload_record_sip(): """ Generates the record from marc. The function requires the marc to be generated, so the function export_marc_from_json must have been called successfully before """ def create(obj, dummy_eng): current_app.logger.info("Upload sip") d = Deposition(obj) sip = d.get_latest_sip(include_sealed=False) sip.seal() tmp_file_fd, tmp_file_path = mkstemp( prefix="webdeposit-%s-%s" % (d.id, sip.uuid), suffix='.xml', dir=CFG_TMPSHAREDDIR, ) os.write(tmp_file_fd, sip.package) os.close(tmp_file_fd) # Trick to have access to task_sequence_id in subsequent tasks. d.workflow_object.task_sequence_id = bibtask_allocate_sequenceid() task_low_level_submission( 'bibupload', 'webdeposit', '-r' if 'recid' in sip.metadata else '-i', tmp_file_path, '-P5', '-I', str(d.workflow_object.task_sequence_id) ) d.update() return create diff --git a/invenio/modules/documents/api.py b/invenio/modules/documents/api.py index 8324abb54..9709e170a 100644 --- a/invenio/modules/documents/api.py +++ b/invenio/modules/documents/api.py @@ -1,34 +1,34 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. from invenio.modules.jsonalchemy.wrappers import SmartJson from invenio.modules.jsonalchemy.jsonext.engines.sqlalchemy import SQLAlchemyStorage from invenio.modules.jsonalchemy.jsonext.readers.json_reader import reader from .models import Document as DocumentModel class Document(SmartJson): storage_engine = SQLAlchemyStorage(DocumentModel) @classmethod def create(cls, data, model='common_document'): - record = reader(data, model=model) + record = reader(data, namespace='documentext', model=model) document = cls(record.translate()) return cls.storage_engine.save_one(document.dumps()) diff --git a/invenio/modules/documents/documentext/__init__.py b/invenio/modules/documents/documentext/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/modules/documents/documentext/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/modules/documents/documentext/fields/__init__.py b/invenio/modules/documents/documentext/fields/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/modules/documents/documentext/fields/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/modules/documents/documentext/fields/core_fields.cfg b/invenio/modules/documents/documentext/fields/base.cfg similarity index 58% rename from invenio/modules/documents/documentext/fields/core_fields.cfg rename to invenio/modules/documents/documentext/fields/base.cfg index f244e8446..8906b6e90 100644 --- a/invenio/modules/documents/documentext/fields/core_fields.cfg +++ b/invenio/modules/documents/documentext/fields/base.cfg @@ -1,49 +1,45 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. @persistent_identifier(0) uuid: + """ + This is the main persistent identifier of a document and will be used + internally as this, therefore the pid important should always be '0'. + """ + schema: + {'_id': {'type':'objectid'}} checker: check_field_existence(1, continuable=False) - documentation: - """ - This is the main persistent identifier of a document and will be used - internally as this, therefore the pid important should always be '0'. - """ version: - documentation: - """Document version""" + """Document version""" parent: - calculated: - @do_not_cache - @parse_first(('parent_uuid', )) - @depends_on(('parent_uuid', )) - invenio.modules.documents.api.Document.storage_engine.get_one(self.get('parent_uuid')) - documentation: - """ - Return parent object - """ + """ + Return parent object + """ + # calculated: + # @depends_on(('parent_uuid', )) + # invenio.modules.documents.api.Document.storage_engine.get_one(self.get('parent_uuid')) parent_uuid: - documentation: - """ - FIXME: add checker if not None document_exists(parent_uuid) - """ + """ + FIXME: add checker if not None document_exists(parent_uuid) + """ diff --git a/invenio/modules/documents/documentext/models/__init__.py b/invenio/modules/documents/documentext/models/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/modules/documents/documentext/models/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/modules/documents/documentext/models/common_document.cfg b/invenio/modules/documents/documentext/models/base.cfg similarity index 82% rename from invenio/modules/documents/documentext/models/common_document.cfg rename to invenio/modules/documents/documentext/models/base.cfg index 0421a11f4..24b12bd12 100644 --- a/invenio/modules/documents/documentext/models/common_document.cfg +++ b/invenio/modules/documents/documentext/models/base.cfg @@ -1,31 +1,29 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. +""" +Common fields in all the documents inside any Invenio installation. +""" + fields: - @inherit_from(('common', )) + @inherit_from(('base', )) _id = uuid version parent parent_uuid - modification_date - creation_date -documentation: - """ - Common fields in all the documents inside any Invenio installation. - """ diff --git a/invenio/modules/formatter/api.py b/invenio/modules/formatter/api.py index 09f92ee62..ba8ee4c35 100644 --- a/invenio/modules/formatter/api.py +++ b/invenio/modules/formatter/api.py @@ -1,534 +1,534 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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. """ invenio.modules.formatter.api ----------------------------------------- Database access related functions for BibFormat engine and administration pages. """ import zlib from invenio.ext.sqlalchemy import db from sqlalchemy.exc import SQLAlchemyError from invenio.utils.date import convert_datetime_to_utc_string, strftime -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.search.models import Tag from .models import Format, Formatname, Bibfmt def get_creation_date(sysno, fmt="%Y-%m-%dT%H:%M:%SZ"): """ Returns the creation date of the record 'sysno'. :param sysno: the record ID for which we want to retrieve creation date :param fmt: output format for the returned date :return: creation date of the record :rtype: string """ try: return convert_datetime_to_utc_string( Bibrec.query.get(sysno).creation_date, fmt) except SQLAlchemyError: return "" def get_modification_date(sysno, fmt="%Y-%m-%dT%H:%M:%SZ"): """ Returns the date of last modification for the record 'sysno'. :param sysno: the record ID for which we want to retrieve modification date :param fmt: output format for the returned date :return: modification date of the record :rtype: string """ try: return convert_datetime_to_utc_string( Bibrec.query.get(sysno).modification_date, fmt) except SQLAlchemyError: return "" ## XML Marc related functions def get_tag_from_name(name): """ Returns the marc code corresponding the given name :param name: name for which we want to retrieve the tag :return: a tag corresponding to X{name} or None if not found """ try: return Tag.query.filter(Tag.name.like(name)).one().value except SQLAlchemyError: return None def get_tags_from_name(name): """ Returns the marc codes corresponding the given name, ordered by value :param name: name for which we want to retrieve the tags :return: list of tags corresponding to X{name} or None if not found """ try: return [tag.value for tag in Tag.query.filter(Tag.name.like(name)) .order_by(Tag.value).all()] except SQLAlchemyError: return None def tag_exists_for_name(name): """ Returns True if a tag exists for name in 'tag' table. :param name: name for which we want to check if a tag exist :return: True if a tag exist for X{name} or False """ return (Tag.query.filter(Tag.name.like(name)).count() > 0) def get_name_from_tag(tag): """ Returns the name corresponding to a marc code :param tag: tag to consider :return: a name corresponding to X{tag} """ try: return Tag.query.filter(Tag.value.like(tag)).one().name except SQLAlchemyError: return None def name_exists_for_tag(tag): """ Returns True if a name exists for tag in 'tag' table. :param tag: tag for which we want to check if a name exist :return: True if a name exist for X{tag} or False """ return (Tag.query.filter(Tag.value.like(tag)).count() > 0) def get_all_name_tag_mappings(): """ Return the list of mappings name<->tag from 'tag' table. The returned object is a dict with name as key (if 2 names are the same we will take the value of one of them, as we cannot make the difference in format templates) :return: a dict containing list of mapping in 'tag' table """ result = dict() for tag in Tag.query.all(): result[tag.name] = tag.value return result ## Output formats related functions def get_format_by_code(code): """ Returns the output format object given by code in the database. Output formats are located inside 'format' table :param code: the code of an output format :return: Format object with given ID. None if not found """ f_code = code if len(code) > 6: f_code = code[:6] try: return Format.query.filter(Format.code == f_code.lower()).one() except SQLAlchemyError: return None def get_format_property(code, property_name, default_value=None): """ Returns the value of a property of the output format given by code. If code or property does not exist, return default_value :param code: the code of the output format to get the value from :param property_name: name of property to return :param default_value: value to be returned if format not found :return: output format property value """ return getattr(get_format_by_code(code), property_name, default_value) def set_format_property(code, property_name, value): """ Sets the property of an output format, given by its code If 'code' does not exist, create format :param code: the code of the output format to update :param property_name: name of property to set :param value: value to assign """ format = get_format_by_code(code) if format is None: format = Format() setattr(format, property_name, value) if(property == 'name'): format.set_name(value) db.session.add(format) db.session.commit() def get_output_format_id(code): """ Returns the id of output format given by code in the database. Output formats are located inside 'format' table :param code: the code of an output format :return: the id in the database of the output format. None if not found """ return get_format_property(code, 'id', None) def add_output_format(code, name="", description="", content_type="text/html", visibility=1): """ Add output format into format table. If format with given code already exists, do nothing :param code: the code of the new format :param name: a new for the new format :param description: a description for the new format :param content_type: the content_type (if applicable) of the new output format :param visibility: if the output format is shown to users (1) or not (0) :return: None """ format = get_format_by_code(code) if format is None: format = Format() format.code = code.lower() format.description = description format.content_type = content_type format.visibility = visibility format.set_name(name) db.session.add(format) db.session.commit() def remove_output_format(code): """ Removes the output format with 'code' If code does not exist in database, do nothing The function also removes all localized names in formatname table :param code: the code of the output format to remove :return: None """ format = get_format_by_code(code) if format is not None: db.session.query(Formatname)\ .filter(Formatname.id_format == format.id).delete() db.session.delete(format) db.session.commit() def get_output_format_description(code): """ Returns the description of the output format given by code If code or description does not exist, return empty string :param code: the code of the output format to get the description from :return: output format description """ return get_format_property(code, 'description', '') def set_output_format_description(code, description): """ Sets the description of an output format, given by its code If 'code' does not exist, create format :param code: the code of the output format to update :param description: the new description :return: None """ set_format_property(code, 'description', description) def get_output_format_visibility(code): """ Returns the visibility of the output format, given by its code If code does not exist, return 0 :param code: the code of an output format :return: output format visibility (0 if not visible, 1 if visible """ visibility = get_format_property(code, 'visibility', 0) if visibility is not None and int(visibility) in range(0, 2): return int(visibility) else: return 0 def set_output_format_visibility(code, visibility): """ Sets the visibility of an output format, given by its code If 'code' does not exist, create format :param code: the code of the output format to update :param visibility: the new visibility (0: not visible, 1:visible) :return: None """ set_format_property(code, 'visibility', visibility) def get_existing_content_types(): """ Returns the list of all MIME content-types used in existing output formats. Always returns at least a list with 'text/html' :return: a list of content-type strings """ types = db.session.query(Format.content_type).distinct() if 'text/html' not in types: types.append('text/html') return types def get_output_format_content_type(code): """ Returns the content_type of the output format given by code If code or content_type does not exist, return empty string :param code: the code of the output format to get the description from :return: output format content_type """ return get_format_property(code, 'content_type', '') or '' def set_output_format_content_type(code, content_type): """ Sets the content_type of an output format, given by its code If 'code' does not exist, create format :param code: the code of the output format to update :param content_type: the content type for the format :return: None """ set_format_property(code, 'content_type', content_type) def get_output_format_names(code): """ Returns the localized names of the output format designated by 'code' The returned object is a dict with keys 'ln' (for long name) and 'sn' (for short name), containing each a dictionary with languages as keys. The key 'generic' contains the generic name of the output format (for use in admin interface) For eg:: {'ln': {'en': "a long name", 'fr': "un long nom", 'de': "ein lange Name"}, 'sn':{'en': "a name", 'fr': "un nom", 'de': "ein Name"} 'generic': "a name"} The returned dictionary is never None. The keys 'ln' and 'sn' are always present. However only languages present in the database are in dicts 'sn' and 'ln'. Language "CFG_SITE_LANG" is always in dict. The localized names of output formats are located in formatname table. :param code: the code of the output format to get the names from :return: a dict containing output format names """ out = {'sn': {}, 'ln': {}, 'generic': ''} format = get_format_by_code(code) if format is None: return out out['generic'] = format.name for fname in Formatname.query\ .filter(Formatname.id_format == format.id).all(): if fname.type == 'sn' or fname.type == 'ln': out[fname.type][fname.ln] = fname.value return out def set_output_format_name(code, name, lang="generic", type='ln'): """ Sets the name of an output format given by code. if 'type' different from 'ln' or 'sn', do nothing if 'name' exceeds 256 chars, 'name' is truncated to first 256 chars. if 'code' does not correspond to exisiting output format, create format if "generic" is given as lang The localized names of output formats are located in formatname table. :param code: the code of an ouput format :param type: either 'ln' (for long name) and 'sn' (for short name) :param lang: the language in which the name is given :param name: the name to give to the output format :return: None """ if type.lower() != "sn" and type.lower() != "ln": return format = get_format_by_code(code) if format is None and lang == "generic" and type.lower() == "ln": # Create output format inside table if it did not exist # Happens when the output format was added not through web interface format = Format() if format is not None: format.set_name(name, lang, type) def change_output_format_code(old_code, new_code): """ Change the code of an output format :param old_code: the code of the output format to change :param new_code: the new code :return: None """ set_format_property(old_code, 'code', new_code.lower()) def get_preformatted_record(recID, of, decompress=zlib.decompress): """ Returns the preformatted record with id 'recID' and format 'of' If corresponding record does not exist for given output format, returns None :param recID: the id of the record to fetch :param of: the output format code :param decompress: the method used to decompress the preformatted record in database :return: formatted record as String, or None if not exist """ try: value = Bibfmt.query\ .filter(Bibfmt.id_bibrec == recID)\ .filter(Bibfmt.format == of)\ .one().value return str(decompress(value)) except SQLAlchemyError: return None # Decide whether to use DB slave: # if of in ('xm', 'recstruct'): # run_on_slave = False # for master formats, use DB master # else: # run_on_slave = True # for other formats, we can use DB slave def get_preformatted_record_date(recID, of): """ Returns the date of the last update of the cache for the considered preformatted record in bibfmt If corresponding record does not exist for given output format, returns None :param recID: the id of the record to fetch :param of: the output format code :return: the date of the last update of the cache, or None if not exist """ try: last_updated = Bibfmt.query\ .filter(Bibfmt.id_bibrec == recID)\ .filter(Bibfmt.format == of)\ .one().last_updated return strftime("%Y-%m-%d %H:%M:%S", last_updated) except SQLAlchemyError: return None ## def keep_formats_in_db(output_formats): ## """ ## Remove from db formats that are not in the list ## TOBE USED ONLY ONCE OLD BIBFORMAT IS REMOVED ## (or old behaviours will be erased...) ## """ ## query = "SELECT code FROM format" ## res = run_sql(query) ## for row in res: ## if not row[0] in output_formats: ## query = "DELETE FROM format WHERE code='%s'"%row[0] ## def add_formats_in_db(output_formats): ## """ ## Add given formats in db (if not already there) ## """ ## for output_format in output_format: ## if get_format_from_db(output_format) is None: ## #Add new ## query = "UPDATE TABLE format " ## else: ## #Update ## query = "UPDATE TABLE format " ## query = "UPDATE TABLE format " ## res = run_sql(query) ## for row in res: ## if not row[0] in output_formats: ## query = "DELETE FROM format WHERE code='%s'"%row[0] diff --git a/invenio/modules/formatter/engine.py b/invenio/modules/formatter/engine.py index 8045777bc..80c6bcb0d 100644 --- a/invenio/modules/formatter/engine.py +++ b/invenio/modules/formatter/engine.py @@ -1,2256 +1,2256 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2013 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. """ Formats a single XML Marc record using specified format. There is no API for the engine. Instead use module L{bibformat}. You can have a look at the various escaping modes available in X{BibFormatObject} in function L{escape_field} Still it is useful sometimes for debugging purpose to use the L{BibFormatObject} class directly. For eg: >>> from invenio.modules.formatter.engine import BibFormatObject >>> bfo = BibFormatObject(102) >>> bfo.field('245__a') The order Rodentia in South America >>> from invenio.modules.formatter.format_elements import bfe_title >>> bfe_title.format_element(bfo) The order Rodentia in South America @see: bibformat.py, bibformat_utils.py """ __revision__ = "$Id$" import re import sys import os import inspect import traceback import zlib import cgi import types from flask import has_app_context from operator import itemgetter from werkzeug.utils import cached_property from invenio.base.globals import cfg from invenio.base.utils import (autodiscover_template_context_functions, autodiscover_format_elements) from invenio.config import \ CFG_PATH_PHP, \ CFG_BINDIR, \ CFG_SITE_LANG from invenio.ext.logging import \ register_exception from invenio.legacy.bibrecord import \ create_record, \ record_get_field_instances, \ record_get_field_value, \ record_get_field_values, \ record_xml_output from . import registry from .engines.xslt import format from invenio.legacy.dbquery import run_sql from invenio.base.i18n import \ language_list_long, \ wash_language, \ gettext_set_language from . import api as bibformat_dblayer from .config import \ CFG_BIBFORMAT_TEMPLATES_DIR, \ CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION, \ CFG_BIBFORMAT_FORMAT_JINJA_TEMPLATE_EXTENSION, \ CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION, \ CFG_BIBFORMAT_OUTPUTS_PATH, \ InvenioBibFormatError from invenio.modules.formatter.utils import \ record_get_xml, \ parse_tag from invenio.utils.html import \ HTMLWasher, \ CFG_HTML_BUFFER_ALLOWED_TAG_WHITELIST, \ CFG_HTML_BUFFER_ALLOWED_ATTRIBUTE_WHITELIST from invenio.modules.knowledge.api import get_kbr_values from invenio.ext.template import render_template_to_string from HTMLParser import HTMLParseError from invenio.utils.shell import escape_shell_arg if CFG_PATH_PHP: #Remove when call_old_bibformat is removed from xml.dom import minidom import tempfile # Cache for data we have already read and parsed format_templates_cache = {} format_elements_cache = {} format_outputs_cache = {} html_field = '' # String indicating that field should be # treated as HTML (and therefore no escaping of # HTML tags should occur. # Appears in some field values. washer = HTMLWasher() # Used to remove dangerous tags from HTML # sources # Regular expression for finding ... tag in format templates pattern_lang = re.compile(r''' #closing start tag (?P.*?) #anything but the next group (greedy) () #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Builds regular expression for finding each known language in tags ln_pattern_text = r"<(" for lang in language_list_long(enabled_langs_only=False): ln_pattern_text += lang[0] +r"|" ln_pattern_text = ln_pattern_text.rstrip(r"|") ln_pattern_text += r")>(.*?)" ln_pattern = re.compile(ln_pattern_text, re.IGNORECASE | re.DOTALL) # Regular expression for finding text to be translated translation_pattern = re.compile(r'_\((?P.*?)\)_', \ re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tag in format templates pattern_format_template_name = re.compile(r''' #closing start tag (?P.*?) #name value. any char that is not end tag ()(\n)? #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tag in format templates pattern_format_template_desc = re.compile(r''' #closing start tag (?P.*?) #description value. any char that is not end tag (\n)? #end tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding tags in format templates pattern_tag = re.compile(r''' [^/\s]+) #any char but a space or slash \s* #any number of spaces (?P(\s* #params here (?P([^=\s])*)\s* #param name: any chars that is not a white space or equality. Followed by space(s) =\s* #equality: = followed by any number of spaces (?P[\'"]) #one of the separators (?P.*?) #param value: any chars that is not a separator like previous one (?P=sep) #same separator as starting one )*) #many params \s* #any number of spaces (/)?> #end of the tag ''', re.IGNORECASE | re.DOTALL | re.VERBOSE) # Regular expression for finding params inside tags in format templates pattern_function_params = re.compile(''' (?P([^=\s])*)\s* # Param name: any chars that is not a white space or equality. Followed by space(s) =\s* # Equality: = followed by any number of spaces (?P[\'"]) # One of the separators (?P.*?) # Param value: any chars that is not a separator like previous one (?P=sep) # Same separator as starting one ''', re.VERBOSE | re.DOTALL ) # Regular expression for finding format elements "params" attributes # (defined by @param) pattern_format_element_params = re.compile(''' @param\s* # Begins with AT param keyword followed by space(s) (?P[^\s=]*):\s* # A single keyword and comma, then space(s) #(=\s*(?P[\'"]) # Equality, space(s) and then one of the separators #(?P.*?) # Default value: any chars that is not a separator like previous one #(?P=sep) # Same separator as starting one #)?\s* # Default value for param is optional. Followed by space(s) (?P.*) # Any text that is not end of line (thanks to MULTILINE parameter) ''', re.VERBOSE | re.MULTILINE) # Regular expression for finding format elements "see also" attribute # (defined by @see) pattern_format_element_seealso = re.compile('''@see:\s*(?P.*)''', re.VERBOSE | re.MULTILINE) #Regular expression for finding 2 expressions in quotes, separated by #comma (as in template("1st","2nd") ) #Used when parsing output formats ## pattern_parse_tuple_in_quotes = re.compile(''' ## (?P[\'"]) ## (?P.*) ## (?P=sep1) ## \s*,\s* ## (?P[\'"]) ## (?P.*) ## (?P=sep2) ## ''', re.VERBOSE | re.MULTILINE) sub_non_alnum = re.compile('[^0-9a-zA-Z]+') fix_tag_name = lambda s: sub_non_alnum.sub('_', s.lower()) from invenio.utils.memoise import memoize class LazyTemplateContextFunctionsCache(object): """Loads bibformat elements using plugin builder and caches results.""" @cached_property def template_context_functions(self): """Returns template context functions""" modules = autodiscover_template_context_functions() elem = {} for m in modules: register_func = getattr(m, 'template_context_function', None) if register_func and isinstance(register_func, types.FunctionType): elem[m.__name__.split('.')[-1]] = register_func return elem @memoize def bibformat_elements(self, modules=None): """Returns bibformat elements.""" if modules is None: modules = registry.format_elements elem = {} for m in modules: if m is None: continue name = m.__name__.split('.')[-1] filename = m.__file__[:-1] if m.__file__.endswith('.pyc') \ else m.__file__ register_func = getattr(m, 'format_element', getattr(m, 'format', None)) escape_values = getattr(m, 'escape_values', None) if register_func and isinstance(register_func, types.FunctionType): register_func._escape_values = escape_values register_func.__file__ = filename elem[name] = register_func return elem @cached_property def functions(self): def insert(name): def _bfe_element(bfo, **kwargs): # convert to utf-8 for legacy app kwargs = dict((k, v.encode('utf-8') if isinstance(v, unicode) else v) for k, v in kwargs.iteritems()) format_element = get_format_element(name) (out, dummy) = eval_format_element(format_element, bfo, kwargs) # returns unicode for jinja2 return out.decode('utf-8') return _bfe_element # Old bibformat templates tfn_from_files = dict((name.lower(), insert(name.lower())) for name in self.bibformat_elements().keys()) # Update with new template context functions tfn_from_files.update(self.template_context_functions) bfe_from_tags = {} if has_app_context(): from invenio.ext.sqlalchemy import db from invenio.modules.search.models import Tag # get functions from tag table bfe_from_tags = dict(('bfe_'+fix_tag_name(name), insert(fix_tag_name(name))) for name in map(itemgetter(0), db.session.query(Tag.name).all())) # overwrite functions from tag table with functions from files bfe_from_tags.update(tfn_from_files) return bfe_from_tags TEMPLATE_CONTEXT_FUNCTIONS_CACHE = LazyTemplateContextFunctionsCache() def get_format_element_path(filename): if filename.endswith('.py'): filename = filename[:-3] return TEMPLATE_CONTEXT_FUNCTIONS_CACHE.bibformat_elements()[filename].__file__ def call_old_bibformat(recID, of="HD", on_the_fly=False, verbose=0): """ FIXME: REMOVE FUNCTION WHEN MIGRATION IS DONE Calls BibFormat for the record RECID in the desired output format 'of'. Note: this functions always try to return HTML, so when bibformat returns XML with embedded HTML format inside the tag FMT $g, as is suitable for prestoring output formats, we perform un-XML-izing here in order to return HTML body only. @param recID: record ID to format @param of: output format to be used for formatting @param on_the_fly: if False, try to return an already preformatted version of the record in the database @param verbose: verbosity @return: a formatted output using old BibFormat """ out = "" res = [] if not on_the_fly: # look for formatted record existence: query = "SELECT value, last_updated FROM bibfmt WHERE "\ "id_bibrec='%s' AND format='%s'" % (recID, of) res = run_sql(query, None, 1) if res: # record 'recID' is formatted in 'of', so print it if verbose == 9: last_updated = res[0][1] out += """\n
    Found preformatted output for record %i (cache updated on %s). """ % (recID, last_updated) decompress = zlib.decompress return "%s" % decompress(res[0][0]) else: # record 'recID' is not formatted in 'of', # so try to call BibFormat on the fly or use default format: if verbose == 9: out += """\n
    Formatting record %i on-the-fly with old BibFormat.
    """ % recID # Retrieve MARCXML # Build it on-the-fly only if 'call_old_bibformat' was called # with format=xm and on_the_fly=True xm_record = record_get_xml(recID, 'xm', on_the_fly=(on_the_fly and of == 'xm')) ## import platform ## # Some problem have been found using either popen() or os.system(). ## # Here is a temporary workaround until the issue is solved. ## if platform.python_compiler().find('Red Hat') > -1: ## # use os.system (result_code, result_path) = tempfile.mkstemp() command = "( %s/bibformat otype=%s ) > %s" % \ (CFG_BINDIR, escape_shell_arg(of), result_path) (xm_code, xm_path) = tempfile.mkstemp() xm_file = open(xm_path, "w") xm_file.write(xm_record) xm_file.close() command = command + " <" + xm_path os.system(command) result_file = open(result_path,"r") bibformat_output = result_file.read() result_file.close() os.close(result_code) os.remove(result_path) os.close(xm_code) os.remove(xm_path) ## else: ## # use popen ## pipe_input, pipe_output, pipe_error = os.popen3(["%s/bibformat" % CFG_BINDIR, ## "otype=%s" % format], ## 'rw') ## pipe_input.write(xm_record) ## pipe_input.flush() ## pipe_input.close() ## bibformat_output = pipe_output.read() ## pipe_output.close() ## pipe_error.close() if bibformat_output.startswith(""): dom = minidom.parseString(bibformat_output) for e in dom.getElementsByTagName('subfield'): if e.getAttribute('code') == 'g': for t in e.childNodes: out += t.data.encode('utf-8') else: out += bibformat_output return out def format_record(recID, of, ln=CFG_SITE_LANG, verbose=0, search_pattern=None, xml_record=None, user_info=None, qid=""): """ Formats a record given output format. Main entry function of bibformat engine. Returns a formatted version of the record in the specified language, search pattern, and with the specified output format. The function will define which format template must be applied. You can either specify an record ID to format, or give its xml representation. if 'xml_record' is not None, then use it instead of recID. 'user_info' allows to grant access to some functionalities on a page depending on the user's priviledges. 'user_info' is the same object as the one returned by 'webuser.collect_user_info(req)' @param recID: the ID of record to format @param of: an output format code (or short identifier for the output format) @param ln: the language to use to format the record @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, stop if error in format elements 9: errors and warnings, stop if error (debug mode )) @param search_pattern: list of strings representing the user request in web interface @param xml_record: an xml string representing the record to format @param user_info: the information of the user who will view the formatted page @return: formatted record """ if search_pattern is None: search_pattern = [] out = "" ln = wash_language(ln) _ = gettext_set_language(ln) # Temporary workflow (during migration of formats): # Call new BibFormat # But if format not found for new BibFormat, then call old BibFormat #Create a BibFormat Object to pass that contain record and context bfo = BibFormatObject(recID, ln, search_pattern, xml_record, user_info, of) if of.lower() != 'xm' and \ (not bfo.get_record() or len(bfo.get_record()) <= 1): # Record only has recid: do not format, excepted # for xm format return "" #Find out which format template to use based on record and output format. template = decide_format_template(bfo, of) if verbose == 9 and template is not None: out += """\n
    Using %s template for record %i. """ % (template, recID) ############### FIXME: REMOVE WHEN MIGRATION IS DONE ############### path = "%s%s%s" % (cfg['CFG_BIBFORMAT_TEMPLATES_PATH'], os.sep, template) if template is None or not ( os.access(path, os.R_OK) or template.endswith("." + CFG_BIBFORMAT_FORMAT_JINJA_TEMPLATE_EXTENSION)): # template not found in new BibFormat. Call old one if verbose == 9: if template is None: out += """\n
    No template found for output format %s and record %i. (Check invenio.err log file for more details) """ % (of, recID) else: out += """\n
    Template %s could not be read. """ % (template) if CFG_PATH_PHP and os.path.isfile(os.path.join(CFG_BINDIR, 'bibformat')): if verbose == 9: out += """\n
    Using old BibFormat for record %s. """ % recID return out + call_old_bibformat(recID, of=of, on_the_fly=True, verbose=verbose) ############################# END ################################## try: raise InvenioBibFormatError(_('No template could be found for output format %(code)s.', code=of)) except InvenioBibFormatError, exc: register_exception(req=bfo.req) if verbose > 5: return out + str(exc.message) return out # Format with template out_ = format_with_format_template(template, bfo, verbose, qid=qid) out += out_ return out def decide_format_template(bfo, of): """ Returns the format template name that should be used for formatting given output format and L{BibFormatObject}. Look at of rules, and take the first matching one. If no rule matches, returns None To match we ignore lettercase and spaces before and after value of rule and value of record @param bfo: a L{BibFormatObject} @param of: the code of the output format to use @return: name of a format template """ output_format = get_output_format(of) for rule in output_format['rules']: if rule['field'].startswith('00'): # Rule uses controlfield values = [bfo.control_field(rule['field']).strip()] #Remove spaces else: # Rule uses datafield values = bfo.fields(rule['field']) # loop over multiple occurences, but take the first match if len(values) > 0: for value in values: value = value.strip() #Remove spaces pattern = rule['value'].strip() #Remove spaces match_obj = re.match(pattern, value, re.IGNORECASE) if match_obj is not None and \ match_obj.end() == len(value): return rule['template'] template = output_format['default'] if template != '': return template else: return None def format_with_format_template(format_template_filename, bfo, verbose=0, format_template_code=None, qid=""): """ Format a record given a format template. Returns a formatted version of the record represented by bfo, in the language specified in bfo, and with the specified format template. If format_template_code is provided, the template will not be loaded from format_template_filename (but format_template_filename will still be used to determine if bft or xsl transformation applies). This allows to preview format code without having to save file on disk. @param format_template_filename: the dilename of a format template @param bfo: the object containing parameters for the current formatting @param format_template_code: if not empty, use code as template instead of reading format_template_filename (used for previews) @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: formatted text """ _ = gettext_set_language(bfo.lang) def translate(match): """ Translate matching values """ word = match.group("word") translated_word = _(word) return translated_word if format_template_code is not None: format_content = str(format_template_code) elif not format_template_filename.endswith("." + CFG_BIBFORMAT_FORMAT_JINJA_TEMPLATE_EXTENSION): format_content = get_format_template(format_template_filename)['code'] if format_template_filename is None or \ format_template_filename.endswith("." + CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION): # .bft filtered_format = filter_languages(format_content, bfo.lang) localized_format = translation_pattern.sub(translate, filtered_format) evaluated_format = eval_format_template_elements(localized_format, bfo, verbose) elif format_template_filename.endswith("." + CFG_BIBFORMAT_FORMAT_JINJA_TEMPLATE_EXTENSION): evaluated_format = '' #try: from functools import wraps - from invenio.legacy.bibfield import \ - create_record as bibfield_create_record, \ - get_record as bibfield_get_record + from invenio.modules.records.api import \ + create_record as new_create_record, \ + get_record as new_get_record from invenio.legacy.search_engine import print_record from flask.ext.login import current_user from invenio.base.helpers import unicodifier def _format_record(recid, of='hb', user_info=current_user, *args, **kwargs): return print_record(recid, format=of, user_info=user_info, *args, **kwargs) # Fixes unicode problems in Jinja2 templates. def encode_utf8(f): @wraps(f) def wrapper(*args, **kwds): return unicodifier(f(*args, **kwds)) return wrapper if bfo.recID: - record = bibfield_get_record(bfo.recID) + record = new_get_record(bfo.recID) else: - record = bibfield_create_record(bfo.xml_record, master_format='marc') + record = new_create_record(bfo.xml_record, master_format='marc') bfo.recID = bfo.recID if bfo.recID else 0 record.__getitem__ = encode_utf8(record.__getitem__) record.get = encode_utf8(record.get) evaluated_format = render_template_to_string( 'format/record/'+format_template_filename, recid=bfo.recID, record=record, format_record=_format_record, qid=qid, bfo=bfo, **TEMPLATE_CONTEXT_FUNCTIONS_CACHE.functions).encode('utf-8') #except Exception: # register_exception() else: #.xsl if bfo.xml_record: # bfo was initialized with a custom MARCXML xml_record = '\n' + \ record_xml_output(bfo.record) else: # Fetch MARCXML. On-the-fly xm if we are now formatting in xm xml_record = '\n' + \ record_get_xml(bfo.recID, 'xm', on_the_fly=False) # Transform MARCXML using stylesheet evaluated_format = format(xml_record, template_source=format_content) try: evaluated_format = evaluated_format.decode('utf8').encode('utf8') except: try: evaluated_format = evaluated_format.encode('utf8') except: evaluated_format = ''.encode('utf8') return evaluated_format def eval_format_template_elements(format_template, bfo, verbose=0): """ Evalutes the format elements of the given template and replace each element with its value. Prepare the format template content so that we can directly replace the marc code by their value. This implies: 1. Look for special tags 2. replace special tags by their evaluation @param format_template: the format template code @param bfo: the object containing parameters for the current formatting @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: tuple (result, errors) """ _ = gettext_set_language(bfo.lang) # First define insert_element_code(match), used in re.sub() function def insert_element_code(match): """ Analyses 'match', interpret the corresponding code, and return the result of the evaluation. Called by substitution in 'eval_format_template_elements(...)' @param match: a match object corresponding to the special tag that must be interpreted """ function_name = match.group("function_name") try: format_element = get_format_element(function_name, verbose) except Exception, e: format_element = None if verbose >= 5: return '' + \ cgi.escape(str(e)).replace('\n', '
    ') + \ '
    ' if format_element is None: try: raise InvenioBibFormatError( _('Could not find format element named %(function_name)s.', function_name=function_name)) except InvenioBibFormatError, exc: register_exception(req=bfo.req) if verbose >= 5: return '' + \ str(exc.message)+'' else: params = {} # Look for function parameters given in format template code all_params = match.group('params') if all_params is not None: function_params_iterator = pattern_function_params.finditer(all_params) for param_match in function_params_iterator: name = param_match.group('param') value = param_match.group('value') params[name] = value # Evaluate element with params and return (Do not return errors) (result, dummy) = eval_format_element(format_element, bfo, params, verbose) return result # Substitute special tags in the format by our own text. # Special tags have the form format = pattern_tag.sub(insert_element_code, format_template) return format def eval_format_element(format_element, bfo, parameters=None, verbose=0): """ Returns the result of the evaluation of the given format element name, with given L{BibFormatObject} and parameters. Also returns the errors of the evaluation. @param format_element: a format element structure as returned by get_format_element @param bfo: a L{BibFormatObject} used for formatting @param parameters: a dict of parameters to be used for formatting. Key is parameter and value is value of parameter @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: tuple (result, errors) """ if parameters is None: parameters = {} errors = [] #Load special values given as parameters prefix = parameters.get('prefix', "") suffix = parameters.get('suffix', "") default_value = parameters.get('default', "") escape = parameters.get('escape', "") output_text = '' _ = gettext_set_language(bfo.lang) # 3 possible cases: # a) format element file is found: we execute it # b) format element file is not found, but exist in tag table (e.g. bfe_isbn) # c) format element is totally unknown. Do nothing or report error if format_element is not None and format_element['type'] == "python": # a) We found an element with the tag name, of type "python" # Prepare a dict 'params' to pass as parameter to 'format' # function of element params = {} # Look for parameters defined in format element # Fill them with specified default values and values # given as parameters. # Also remember if the element overrides the 'escape' # parameter format_element_overrides_escape = False for param in format_element['attrs']['params']: name = param['name'] default = param['default'] params[name] = parameters.get(name, default) if name == 'escape': format_element_overrides_escape = True # Add BibFormatObject params['bfo'] = bfo # Execute function with given parameters and return result. function = format_element['code'] _ = gettext_set_language(bfo.lang) try: output_text = apply(function, (), params) except Exception, e: name = format_element['attrs']['name'] try: raise InvenioBibFormatError(_( 'Error when evaluating format element %(format_element)s with parameters %(parameters)s.', format_element=name, parameters=str(params))) except InvenioBibFormatError, exc: register_exception(req=bfo.req) errors.append(exc.message) if verbose >= 5: tb = sys.exc_info()[2] stack = traceback.format_exception(Exception, e, tb, limit=None) output_text = ''+ \ str(exc.message) + "".join(stack) +' ' # None can be returned when evaluating function if output_text is None: output_text = "" else: output_text = str(output_text) # Escaping: # (1) By default, everything is escaped in mode 1 # (2) If evaluated element has 'escape_values()' function, use # its returned value as escape mode, and override (1) # (3) If template has a defined parameter 'escape' (in allowed # values), use it, and override (1) and (2). If this # 'escape' parameter is overriden by the format element # (defined in the 'format' function of the element), leave # the escaping job to this element # (1) escape_mode = 1 # (2) escape_function = format_element['escape_function'] if escape_function is not None: try: escape_mode = apply(escape_function, (), {'bfo': bfo}) except Exception, e: try: raise InvenioBibFormatError(_('Escape mode for format element %s could not be retrieved. Using default mode instead.') % name) except InvenioBibFormatError, exc: register_exception(req=bfo.req) errors.append(exc.message) if verbose >= 5: tb = sys.exc_info()[2] output_text += ''+ \ str(exc.message) +' ' # (3) if escape in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: escape_mode = int(escape) # If escape is equal to 1, then escape all # HTML reserved chars. if escape_mode > 0 and not format_element_overrides_escape: output_text = escape_field(output_text, mode=escape_mode) # Add prefix and suffix if they have been given as parameters and if # the evaluation of element is not empty if output_text.strip() != "": output_text = prefix + output_text + suffix # Add the default value if output_text is empty if output_text == "": output_text = default_value return (output_text, errors) elif format_element is not None and format_element['type'] == "field": # b) We have not found an element in files that has the tag # name. Then look for it in the table "tag" # # # # Load special values given as parameters separator = parameters.get('separator ', "") nbMax = parameters.get('nbMax', "") escape = parameters.get('escape', "1") # By default, escape here # Get the fields tags that have to be printed tags = format_element['attrs']['tags'] output_text = [] # Get values corresponding to tags for tag in tags: p_tag = parse_tag(tag) values = record_get_field_values(bfo.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if len(values)>0 and isinstance(values[0], dict): #flatten dict to its values only values_list = map(lambda x: x.values(), values) #output_text.extend(values) for values in values_list: output_text.extend(values) else: output_text.extend(values) if nbMax != "": try: nbMax = int(nbMax) output_text = output_text[:nbMax] except: name = format_element['attrs']['name'] try: raise InvenioBibFormatError(_('"nbMax" parameter for %s must be an "int".') % name) except InvenioBibFormatError, exc: register_exception(req=bfo.req) errors.append(exc.message) if verbose >= 5: output_text = output_text.append(exc.message) # Add prefix and suffix if they have been given as parameters and if # the evaluation of element is not empty. # If evaluation is empty string, return default value if it exists. # Else return empty string if ("".join(output_text)).strip() != "": # If escape is equal to 1, then escape all # HTML reserved chars. if escape == '1': output_text = cgi.escape(separator.join(output_text)) else: output_text = separator.join(output_text) output_text = prefix + output_text + suffix else: #Return default value output_text = default_value return (output_text, errors) else: # c) Element is unknown try: raise InvenioBibFormatError(_('Could not find format element named %(format_element)s.', format_element=format_element)) except InvenioBibFormatError, exc: register_exception(req=bfo.req) errors.append(exc.message) if verbose < 5: return ("", errors) elif verbose >= 5: if verbose >= 9: sys.exit(exc.message) return ('' + \ str(exc.message)+'', errors) def filter_languages(format_template, ln='en'): """ Filters the language tags that do not correspond to the specified language. @param format_template: the format template code @param ln: the language that is NOT filtered out from the template @return: the format template with unnecessary languages filtered out """ # First define search_lang_tag(match) and clean_language_tag(match), used # in re.sub() function def search_lang_tag(match): """ Searches for the ... tag and remove inner localized tags such as , , that are not current_lang. If current_lang cannot be found inside ... , try to use 'CFG_SITE_LANG' @param match: a match object corresponding to the special tag that must be interpreted """ current_lang = ln def clean_language_tag(match): """ Return tag text content if tag language of match is output language. Called by substitution in 'filter_languages(...)' @param match: a match object corresponding to the special tag that must be interpreted """ if match.group(1) == current_lang: return match.group(2) else: return "" # End of clean_language_tag lang_tag_content = match.group("langs") # Try to find tag with current lang. If it does not exists, # then current_lang becomes CFG_SITE_LANG until the end of this # replace pattern_current_lang = re.compile(r"<("+current_lang+ \ r")\s*>(.*?)()", re.IGNORECASE | re.DOTALL) if re.search(pattern_current_lang, lang_tag_content) is None: current_lang = CFG_SITE_LANG cleaned_lang_tag = ln_pattern.sub(clean_language_tag, lang_tag_content) return cleaned_lang_tag # End of search_lang_tag filtered_format_template = pattern_lang.sub(search_lang_tag, format_template) return filtered_format_template def get_format_template(filename, with_attributes=False): """ Returns the structured content of the given formate template. if 'with_attributes' is true, returns the name and description. Else 'attrs' is not returned as key in dictionary (it might, if it has already been loaded previously):: {'code':"Some template code" 'attrs': {'name': "a name", 'description': "a description"} } @param filename: the filename of an format template @param with_attributes: if True, fetch the attributes (names and description) for format' @return: strucured content of format template """ _ = gettext_set_language(CFG_SITE_LANG) # Get from cache whenever possible global format_templates_cache if not filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION) and \ not filename.endswith(".xsl"): return None if format_templates_cache.has_key(filename): # If we must return with attributes and template exist in # cache with attributes then return cache. # Else reload with attributes if with_attributes and \ format_templates_cache[filename].has_key('attrs'): return format_templates_cache[filename] format_template = {'code': ""} try: path = registry.format_templates_lookup[filename] format_file = open(path) format_content = format_file.read() format_file.close() # Load format template code # Remove name and description if filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION): code_and_description = pattern_format_template_name.sub("", format_content, 1) code = pattern_format_template_desc.sub("", code_and_description, 1) else: code = format_content format_template['code'] = code except Exception, e: try: raise InvenioBibFormatError(_('Could not read format template named %(filename)s. %(error)s.', filename=filename, error=str(e))) except InvenioBibFormatError: register_exception() # Save attributes if necessary if with_attributes: format_template['attrs'] = get_format_template_attrs(filename) # Cache and return format_templates_cache[filename] = format_template return format_template def get_format_templates(with_attributes=False): """ Returns the list of all format templates, as dictionary with filenames as keys if 'with_attributes' is true, returns the name and description. Else 'attrs' is not returned as key in each dictionary (it might, if it has already been loaded previously):: [{'code':"Some template code" 'attrs': {'name': "a name", 'description': "a description"} }, ... } @param with_attributes: if True, fetch the attributes (names and description) for formats @return: the list of format templates (with code and info) """ format_templates = {} for filename in registry.format_templates: if filename.endswith("."+CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION) or \ filename.endswith(".xsl"): filename = os.path.basename(filename) format_templates[filename] = get_format_template(filename, with_attributes) return format_templates def get_format_template_attrs(filename): """ Returns the attributes of the format template with given filename The attributes are {'name', 'description'} Caution: the function does not check that path exists or that the format element is valid. @param filename: the name of a format template @return: a structure with detailed information about given format template """ _ = gettext_set_language(CFG_SITE_LANG) attrs = {} attrs['name'] = "" attrs['description'] = "" try: template_file = open(registry.format_templates_lookup[filename]) code = template_file.read() template_file.close() match = None if filename.endswith(".xsl"): # .xsl attrs['name'] = filename[:-4] else: # .bft match = pattern_format_template_name.search(code) if match is not None: attrs['name'] = match.group('name') else: attrs['name'] = filename match = pattern_format_template_desc.search(code) if match is not None: attrs['description'] = match.group('desc').rstrip('.') except Exception, e: try: raise InvenioBibFormatError(_('Could not read format template named %(filename)s. %(error)s.', filename=filename, error=str(e))) except InvenioBibFormatError: register_exception() attrs['name'] = filename return attrs def get_format_element(element_name, verbose=0, with_built_in_params=False): """ Returns the format element structured content. Return None if element cannot be loaded (file not found, not readable or invalid) The returned structure is:: {'attrs': {some attributes in dict. See get_format_element_attrs_from_*} 'code': the_function_code, 'type':"field" or "python" depending if element is defined in file or table, 'escape_function': the function to call to know if element output must be escaped} @param element_name: the name of the format element to load @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @param with_built_in_params: if True, load the parameters built in all elements @return: a dictionary with format element attributes """ _ = gettext_set_language(CFG_SITE_LANG) # Get from cache whenever possible global format_elements_cache errors = [] # Resolve filename and prepare 'name' as key for the cache filename = resolve_format_element_filename(element_name) if filename is not None: name = filename.upper() else: name = element_name.upper() if format_elements_cache.has_key(name): element = format_elements_cache[name] if not with_built_in_params or \ (with_built_in_params and \ element['attrs'].has_key('builtin_params')): return element if filename is None: # Element is maybe in tag table if bibformat_dblayer.tag_exists_for_name(element_name): format_element = {'attrs': get_format_element_attrs_from_table( \ element_name, with_built_in_params), 'code':None, 'escape_function':None, 'type':"field"} # Cache and returns format_elements_cache[name] = format_element return format_element else: try: raise InvenioBibFormatError(_('Format element %(element_name)s could not be found.', element_name=element_name)) except InvenioBibFormatError, exc: register_exception() if verbose >= 5: sys.stderr.write(exc.message) return None else: format_element = {} module_name = filename if module_name.endswith(".py"): module_name = module_name[:-3] # Load function 'format_element()' inside element try: function_format = TEMPLATE_CONTEXT_FUNCTIONS_CACHE.\ bibformat_elements()[module_name] format_element['code'] = function_format except KeyError: try: raise InvenioBibFormatError(_('Format element %(element_name)s has no function named "format".', element_name=element_name)) except InvenioBibFormatError, exc: register_exception() errors.append(exc.message) if verbose >= 5: sys.stderr.write(exc.message) if errors: if verbose >= 7: raise Exception, exc.message return None # Load function 'escape_values()' inside element format_element['escape_function'] = function_format._escape_values # Prepare, cache and return format_element['attrs'] = get_format_element_attrs_from_function( \ function_format, element_name, with_built_in_params) format_element['type'] = "python" format_elements_cache[name] = format_element return format_element def get_format_elements(with_built_in_params=False): """ Returns the list of format elements attributes as dictionary structure Elements declared in files have priority over element declared in 'tag' table The returned object has this format:: {element_name1: {'attrs': {'description':..., 'seealso':... 'params':[{'name':..., 'default':..., 'description':...}, ...] 'builtin_params':[{'name':..., 'default':..., 'description':...}, ...] }, 'code': code_of_the_element }, element_name2: {...}, ...} Returns only elements that could be loaded (not error in code) @return: a dict of format elements with name as key, and a dict as attributes @param with_built_in_params: if True, load the parameters built in all elements """ format_elements = {} mappings = bibformat_dblayer.get_all_name_tag_mappings() for name in mappings: format_elements[name.upper().replace(" ", "_").strip()] = get_format_element(name, with_built_in_params=with_built_in_params) for module in registry.format_elements: filename = os.path.basename(module.__file__) filename_test = filename.upper().replace(" ", "_") if filename_test.endswith(".PYC"): filename_test = filename_test[:-1] if filename_test.endswith(".PY") and not filename.upper().startswith("__INIT__.PY"): if filename_test.startswith("BFE_"): filename_test = filename_test[4:] element_name = filename_test[:-3] element = get_format_element(element_name, with_built_in_params=with_built_in_params) if element is not None: format_elements[element_name] = element return format_elements def get_format_element_attrs_from_function(function, element_name, with_built_in_params=False): """ Returns the attributes of the function given as parameter. It looks for standard parameters of the function, default values and comments in the docstring. The attributes are:: {'name' : "name of element" #basically the name of 'name' parameter 'description': "a string description of the element", 'seealso' : ["element_1.py", "element_2.py", ...] #a list of related elements 'params': [{'name':"param_name", #a list of parameters for this element (except 'bfo') 'default':"default value", 'description': "a description"}, ...], 'builtin_params': {name: {'name':"param_name",#the parameters builtin for all elem of this kind 'default':"default value", 'description': "a description"}, ...}, } @param function: the formatting function of a format element @param element_name: the name of the element @param with_built_in_params: if True, load the parameters built in all elements @return: a structure with detailed information of a function """ attrs = {} attrs['description'] = "" attrs['name'] = element_name.replace(" ", "_").upper() attrs['seealso'] = [] docstring = function.__doc__ if isinstance(docstring, str): # Look for function description in docstring #match = pattern_format_element_desc.search(docstring) description = docstring.split("@param")[0] description = description.split("@see:")[0] attrs['description'] = description.strip().rstrip('.') # Look for @see: in docstring match = pattern_format_element_seealso.search(docstring) if match is not None: elements = match.group('see').rstrip('.').split(",") for element in elements: attrs['seealso'].append(element.strip()) params = {} # Look for parameters in function definition (args, varargs, varkw, defaults) = inspect.getargspec(function) # Prepare args and defaults_list such that we can have a mapping # from args to defaults args.reverse() if defaults is not None: defaults_list = list(defaults) defaults_list.reverse() else: defaults_list = [] for arg, default in map(None, args, defaults_list): if arg == "bfo": #Don't keep this as parameter. It is hidden to users, and #exists in all elements of this kind continue param = {} param['name'] = arg if default is None: #In case no check is made inside element, we prefer to #print "" (nothing) than None in output param['default'] = "" else: param['default'] = default param['description'] = "(no description provided)" params[arg] = param if isinstance(docstring, str): # Look for AT param descriptions in docstring. # Add description to existing parameters in params dict params_iterator = pattern_format_element_params.finditer(docstring) for match in params_iterator: name = match.group('name') if params.has_key(name): params[name]['description'] = match.group('desc').rstrip('.') attrs['params'] = params.values() # Load built-in parameters if necessary if with_built_in_params: builtin_params = [] # Add 'prefix' parameter param_prefix = {} param_prefix['name'] = "prefix" param_prefix['default'] = "" param_prefix['description'] = """A prefix printed only if the record has a value for this element""" builtin_params.append(param_prefix) # Add 'suffix' parameter param_suffix = {} param_suffix['name'] = "suffix" param_suffix['default'] = "" param_suffix['description'] = """A suffix printed only if the record has a value for this element""" builtin_params.append(param_suffix) # Add 'default' parameter param_default = {} param_default['name'] = "default" param_default['default'] = "" param_default['description'] = """A default value printed if the record has no value for this element""" builtin_params.append(param_default) # Add 'escape' parameter param_escape = {} param_escape['name'] = "escape" param_escape['default'] = "" param_escape['description'] = """0 keeps value as it is. Refer to main documentation for escaping modes 1 to 7""" builtin_params.append(param_escape) attrs['builtin_params'] = builtin_params return attrs def get_format_element_attrs_from_table(element_name, with_built_in_params=False): """ Returns the attributes of the format element with given name in 'tag' table. Returns None if element_name does not exist in tag table. The attributes are:: {'name' : "name of element" #basically the name of 'element_name' parameter 'description': "a string description of the element", 'seealso' : [] #a list of related elements. Always empty in this case 'params': [], #a list of parameters for this element. Always empty in this case 'builtin_params': [{'name':"param_name", #the parameters builtin for all elem of this kind 'default':"default value", 'description': "a description"}, ...], 'tags':["950.1", 203.a] #the list of tags printed by this element } @param element_name: an element name in database @param element_name: the name of the element @param with_built_in_params: if True, load the parameters built in all elements @return: a structure with detailed information of an element found in DB """ attrs = {} tags = bibformat_dblayer.get_tags_from_name(element_name) field_label = "field" if len(tags)>1: field_label = "fields" attrs['description'] = "Prints %s %s of the record" % (field_label, ", ".join(tags)) attrs['name'] = element_name.replace(" ", "_").upper() attrs['seealso'] = [] attrs['params'] = [] attrs['tags'] = tags # Load built-in parameters if necessary if with_built_in_params: builtin_params = [] # Add 'prefix' parameter param_prefix = {} param_prefix['name'] = "prefix" param_prefix['default'] = "" param_prefix['description'] = """A prefix printed only if the record has a value for this element""" builtin_params.append(param_prefix) # Add 'suffix' parameter param_suffix = {} param_suffix['name'] = "suffix" param_suffix['default'] = "" param_suffix['description'] = """A suffix printed only if the record has a value for this element""" builtin_params.append(param_suffix) # Add 'separator' parameter param_separator = {} param_separator['name'] = "separator" param_separator['default'] = " " param_separator['description'] = """A separator between elements of the field""" builtin_params.append(param_separator) # Add 'nbMax' parameter param_nbMax = {} param_nbMax['name'] = "nbMax" param_nbMax['default'] = "" param_nbMax['description'] = """The maximum number of values to print for this element. No limit if not specified""" builtin_params.append(param_nbMax) # Add 'default' parameter param_default = {} param_default['name'] = "default" param_default['default'] = "" param_default['description'] = """A default value printed if the record has no value for this element""" builtin_params.append(param_default) # Add 'escape' parameter param_escape = {} param_escape['name'] = "escape" param_escape['default'] = "" param_escape['description'] = """If set to 1, replaces special characters '&', '<' and '>' of this element by SGML entities""" builtin_params.append(param_escape) attrs['builtin_params'] = builtin_params return attrs def get_output_format(code, with_attributes=False, verbose=0): """ Returns the structured content of the given output format If 'with_attributes' is true, also returns the names and description of the output formats, else 'attrs' is not returned in dict (it might, if it has already been loaded previously). if output format corresponding to 'code' is not found return an empty structure. See get_output_format_attrs() to learn more about the attributes:: {'rules': [ {'field': "980__a", 'value': "PREPRINT", 'template': "filename_a.bft", }, {...} ], 'attrs': {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1", 'content_type': "application/ms-excel", 'visibility': 1 } 'default':"filename_b.bft" } @param code: the code of an output_format @param with_attributes: if True, fetch the attributes (names and description) for format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: strucured content of output format """ _ = gettext_set_language(CFG_SITE_LANG) output_format = {'rules':[], 'default':""} filename = resolve_output_format_filename(code, verbose) if filename is None: try: raise InvenioBibFormatError(_('Output format with code %(code)s could not be found.', code=code)) except InvenioBibFormatError, exc: register_exception() if with_attributes: #Create empty attrs if asked for attributes output_format['attrs'] = get_output_format_attrs(code, verbose) return output_format # Get from cache whenever possible global format_outputs_cache if format_outputs_cache.has_key(filename): # If was must return with attributes but cache has not # attributes, then load attributes if with_attributes and not \ format_outputs_cache[filename].has_key('attrs'): format_outputs_cache[filename]['attrs'] = get_output_format_attrs(code, verbose) return format_outputs_cache[filename] try: if with_attributes: output_format['attrs'] = get_output_format_attrs(code, verbose) path = "%s%s%s" % (CFG_BIBFORMAT_OUTPUTS_PATH, os.sep, filename ) format_file = open(path) current_tag = '' for line in format_file: line = line.strip() if line == "": # Ignore blank lines continue if line.endswith(":"): # Retrieve tag # Remove : spaces and eol at the end of line clean_line = line.rstrip(": \n\r") # The tag starts at second position current_tag = "".join(clean_line.split()[1:]).strip() elif line.find('---') != -1: words = line.split('---') template = words[-1].strip() condition = ''.join(words[:-1]) value = "" output_format['rules'].append({'field': current_tag, 'value': condition, 'template': template, }) elif line.find(':') != -1: # Default case default = line.split(':')[1].strip() output_format['default'] = default except Exception, e: try: raise InvenioBibFormatError(_('Output format %(filename)s cannot not be read. %(error)s.', filename=filename, error=str(e))) except InvenioBibFormatError, exc: register_exception() # Cache and return format_outputs_cache[filename] = output_format return output_format def get_output_format_attrs(code, verbose=0): """ Returns the attributes of an output format. The attributes contain 'code', which is the short identifier of the output format (to be given as parameter in format_record function to specify the output format), 'description', a description of the output format, 'visibility' the visibility of the format in the output format list on public pages and 'names', the localized names of the output format. If 'content_type' is specified then the search_engine will send a file with this content type and with result of formatting as content to the user. The 'names' dict always contais 'generic', 'ln' (for long name) and 'sn' (for short names) keys. 'generic' is the default name for output format. 'ln' and 'sn' contain long and short localized names of the output format. Only the languages for which a localization exist are used:: {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1", 'content_type': "application/ms-excel", 'visibility': 1 } @param code: the short identifier of the format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: strucured content of output format attributes """ if code.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = code[:-(len(CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION) + 1)] attrs = {'names':{'generic':"", 'ln':{}, 'sn':{}}, 'description':'', 'code':code.upper(), 'content_type':"", 'visibility':1} filename = resolve_output_format_filename(code, verbose) if filename is None: return attrs attrs['names'] = bibformat_dblayer.get_output_format_names(code) attrs['description'] = bibformat_dblayer.get_output_format_description(code) attrs['content_type'] = bibformat_dblayer.get_output_format_content_type(code) attrs['visibility'] = bibformat_dblayer.get_output_format_visibility(code) return attrs def get_output_formats(with_attributes=False): """ Returns the list of all output format, as a dictionary with their filename as key If 'with_attributes' is true, also returns the names and description of the output formats, else 'attrs' is not returned in dicts (it might, if it has already been loaded previously). See get_output_format_attrs() to learn more on the attributes:: {'filename_1.bfo': {'rules': [ {'field': "980__a", 'value': "PREPRINT", 'template': "filename_a.bft", }, {...} ], 'attrs': {'names': {'generic':"a name", 'sn':{'en': "a name", 'fr':"un nom"}, 'ln':{'en':"a long name"}} 'description': "a description" 'code': "fnm1" } 'default':"filename_b.bft" }, 'filename_2.bfo': {...}, ... } @param with_attributes: if returned output formats contain detailed info, or not @type with_attributes: boolean @return: the list of output formats """ output_formats = {} files = os.listdir(CFG_BIBFORMAT_OUTPUTS_PATH) for filename in files: if filename.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = "".join(filename.split(".")[:-1]) output_formats[filename] = get_output_format(code, with_attributes) return output_formats def resolve_format_element_filename(element_name): """ Returns the filename of element corresponding to x{element_name} This is necessary since format templates code call elements by ignoring case, for eg. is the same as . It is also recommended that format elements filenames are prefixed with bfe_ . We need to look for these too. The name of the element has to start with "BFE_". @param element_name: a name for a format element @return: the corresponding filename, with right case """ if not element_name.endswith(".py"): name = element_name.replace(" ", "_").upper() +".PY" else: name = element_name.replace(" ", "_").upper() files = registry.format_elements for element in files: filename = element.__file__ if filename.endswith('.pyc'): filename = filename[:-1] basename = os.path.basename(filename) test_filename = basename.replace(" ", "_").upper() if test_filename == name or \ test_filename == "BFE_" + name or \ "BFE_" + test_filename == name: return basename # No element with that name found # Do not log error, as it might be a normal execution case: # element can be in database return None def resolve_output_format_filename(code, verbose=0): """ Returns the filename of output corresponding to code This is necessary since output formats names are not case sensitive but most file systems are. @param code: the code for an output format @param verbose: the level of verbosity from 0 to 9 (O: silent, 5: errors, 7: errors and warnings, 9: errors and warnings, stop if error (debug mode )) @return: the corresponding filename, with right case, or None if not found """ _ = gettext_set_language(CFG_SITE_LANG) #Remove non alphanumeric chars (except . and _) code = re.sub(r"[^.0-9a-zA-Z_]", "", code) if not code.endswith("."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION): code = re.sub(r"\W", "", code) code += "."+CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION files = os.listdir(CFG_BIBFORMAT_OUTPUTS_PATH) for filename in files: if filename.upper() == code.upper(): return filename # No output format with that name found try: raise InvenioBibFormatError(_('Could not find output format named %(code)s.', code=code)) except InvenioBibFormatError, exc: register_exception() if verbose >= 5: sys.stderr.write(exc.message) if verbose >= 9: sys.exit(exc.message) return None def get_fresh_format_template_filename(name): """ Returns a new filename and name for template with given name. Used when writing a new template to a file, so that the name has no space, is unique in template directory Returns (unique_filename, modified_name) @param name: name for a format template @return: the corresponding filename, and modified name if necessary """ #name = re.sub(r"\W", "", name) #Remove non alphanumeric chars name = name.replace(" ", "_") filename = name # Remove non alphanumeric chars (except .) filename = re.sub(r"[^.0-9a-zA-Z]", "", filename) index = 1 def _get_fullname(filename): return filename + '.' + CFG_BIBFORMAT_FORMAT_TEMPLATE_EXTENSION while _get_fullname(filename) in registry.format_templates_lookup: index += 1 filename = name + str(index) if index > 1: returned_name = (name + str(index)).replace("_", " ") else: returned_name = name.replace("_", " ") return (_get_fullname(filename), returned_name) def get_fresh_output_format_filename(code): """ Returns a new filename for output format with given code. Used when writing a new output format to a file, so that the code has no space, is unique in output format directory. The filename also need to be at most 6 chars long, as the convention is that filename == output format code (+ .extension) We return an uppercase code Returns (unique_filename, modified_code) @param code: the code of an output format @return: the corresponding filename, and modified code if necessary """ _ = gettext_set_language(CFG_SITE_LANG) #code = re.sub(r"\W", "", code) #Remove non alphanumeric chars code = code.upper().replace(" ", "_") # Remove non alphanumeric chars (except . and _) code = re.sub(r"[^.0-9a-zA-Z_]", "", code) if len(code) > 6: code = code[:6] filename = code path = CFG_BIBFORMAT_OUTPUTS_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION index = 2 while os.path.exists(path): filename = code + str(index) if len(filename) > 6: filename = code[:-(len(str(index)))]+str(index) index += 1 path = CFG_BIBFORMAT_OUTPUTS_PATH + os.sep + filename \ + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION # We should not try more than 99999... Well I don't see how we # could get there.. Sanity check. if index >= 99999: try: raise InvenioBibFormatError(_('Could not find a fresh name for output format %s.') % code) except InvenioBibFormatError, exc: register_exception() sys.exit("Output format cannot be named as %s"%code) return (filename + "." + CFG_BIBFORMAT_FORMAT_OUTPUT_EXTENSION, filename) def clear_caches(): """ Clear the caches (Output Format, Format Templates and Format Elements) @return: None """ global format_templates_cache, format_elements_cache, format_outputs_cache format_templates_cache = {} format_elements_cache = {} format_outputs_cache = {} class BibFormatObject: """ An object that encapsulates a record and associated methods, and that is given as parameter to all format elements 'format' function. The object is made specifically for a given formatting, i.e. it includes for example the language for the formatting. The object provides basic accessors to the record. For full access, one can get the record with get_record() and then use BibRecord methods on the returned object. """ # The record record = None # The language in which the formatting has to be done lang = CFG_SITE_LANG # A list of string describing the context in which the record has # to be formatted. # It represents the words of the user request in web interface search search_pattern = [] # The id of the record recID = 0 # The information about the user, as returned by # 'webuser.collect_user_info(req)' user_info = None # The format in which the record is being formatted output_format = '' req = None # DEPRECATED: use bfo.user_info instead. Used by WebJournal. def __init__(self, recID, ln=CFG_SITE_LANG, search_pattern=None, xml_record=None, user_info=None, output_format=''): """ Creates a new bibformat object, with given record. You can either specify an record ID to format, or give its xml representation. if 'xml_record' is not None, use 'xml_record' instead of recID for the record. 'user_info' allows to grant access to some functionalities on a page depending on the user's priviledges. It is a dictionary in the following form:: user_info = { 'remote_ip' : '', 'remote_host' : '', 'referer' : '', 'uri' : '', 'agent' : '', 'uid' : -1, 'nickname' : '', 'email' : '', 'group' : [], 'guest' : '1' } @param recID: the id of a record @param ln: the language in which the record has to be formatted @param search_pattern: list of string representing the request used by the user in web interface @param xml_record: a xml string of the record to format @param user_info: the information of the user who will view the formatted page @param output_format: the output_format used for formatting this record """ self.xml_record = None # *Must* remain empty if recid is given if xml_record is not None: # If record is given as parameter self.xml_record = xml_record self.record = create_record(xml_record)[0] recID = record_get_field_value(self.record, "001") self.lang = wash_language(ln) if search_pattern is None: search_pattern = [] self.search_pattern = search_pattern self.recID = recID self.output_format = output_format self.user_info = user_info if self.user_info is None: from invenio.ext.login.legacy_user import UserInfo self.user_info = UserInfo(None) def get_record(self): """ Returns the record structure of this L{BibFormatObject} instance @return: the record structure as defined by BibRecord library """ from invenio.legacy.search_engine import get_record # Create record if necessary if self.record is None: # on-the-fly creation if current output is xm self.record = get_record(self.recID) return self.record def control_field(self, tag, escape=0): """ Returns the value of control field given by tag in record @param tag: the marc code of a field @param escape: 1 if returned value should be escaped. Else 0. @return: value of field tag in record """ if self.get_record() is None: #Case where BibRecord could not parse object return '' p_tag = parse_tag(tag) field_value = record_get_field_value(self.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if escape == 0: return field_value else: return escape_field(field_value, escape) def field(self, tag, escape=0): """ Returns the value of the field corresponding to tag in the current record. If the value does not exist, return empty string. Else returns the same as bfo.fields(..)[0] (see docstring below). 'escape' parameter allows to escape special characters of the field. The value of escape can be: 0. no escaping 1. escape all HTML characters 2. remove unsafe HTML tags (Eg. keep
    ) 3. Mix of mode 1 and 2. If value of field starts with , then use mode 2. Else use mode 1. 4. Remove all HTML tags 5. Same as 2, with more tags allowed (like ) 6. Same as 3, with more tags allowed (like ) 7. Mix of mode 0 and mode 1. If field_value starts with , then use mode 0. Else use mode 1. 8. Same as mode 1, but also escape double-quotes 9. Same as mode 4, but also escape double-quotes @param tag: the marc code of a field @param escape: 1 if returned value should be escaped. Else 0. (see above for other modes) @return: value of field tag in record """ list_of_fields = self.fields(tag) if len(list_of_fields) > 0: # Escaping below if escape == 0: return list_of_fields[0] else: return escape_field(list_of_fields[0], escape) else: return "" def fields(self, tag, escape=0, repeatable_subfields_p=False): """ Returns the list of values corresonding to "tag". If tag has an undefined subcode (such as 999C5), the function returns a list of dictionaries, whoose keys are the subcodes and the values are the values of tag.subcode. If the tag has a subcode, simply returns list of values corresponding to tag. Eg. for given MARC:: 999C5 $a value_1a $b value_1b 999C5 $b value_2b 999C5 $b value_3b $b value_3b_bis >>> bfo.fields('999C5b') >>> ['value_1b', 'value_2b', 'value_3b', 'value_3b_bis'] >>> bfo.fields('999C5') >>> [{'a':'value_1a', 'b':'value_1b'}, {'b':'value_2b'}, {'b':'value_3b'}] By default the function returns only one value for each subfield (that is it considers that repeatable subfields are not allowed). It is why in the above example 'value3b_bis' is not shown for bfo.fields('999C5'). (Note that it is not defined which of value_3b or value_3b_bis is returned). This is to simplify the use of the function, as most of the time subfields are not repeatable (in that way we get a string instead of a list). You can allow repeatable subfields by setting 'repeatable_subfields_p' parameter to True. In this mode, the above example would return: >>> bfo.fields('999C5b', repeatable_subfields_p=True) >>> ['value_1b', 'value_2b', 'value_3b'] >>> bfo.fields('999C5', repeatable_subfields_p=True) >>> [{'a':['value_1a'], 'b':['value_1b']}, {'b':['value_2b']}, {'b':['value_3b', 'value3b_bis']}] NOTICE THAT THE RETURNED STRUCTURE IS DIFFERENT. Also note that whatever the value of 'repeatable_subfields_p' is, bfo.fields('999C5b') always show all fields, even repeatable ones. This is because the parameter has no impact on the returned structure (it is always a list). 'escape' parameter allows to escape special characters of the fields. The value of escape can be: 0. No escaping 1. Escape all HTML characters 2. Remove unsafe HTML tags (Eg. keep
    ) 3. Mix of mode 1 and 2. If value of field starts with , then use mode 2. Else use mode 1. 4. Remove all HTML tags 5. Same as 2, with more tags allowed (like ) 6. Same as 3, with more tags allowed (like ) 7. Mix of mode 0 and mode 1. If field_value starts with , then use mode 0. Else use mode 1. 8. Same as mode 1, but also escape double-quotes 9. Same as mode 4, but also escape double-quotes @param tag: the marc code of a field @param escape: 1 if returned values should be escaped. Else 0. @repeatable_subfields_p if True, returns the list of subfields in the dictionary @return: values of field tag in record """ if self.get_record() is None: # Case where BibRecord could not parse object return [] p_tag = parse_tag(tag) if p_tag[3] != "": # Subcode has been defined. Simply returns list of values values = record_get_field_values(self.get_record(), p_tag[0], p_tag[1], p_tag[2], p_tag[3]) if escape == 0: return values else: return [escape_field(value, escape) for value in values] else: # Subcode is undefined. Returns list of dicts. # However it might be the case of a control field. instances = record_get_field_instances(self.get_record(), p_tag[0], p_tag[1], p_tag[2]) if repeatable_subfields_p: list_of_instances = [] for instance in instances: instance_dict = {} for subfield in instance[0]: if not instance_dict.has_key(subfield[0]): instance_dict[subfield[0]] = [] if escape == 0: instance_dict[subfield[0]].append(subfield[1]) else: instance_dict[subfield[0]].append(escape_field(subfield[1], escape)) list_of_instances.append(instance_dict) return list_of_instances else: if escape == 0: return [dict(instance[0]) for instance in instances] else: return [dict([ (subfield[0], escape_field(subfield[1], escape)) \ for subfield in instance[0] ]) \ for instance in instances] def kb(self, kb, string, default=""): """ Returns the value of the "string" in the knowledge base "kb". If kb does not exist or string does not exist in kb, returns 'default' string or empty string if not specified. @param kb: a knowledge base name @param string: the string we want to translate @param default: a default value returned if 'string' not found in 'kb' @return: a string value corresponding to translated input with given kb """ if not string: return default val = get_kbr_values(kb, searchkey=string, searchtype='e') try: return val[0][0] except: return default def escape_field(value, mode=0): """ Utility function used to escape the value of a field in given mode. - mode 0: no escaping - mode 1: escaping all HTML/XML characters (escaped chars are shown as escaped) - mode 2: escaping unsafe HTML tags to avoid XSS, but keep basic one (such as
    ) Escaped tags are removed. - mode 3: mix of mode 1 and mode 2. If field_value starts with , then use mode 2. Else use mode 1. - mode 4: escaping all HTML/XML tags (escaped tags are removed) - mode 5: same as 2, but allows more tags, like - mode 6: same as 3, but allows more tags, like - mode 7: mix of mode 0 and mode 1. If field_value starts with , then use mode 0. Else use mode 1. - mode 8: same as mode 1, but also escape double-quotes - mode 9: same as mode 4, but also escape double-quotes @param value: value to escape @param mode: escaping mode to use @return: an escaped version of X{value} according to chosen X{mode} """ if mode == 1: return cgi.escape(value) elif mode == 8: return cgi.escape(value, True) elif mode in [2, 5]: allowed_attribute_whitelist = CFG_HTML_BUFFER_ALLOWED_ATTRIBUTE_WHITELIST allowed_tag_whitelist = CFG_HTML_BUFFER_ALLOWED_TAG_WHITELIST + \ ('class',) if mode == 5: allowed_attribute_whitelist += ('src', 'alt', 'width', 'height', 'style', 'summary', 'border', 'cellspacing', 'cellpadding') allowed_tag_whitelist += ('img', 'table', 'td', 'tr', 'th', 'span', 'caption') try: return washer.wash(value, allowed_attribute_whitelist=\ allowed_attribute_whitelist, allowed_tag_whitelist= \ allowed_tag_whitelist ) except HTMLParseError: # Parsing failed return cgi.escape(value) elif mode in [3, 6]: if value.lstrip(' \n').startswith(html_field): allowed_attribute_whitelist = CFG_HTML_BUFFER_ALLOWED_ATTRIBUTE_WHITELIST allowed_tag_whitelist = CFG_HTML_BUFFER_ALLOWED_TAG_WHITELIST + \ ('class',) if mode == 6: allowed_attribute_whitelist += ('src', 'alt', 'width', 'height', 'style', 'summary', 'border', 'cellspacing', 'cellpadding') allowed_tag_whitelist += ('img', 'table', 'td', 'tr', 'th', 'span', 'caption') try: return washer.wash(value, allowed_attribute_whitelist=\ allowed_attribute_whitelist, allowed_tag_whitelist=\ allowed_tag_whitelist ) except HTMLParseError: # Parsing failed return cgi.escape(value) else: return cgi.escape(value) elif mode in [4, 9]: try: out = washer.wash(value, allowed_attribute_whitelist=[], allowed_tag_whitelist=[] ) if mode == 9: out = out.replace('"', '"') return out except HTMLParseError: # Parsing failed if mode == 4: return cgi.escape(value) else: return cgi.escape(value, True) elif mode == 7: if value.lstrip(' \n').startswith(html_field): return value else: return cgi.escape(value) else: return value def bf_profile(): """ Runs a benchmark @return: None """ for i in range(1, 51): format_record(i, "HD", ln=CFG_SITE_LANG, verbose=9, search_pattern=[]) return if __name__ == "__main__": import profile import pstats #bf_profile() profile.run('bf_profile()', "bibformat_profile") p = pstats.Stats("bibformat_profile") p.strip_dirs().sort_stats("cumulative").print_stats() diff --git a/invenio/modules/formatter/models.py b/invenio/modules/formatter/models.py index 3c6b04b2f..23d7012bc 100644 --- a/invenio/modules/formatter/models.py +++ b/invenio/modules/formatter/models.py @@ -1,163 +1,163 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2013 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 02D111-1307, USA. """ invenio.modules.formatter.models ----------------------------------- Database access related functions for Formatter engine and administration pages. """ from invenio.ext.sqlalchemy import db -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec class Format(db.Model): """Represents a Format record.""" __tablename__ = 'format' id = db.Column( db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) name = db.Column(db.String(255), nullable=False) code = db.Column(db.String(6), nullable=False, unique=True) description = db.Column(db.String(255), server_default='') content_type = db.Column(db.String(255), server_default='') visibility = db.Column( db.TinyInteger(4), nullable=False, server_default='1') last_updated = db.Column(db.DateTime, nullable=True) @classmethod def get_export_formats(cls): return cls.query.filter(db.and_( Format.content_type != 'text/html', Format.visibility == 1) ).order_by(Format.name).all() def set_name(self, name, lang="generic", type='ln'): """ Sets the name of an output format. if 'type' different from 'ln' or 'sn', do nothing if 'name' exceeds 256 chars, 'name' is truncated to first 256 chars. The localized names of output formats are located in formatname table. :param type: either 'ln' (for long name) and 'sn' (for short name) :param lang: the language in which the name is given :param name: the name to give to the output format """ if len(name) > 256: name = name[:256] if type.lower() != "sn" and type.lower() != "ln": return if lang == "generic" and type.lower() == "ln": self.name = name else: # Save inside formatname table for name variations fname = db.session.query(Formatname)\ .get((self.id, lang, type.lower())) if not fname: fname = db.session.merge(Formatname()) fname.id_format = self.id fname.ln = lang fname.type = type.lower() fname.value = name db.session.save(fname) db.session.add(self) db.session.commit() class Formatname(db.Model): """Represents a Formatname record.""" __tablename__ = 'formatname' id_format = db.Column( db.MediumInteger(9, unsigned=True), db.ForeignKey(Format.id), primary_key=True) ln = db.Column( db.Char(5), primary_key=True, server_default='') type = db.Column( db.Char(3), primary_key=True, server_default='sn') value = db.Column(db.String(255), nullable=False) format = db.relationship(Format, backref='names') #TODO add association proxy with key (id_format, ln, type) class Bibfmt(db.Model): """Represents a Bibfmt record.""" def __init__(self): pass __tablename__ = 'bibfmt' id_bibrec = db.Column( db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0', primary_key=True, autoincrement=False) format = db.Column( db.String(10), nullable=False, server_default='', primary_key=True, index=True) last_updated = db.Column( db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', index=True) value = db.Column(db.iLargeBinary) bibrec = db.relationship(Bibrec, backref='bibfmt') __all__ = [ 'Format', 'Formatname', 'Bibfmt', ] diff --git a/invenio/modules/indexer/models.py b/invenio/modules/indexer/models.py index a1779b8bc..f413bc82a 100644 --- a/invenio/modules/indexer/models.py +++ b/invenio/modules/indexer/models.py @@ -1,2290 +1,2290 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ bibindex database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.search.models import Field class IdxINDEX(db.Model): """Represents a IdxINDEX record.""" def __init__(self): pass __tablename__ = 'idxINDEX' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False, server_default='') description = db.Column(db.String(255), nullable=False, server_default='') last_updated = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') stemming_language = db.Column(db.String(10), nullable=False, server_default='') indexer = db.Column(db.String(10), nullable=False, server_default='native') synonym_kbrs = db.Column(db.String(255), nullable=False, server_default='') remove_stopwords = db.Column(db.String(255), nullable=False, server_default='') remove_html_markup = db.Column(db.String(10), nullable=False, server_default='') remove_latex_markup = db.Column(db.String(10), nullable=False, server_default='') tokenizer = db.Column(db.String(50), nullable=False, server_default='') class IdxINDEXIdxINDEX(db.Model): """Represents an IdxINDEXIdxINDEX record.""" def __init__(self): pass __tablename__ = 'idxINDEX_idxINDEX' id_virtual = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(IdxINDEX.id), nullable=False, server_default='0', primary_key=True) id_normal = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(IdxINDEX.id), nullable=False, server_default='0', primary_key=True) class IdxINDEXNAME(db.Model): """Represents a IdxINDEXNAME record.""" def __init__(self): pass __tablename__ = 'idxINDEXNAME' id_idxINDEX = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(IdxINDEX.id), primary_key=True) ln = db.Column(db.Char(5), primary_key=True, server_default='') type = db.Column(db.Char(3), primary_key=True, server_default='sn') value = db.Column(db.String(255), nullable=False) idxINDEX = db.relationship(IdxINDEX, backref='names') class IdxINDEXField(db.Model): """Represents a IdxINDEXField record.""" def __init__(self): pass __tablename__ = 'idxINDEX_field' id_idxINDEX = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(IdxINDEX.id), primary_key=True) id_field = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Field.id), primary_key=True) regexp_punctuation = db.Column(db.String(255), nullable=False, server_default='[.,:;?!"]') regexp_alphanumeric_separators = db.Column(db.String(255), nullable=False) #FIX ME , #server_default='[!"#$\\%&''()*+,-./:;<=>?@[\\]^\\_`{|}~]') idxINDEX = db.relationship(IdxINDEX, backref='fields') field = db.relationship(Field, backref='idxINDEXes') #GENERATED class IdxPAIR01F(db.Model): """Represents a IdxPAIR01F record.""" def __init__(self): pass __tablename__ = 'idxPAIR01F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR01R(db.Model): """Represents a IdxPAIR01R record.""" def __init__(self): pass __tablename__ = 'idxPAIR01R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR02F(db.Model): """Represents a IdxPAIR02F record.""" def __init__(self): pass __tablename__ = 'idxPAIR02F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR02R(db.Model): """Represents a IdxPAIR02R record.""" def __init__(self): pass __tablename__ = 'idxPAIR02R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR03F(db.Model): """Represents a IdxPAIR03F record.""" def __init__(self): pass __tablename__ = 'idxPAIR03F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR03R(db.Model): """Represents a IdxPAIR03R record.""" def __init__(self): pass __tablename__ = 'idxPAIR03R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR04F(db.Model): """Represents a IdxPAIR04F record.""" def __init__(self): pass __tablename__ = 'idxPAIR04F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR04R(db.Model): """Represents a IdxPAIR04R record.""" def __init__(self): pass __tablename__ = 'idxPAIR04R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR05F(db.Model): """Represents a IdxPAIR05F record.""" def __init__(self): pass __tablename__ = 'idxPAIR05F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR05R(db.Model): """Represents a IdxPAIR05R record.""" def __init__(self): pass __tablename__ = 'idxPAIR05R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR06F(db.Model): """Represents a IdxPAIR06F record.""" def __init__(self): pass __tablename__ = 'idxPAIR06F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR06R(db.Model): """Represents a IdxPAIR06R record.""" def __init__(self): pass __tablename__ = 'idxPAIR06R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR07F(db.Model): """Represents a IdxPAIR07F record.""" def __init__(self): pass __tablename__ = 'idxPAIR07F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR07R(db.Model): """Represents a IdxPAIR07R record.""" def __init__(self): pass __tablename__ = 'idxPAIR07R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR08F(db.Model): """Represents a IdxPAIR08F record.""" def __init__(self): pass __tablename__ = 'idxPAIR08F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR08R(db.Model): """Represents a IdxPAIR08R record.""" def __init__(self): pass __tablename__ = 'idxPAIR08R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR09F(db.Model): """Represents a IdxPAIR09F record.""" def __init__(self): pass __tablename__ = 'idxPAIR09F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR09R(db.Model): """Represents a IdxPAIR09R record.""" def __init__(self): pass __tablename__ = 'idxPAIR09R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR10F(db.Model): """Represents a IdxPAIR10F record.""" def __init__(self): pass __tablename__ = 'idxPAIR10F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR10R(db.Model): """Represents a IdxPAIR10R record.""" def __init__(self): pass __tablename__ = 'idxPAIR10R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR11F(db.Model): """Represents a IdxPAIR11F record.""" def __init__(self): pass __tablename__ = 'idxPAIR11F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR11R(db.Model): """Represents a IdxPAIR11R record.""" def __init__(self): pass __tablename__ = 'idxPAIR11R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR12F(db.Model): """Represents a IdxPAIR12F record.""" def __init__(self): pass __tablename__ = 'idxPAIR12F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR12R(db.Model): """Represents a IdxPAIR12R record.""" def __init__(self): pass __tablename__ = 'idxPAIR12R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR13F(db.Model): """Represents a IdxPAIR13F record.""" def __init__(self): pass __tablename__ = 'idxPAIR13F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR13R(db.Model): """Represents a IdxPAIR13R record.""" def __init__(self): pass __tablename__ = 'idxPAIR13R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR14F(db.Model): """Represents a IdxPAIR14F record.""" def __init__(self): pass __tablename__ = 'idxPAIR14F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR14R(db.Model): """Represents a IdxPAIR14R record.""" def __init__(self): pass __tablename__ = 'idxPAIR14R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR15F(db.Model): """Represents a IdxPAIR15F record.""" def __init__(self): pass __tablename__ = 'idxPAIR15F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR15R(db.Model): """Represents a IdxPAIR15R record.""" def __init__(self): pass __tablename__ = 'idxPAIR15R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR16F(db.Model): """Represents a IdxPAIR16F record.""" def __init__(self): pass __tablename__ = 'idxPAIR16F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR16R(db.Model): """Represents a IdxPAIR16R record.""" def __init__(self): pass __tablename__ = 'idxPAIR16R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR17F(db.Model): """Represents a IdxPAIR17F record.""" def __init__(self): pass __tablename__ = 'idxPAIR17F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR17R(db.Model): """Represents a IdxPAIR17R record.""" def __init__(self): pass __tablename__ = 'idxPAIR17R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR18F(db.Model): """Represents a IdxPAIR18F record.""" def __init__(self): pass __tablename__ = 'idxPAIR18F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR18R(db.Model): """Represents a IdxPAIR18R record.""" def __init__(self): pass __tablename__ = 'idxPAIR18R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR19F(db.Model): """Represents a IdxPAIR19F record.""" def __init__(self): pass __tablename__ = 'idxPAIR19F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR19R(db.Model): """Represents a IdxPAIR19R record.""" def __init__(self): pass __tablename__ = 'idxPAIR19R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR20F(db.Model): """Represents a IdxPAIR20F record.""" def __init__(self): pass __tablename__ = 'idxPAIR20F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR20R(db.Model): """Represents a IdxPAIR20R record.""" def __init__(self): pass __tablename__ = 'idxPAIR20R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR21F(db.Model): """Represents a IdxPAIR21F record.""" def __init__(self): pass __tablename__ = 'idxPAIR21F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR21R(db.Model): """Represents a IdxPAIR21R record.""" def __init__(self): pass __tablename__ = 'idxPAIR21R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR22F(db.Model): """Represents a IdxPAIR22F record.""" def __init__(self): pass __tablename__ = 'idxPAIR22F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR22R(db.Model): """Represents a IdxPAIR22R record.""" def __init__(self): pass __tablename__ = 'idxPAIR22R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR23F(db.Model): """Represents a IdxPAIR23F record.""" def __init__(self): pass __tablename__ = 'idxPAIR23F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR23R(db.Model): """Represents a IdxPAIR23R record.""" def __init__(self): pass __tablename__ = 'idxPAIR23R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR24F(db.Model): """Represents a IdxPAIR24F record.""" def __init__(self): pass __tablename__ = 'idxPAIR24F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR24R(db.Model): """Represents a IdxPAIR24R record.""" def __init__(self): pass __tablename__ = 'idxPAIR24R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR25F(db.Model): """Represents a IdxPAIR25F record.""" def __init__(self): pass __tablename__ = 'idxPAIR25F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR25R(db.Model): """Represents a IdxPAIR25R record.""" def __init__(self): pass __tablename__ = 'idxPAIR25R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPAIR26F(db.Model): """Represents a IdxPAIR26F record.""" def __init__(self): pass __tablename__ = 'idxPAIR26F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPAIR26R(db.Model): """Represents a IdxPAIR26R record.""" def __init__(self): pass __tablename__ = 'idxPAIR26R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE01F(db.Model): """Represents a IdxPHRASE01F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE01F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE01R(db.Model): """Represents a IdxPHRASE01R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE01R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE02F(db.Model): """Represents a IdxPHRASE02F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE02F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE02R(db.Model): """Represents a IdxPHRASE02R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE02R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE03F(db.Model): """Represents a IdxPHRASE03F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE03F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE03R(db.Model): """Represents a IdxPHRASE03R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE03R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE04F(db.Model): """Represents a IdxPHRASE04F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE04F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE04R(db.Model): """Represents a IdxPHRASE04R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE04R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE05F(db.Model): """Represents a IdxPHRASE05F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE05F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE05R(db.Model): """Represents a IdxPHRASE05R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE05R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE06F(db.Model): """Represents a IdxPHRASE06F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE06F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE06R(db.Model): """Represents a IdxPHRASE06R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE06R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE07F(db.Model): """Represents a IdxPHRASE07F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE07F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE07R(db.Model): """Represents a IdxPHRASE07R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE07R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE08F(db.Model): """Represents a IdxPHRASE08F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE08F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE08R(db.Model): """Represents a IdxPHRASE08R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE08R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE09F(db.Model): """Represents a IdxPHRASE09F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE09F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE09R(db.Model): """Represents a IdxPHRASE09R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE09R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE10F(db.Model): """Represents a IdxPHRASE10F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE10F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE10R(db.Model): """Represents a IdxPHRASE10R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE10R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE11F(db.Model): """Represents a IdxPHRASE11F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE11F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE11R(db.Model): """Represents a IdxPHRASE11R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE11R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE12F(db.Model): """Represents a IdxPHRASE12F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE12F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE12R(db.Model): """Represents a IdxPHRASE12R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE12R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE13F(db.Model): """Represents a IdxPHRASE13F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE13F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE13R(db.Model): """Represents a IdxPHRASE13R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE13R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE14F(db.Model): """Represents a IdxPHRASE14F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE14F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE14R(db.Model): """Represents a IdxPHRASE14R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE14R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE15F(db.Model): """Represents a IdxPHRASE15F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE15F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE15R(db.Model): """Represents a IdxPHRASE15R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE15R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE16F(db.Model): """Represents a IdxPHRASE16F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE16F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE16R(db.Model): """Represents a IdxPHRASE16R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE16R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE17F(db.Model): """Represents a IdxPHRASE17F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE17F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE17R(db.Model): """Represents a IdxPHRASE17R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE17R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE18F(db.Model): """Represents a IdxPHRASE18F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE18F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE18R(db.Model): """Represents a IdxPHRASE18R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE18R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE19F(db.Model): """Represents a IdxPHRASE19F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE19F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE19R(db.Model): """Represents a IdxPHRASE19R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE19R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE20F(db.Model): """Represents a IdxPHRASE20F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE20F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE20R(db.Model): """Represents a IdxPHRASE20R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE20R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE21F(db.Model): """Represents a IdxPHRASE21F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE21F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE21R(db.Model): """Represents a IdxPHRASE21R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE21R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE22F(db.Model): """Represents a IdxPHRASE22F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE22F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE22R(db.Model): """Represents a IdxPHRASE22R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE22R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE23F(db.Model): """Represents a IdxPHRASE23F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE23F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE23R(db.Model): """Represents a IdxPHRASE23R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE23R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE24F(db.Model): """Represents a IdxPHRASE24F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE24F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE24R(db.Model): """Represents a IdxPHRASE24R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE24R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE25F(db.Model): """Represents a IdxPHRASE25F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE25F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE25R(db.Model): """Represents a IdxPHRASE25R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE25R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxPHRASE26F(db.Model): """Represents a IdxPHRASE26F record.""" def __init__(self): pass __tablename__ = 'idxPHRASE26F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(100), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxPHRASE26R(db.Model): """Represents a IdxPHRASE26R record.""" def __init__(self): pass __tablename__ = 'idxPHRASE26R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD01F(db.Model): """Represents a IdxWORD01F record.""" def __init__(self): pass __tablename__ = 'idxWORD01F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD01R(db.Model): """Represents a IdxWORD01R record.""" def __init__(self): pass __tablename__ = 'idxWORD01R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD02F(db.Model): """Represents a IdxWORD02F record.""" def __init__(self): pass __tablename__ = 'idxWORD02F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD02R(db.Model): """Represents a IdxWORD02R record.""" def __init__(self): pass __tablename__ = 'idxWORD02R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD03F(db.Model): """Represents a IdxWORD03F record.""" def __init__(self): pass __tablename__ = 'idxWORD03F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD03R(db.Model): """Represents a IdxWORD03R record.""" def __init__(self): pass __tablename__ = 'idxWORD03R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD04F(db.Model): """Represents a IdxWORD04F record.""" def __init__(self): pass __tablename__ = 'idxWORD04F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD04R(db.Model): """Represents a IdxWORD04R record.""" def __init__(self): pass __tablename__ = 'idxWORD04R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD05F(db.Model): """Represents a IdxWORD05F record.""" def __init__(self): pass __tablename__ = 'idxWORD05F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD05R(db.Model): """Represents a IdxWORD05R record.""" def __init__(self): pass __tablename__ = 'idxWORD05R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD06F(db.Model): """Represents a IdxWORD06F record.""" def __init__(self): pass __tablename__ = 'idxWORD06F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD06R(db.Model): """Represents a IdxWORD06R record.""" def __init__(self): pass __tablename__ = 'idxWORD06R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD07F(db.Model): """Represents a IdxWORD07F record.""" def __init__(self): pass __tablename__ = 'idxWORD07F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD07R(db.Model): """Represents a IdxWORD07R record.""" def __init__(self): pass __tablename__ = 'idxWORD07R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD08F(db.Model): """Represents a IdxWORD08F record.""" def __init__(self): pass __tablename__ = 'idxWORD08F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD08R(db.Model): """Represents a IdxWORD08R record.""" def __init__(self): pass __tablename__ = 'idxWORD08R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD09F(db.Model): """Represents a IdxWORD09F record.""" def __init__(self): pass __tablename__ = 'idxWORD09F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD09R(db.Model): """Represents a IdxWORD09R record.""" def __init__(self): pass __tablename__ = 'idxWORD09R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD10F(db.Model): """Represents a IdxWORD10F record.""" def __init__(self): pass __tablename__ = 'idxWORD10F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD10R(db.Model): """Represents a IdxWORD10R record.""" def __init__(self): pass __tablename__ = 'idxWORD10R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD11F(db.Model): """Represents a IdxWORD11F record.""" def __init__(self): pass __tablename__ = 'idxWORD11F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD11R(db.Model): """Represents a IdxWORD11R record.""" def __init__(self): pass __tablename__ = 'idxWORD11R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD12F(db.Model): """Represents a IdxWORD12F record.""" def __init__(self): pass __tablename__ = 'idxWORD12F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD12R(db.Model): """Represents a IdxWORD12R record.""" def __init__(self): pass __tablename__ = 'idxWORD12R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD13F(db.Model): """Represents a IdxWORD13F record.""" def __init__(self): pass __tablename__ = 'idxWORD13F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD13R(db.Model): """Represents a IdxWORD13R record.""" def __init__(self): pass __tablename__ = 'idxWORD13R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD14F(db.Model): """Represents a IdxWORD14F record.""" def __init__(self): pass __tablename__ = 'idxWORD14F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD14R(db.Model): """Represents a IdxWORD14R record.""" def __init__(self): pass __tablename__ = 'idxWORD14R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD15F(db.Model): """Represents a IdxWORD15F record.""" def __init__(self): pass __tablename__ = 'idxWORD15F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD15R(db.Model): """Represents a IdxWORD15R record.""" def __init__(self): pass __tablename__ = 'idxWORD15R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD16F(db.Model): """Represents a IdxWORD16F record.""" def __init__(self): pass __tablename__ = 'idxWORD16F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD16R(db.Model): """Represents a IdxWORD16R record.""" def __init__(self): pass __tablename__ = 'idxWORD16R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD17F(db.Model): """Represents a IdxWORD17F record.""" def __init__(self): pass __tablename__ = 'idxWORD17F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD17R(db.Model): """Represents a IdxWORD17R record.""" def __init__(self): pass __tablename__ = 'idxWORD17R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD18F(db.Model): """Represents a IdxWORD18F record.""" def __init__(self): pass __tablename__ = 'idxWORD18F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD18R(db.Model): """Represents a IdxWORD18R record.""" def __init__(self): pass __tablename__ = 'idxWORD18R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD19F(db.Model): """Represents a IdxWORD19F record.""" def __init__(self): pass __tablename__ = 'idxWORD19F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD19R(db.Model): """Represents a IdxWORD19R record.""" def __init__(self): pass __tablename__ = 'idxWORD19R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD20F(db.Model): """Represents a IdxWORD20F record.""" def __init__(self): pass __tablename__ = 'idxWORD20F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD20R(db.Model): """Represents a IdxWORD20R record.""" def __init__(self): pass __tablename__ = 'idxWORD20R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD21F(db.Model): """Represents a IdxWORD21F record.""" def __init__(self): pass __tablename__ = 'idxWORD21F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD21R(db.Model): """Represents a IdxWORD21R record.""" def __init__(self): pass __tablename__ = 'idxWORD21R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD22F(db.Model): """Represents a IdxWORD22F record.""" def __init__(self): pass __tablename__ = 'idxWORD22F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD22R(db.Model): """Represents a IdxWORD22R record.""" def __init__(self): pass __tablename__ = 'idxWORD22R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD23F(db.Model): """Represents a IdxWORD23F record.""" def __init__(self): pass __tablename__ = 'idxWORD23F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD23R(db.Model): """Represents a IdxWORD23R record.""" def __init__(self): pass __tablename__ = 'idxWORD23R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD24F(db.Model): """Represents a IdxWORD24F record.""" def __init__(self): pass __tablename__ = 'idxWORD24F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD24R(db.Model): """Represents a IdxWORD24R record.""" def __init__(self): pass __tablename__ = 'idxWORD24R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD25F(db.Model): """Represents a IdxWORD25F record.""" def __init__(self): pass __tablename__ = 'idxWORD25F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD25R(db.Model): """Represents a IdxWORD25R record.""" def __init__(self): pass __tablename__ = 'idxWORD25R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) class IdxWORD26F(db.Model): """Represents a IdxWORD26F record.""" def __init__(self): pass __tablename__ = 'idxWORD26F' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class IdxWORD26R(db.Model): """Represents a IdxWORD26R record.""" def __init__(self): pass __tablename__ = 'idxWORD26R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), primary_key=True) termlist = db.Column(db.iLargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) __all__ = ['IdxINDEX', 'IdxINDEXIdxINDEX', 'IdxINDEXNAME', 'IdxINDEXField', 'IdxPAIR01F', 'IdxPAIR01R', 'IdxPAIR02F', 'IdxPAIR02R', 'IdxPAIR03F', 'IdxPAIR03R', 'IdxPAIR04F', 'IdxPAIR04R', 'IdxPAIR05F', 'IdxPAIR05R', 'IdxPAIR06F', 'IdxPAIR06R', 'IdxPAIR07F', 'IdxPAIR07R', 'IdxPAIR08F', 'IdxPAIR08R', 'IdxPAIR09F', 'IdxPAIR09R', 'IdxPAIR10F', 'IdxPAIR10R', 'IdxPAIR11F', 'IdxPAIR11R', 'IdxPAIR12F', 'IdxPAIR12R', 'IdxPAIR13F', 'IdxPAIR13R', 'IdxPAIR14F', 'IdxPAIR14R', 'IdxPAIR15F', 'IdxPAIR15R', 'IdxPAIR16F', 'IdxPAIR16R', 'IdxPAIR17F', 'IdxPAIR17R', 'IdxPAIR18F', 'IdxPAIR18R', 'IdxPAIR19F', 'IdxPAIR19R', 'IdxPAIR20F', 'IdxPAIR20R', 'IdxPAIR21F', 'IdxPAIR21R', 'IdxPAIR22F', 'IdxPAIR22R', 'IdxPAIR23F', 'IdxPAIR23R', 'IdxPAIR24F', 'IdxPAIR24R', 'IdxPAIR25F', 'IdxPAIR25R', 'IdxPAIR26F', 'IdxPAIR26R', 'IdxPHRASE01F', 'IdxPHRASE01R', 'IdxPHRASE02F', 'IdxPHRASE02R', 'IdxPHRASE03F', 'IdxPHRASE03R', 'IdxPHRASE04F', 'IdxPHRASE04R', 'IdxPHRASE05F', 'IdxPHRASE05R', 'IdxPHRASE06F', 'IdxPHRASE06R', 'IdxPHRASE07F', 'IdxPHRASE07R', 'IdxPHRASE08F', 'IdxPHRASE08R', 'IdxPHRASE09F', 'IdxPHRASE09R', 'IdxPHRASE10F', 'IdxPHRASE10R', 'IdxPHRASE11F', 'IdxPHRASE11R', 'IdxPHRASE12F', 'IdxPHRASE12R', 'IdxPHRASE13F', 'IdxPHRASE13R', 'IdxPHRASE14F', 'IdxPHRASE14R', 'IdxPHRASE15F', 'IdxPHRASE15R', 'IdxPHRASE16F', 'IdxPHRASE16R', 'IdxPHRASE17F', 'IdxPHRASE17R', 'IdxPHRASE18F', 'IdxPHRASE18R', 'IdxPHRASE19F', 'IdxPHRASE19R', 'IdxPHRASE20F', 'IdxPHRASE20R', 'IdxPHRASE21F', 'IdxPHRASE21R', 'IdxPHRASE22F', 'IdxPHRASE22R', 'IdxPHRASE23F', 'IdxPHRASE23R', 'IdxPHRASE24F', 'IdxPHRASE24R', 'IdxPHRASE25F', 'IdxPHRASE25R', 'IdxPHRASE26F', 'IdxPHRASE26R', 'IdxWORD01F', 'IdxWORD01R', 'IdxWORD02F', 'IdxWORD02R', 'IdxWORD03F', 'IdxWORD03R', 'IdxWORD04F', 'IdxWORD04R', 'IdxWORD05F', 'IdxWORD05R', 'IdxWORD06F', 'IdxWORD06R', 'IdxWORD07F', 'IdxWORD07R', 'IdxWORD08F', 'IdxWORD08R', 'IdxWORD09F', 'IdxWORD09R', 'IdxWORD10F', 'IdxWORD10R', 'IdxWORD11F', 'IdxWORD11R', 'IdxWORD12F', 'IdxWORD12R', 'IdxWORD13F', 'IdxWORD13R', 'IdxWORD14F', 'IdxWORD14R', 'IdxWORD15F', 'IdxWORD15R', 'IdxWORD16F', 'IdxWORD16R', 'IdxWORD17F', 'IdxWORD17R', 'IdxWORD18F', 'IdxWORD18R', 'IdxWORD19F', 'IdxWORD19R', 'IdxWORD20F', 'IdxWORD20R', 'IdxWORD21F', 'IdxWORD21R', 'IdxWORD22F', 'IdxWORD22R', 'IdxWORD23F', 'IdxWORD23R', 'IdxWORD24F', 'IdxWORD24R', 'IdxWORD25F', 'IdxWORD25R', 'IdxWORD26F', 'IdxWORD26R'] diff --git a/invenio/modules/indexer/tokenizers/BibIndexFiletypeTokenizer.py b/invenio/modules/indexer/tokenizers/BibIndexFiletypeTokenizer.py index 00342a33b..4111dca80 100644 --- a/invenio/modules/indexer/tokenizers/BibIndexFiletypeTokenizer.py +++ b/invenio/modules/indexer/tokenizers/BibIndexFiletypeTokenizer.py @@ -1,63 +1,63 @@ # -*- coding:utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2010, 2011, 2012, 2013 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. """BibIndexFiletypeTokenizer: 'tokenizes' for file extensions. - Tokenizer is adapted to work with bibfield and its get_record function. + Tokenizer is adapted to work with recjson and its get_record function. """ from invenio.modules.indexer.tokenizers.BibIndexEmptyTokenizer import BibIndexEmptyTokenizer class BibIndexFiletypeTokenizer(BibIndexEmptyTokenizer): """ Tokenizes for file extensions. - Tokenizer is adapted to work with bibfield and its get_record function. + Tokenizer is adapted to work with recjson and its get_record function. It accepts as an input a record created by a get_record function: - from bibfield import get_record + from invenio.modules.records.api import get_record record16 = get_record(16) tokenizer = BibIndexFiletypeTokenizer() new_words = tokenizer.tokenize(record16) """ def __init__(self, stemming_language = None, remove_stopwords = False, remove_html_markup = False, remove_latex_markup = False): pass def tokenize(self, record): - """'record' is a recjson record from bibfield. + """'record' is a recjson record. Function uses derived field 'filetypes' from the record. @param urls: recjson record """ values = [] try: if record.has_key('filetypes'): values = record['filetypes'] except KeyError: pass except TypeError: return [] return values def get_tokenizing_function(self, wordtable_type): return self.tokenize diff --git a/invenio/core/__init__.py b/invenio/modules/jsonalchemy/__init__.py similarity index 100% copy from invenio/core/__init__.py copy to invenio/modules/jsonalchemy/__init__.py diff --git a/invenio/legacy/bibfield/functions/get_number_of_comments.py b/invenio/modules/jsonalchemy/errors.py similarity index 68% rename from invenio/legacy/bibfield/functions/get_number_of_comments.py rename to invenio/modules/jsonalchemy/errors.py index 2adbb4182..2aa2f937f 100644 --- a/invenio/legacy/bibfield/functions/get_number_of_comments.py +++ b/invenio/modules/jsonalchemy/errors.py @@ -1,31 +1,31 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. +class FieldParserException(Exception): + """Exception raised when some error happens parsing field definitions""" + pass -def get_number_of_comments(recid): - """ - Returns number of comments for given record. - @param recid: +class ModelParserException(Exception): + """Exception raised when some error happens parsing model definitions""" + pass - @return: Number of comments - """ - from invenio.legacy.webcomment.adminlib import get_nb_comments - if recid: - return get_nb_comments(recid) +class ReaderException(Exception): + """Exception raised when some error happens reading a blob""" + pass diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/jsonext/__init__.py similarity index 100% copy from invenio/legacy/bibfield/functions/__init__.py copy to invenio/modules/jsonalchemy/jsonext/__init__.py diff --git a/invenio/core/record/__init__.py b/invenio/modules/jsonalchemy/jsonext/engines/__init__.py similarity index 100% rename from invenio/core/record/__init__.py rename to invenio/modules/jsonalchemy/jsonext/engines/__init__.py diff --git a/invenio/utils/jsonalchemy/engines/mongodb_pymongo.py b/invenio/modules/jsonalchemy/jsonext/engines/mongodb_pymongo.py similarity index 65% rename from invenio/utils/jsonalchemy/engines/mongodb_pymongo.py rename to invenio/modules/jsonalchemy/jsonext/engines/mongodb_pymongo.py index bd3eb0354..13d9ab96e 100644 --- a/invenio/utils/jsonalchemy/engines/mongodb_pymongo.py +++ b/invenio/modules/jsonalchemy/jsonext/engines/mongodb_pymongo.py @@ -1,104 +1,101 @@ # -*- coding: utf-8 -*- - +## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ - invenio.utils.jsonalchemy.engines.mongo_pymongo + invenio.modules.jsonalchemy.engines.mongo_pymongo ------------------------------------------ """ -from itertools import izip, imap - import pymongo -from invenio.utils.jsonalchemy.storage import Storage - +from invenio.modules.jsonalchemy.storage import Storage class MongoDBStorage(Storage): """ Implements storage engine for MongoDB using the driver pymongo """ def __init__(self, model, **kwards): """ - TBC - See also :meth:`~invenio.utils.jsonalchemy.storage:Storage.__init__` + See also :meth:`~invenio.modules.jsonalchemy.storage:Storage.__init__` """ self.model = model host = kwards.get('host', 'localhost') port = kwards.get('port', 27017) database = kwards.get('database', 'invenio') self.__connection = pymongo.MongoClient(host=host,port=port) self.__database = self.__connection[database] self.__collection = self.__database[model] def save_one(self, json, id=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.save_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.save_one`""" if not id is None: json['_id'] = id return self.__collection.insert(json) def save_many(self, jsons, ids=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.save_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.save_many`""" if not ids is None: def add_id(t): t[0]['_id'] = t[1] return t[0] - jsons = impa(add_id, izip(jsons, ids)) + jsons = impa(add_id, zip(jsons, ids)) return self.__collection.insert(jsons, continue_on_error=True) def update_one(self, json, id=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.update_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.update_one`""" #FIXME: what if we get only the fields that have change if not recid is None: json['_id'] = recid return self.__collection.save(json) def update_many(self, jsons, ids=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.update_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.update_many`""" if not recids is None: def add_id(t): t[0]['_id'] = t[1] return t[0] - jsons = impa(add_id, izip(jsons, ids)) + jsons = impa(add_id, zip(jsons, ids)) - return imap(self.__collection.save, jsons) + return map(self.__collection.save, jsons) def get_one(self, id): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_one`""" return self.__collection.find_one(id) def get_many(self, ids): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_many`""" return self.__collection.find({'_id': {'$in':ids}}) - def get_field_values(recids, field, repetitive_values=True, count=False, + def get_field_values(ids, field, repetitive_values=True, count=False, include_recid=False, split_by=0): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_field_values`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_field_values`""" raise NotImplementedError() - def get_fields_values(recids, fields, repetitive_values=True, count=False, + def get_fields_values(ids, fields, repetitive_values=True, count=False, include_recid=False, split_by=0): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_fields_values`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_fields_values`""" raise NotImplementedError() - - + def search(self, query): + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.search`""" + return self.__collection.find(query) diff --git a/invenio/utils/jsonalchemy/engines/sqlalchemy.py b/invenio/modules/jsonalchemy/jsonext/engines/sqlalchemy.py similarity index 67% rename from invenio/utils/jsonalchemy/engines/sqlalchemy.py rename to invenio/modules/jsonalchemy/jsonext/engines/sqlalchemy.py index 37a7afbb4..153b4ae2e 100644 --- a/invenio/utils/jsonalchemy/engines/sqlalchemy.py +++ b/invenio/modules/jsonalchemy/jsonext/engines/sqlalchemy.py @@ -1,123 +1,119 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. -""" - invenio.core.record.backends.sqlalchemy - --------------------------------------- +import six -""" -from itertools import izip, imap - -from flask import current_app from flask.helpers import locked_cached_property from werkzeug import import_string -from invenio.utils.jsonalchemy.storage import Storage - +from invenio.modules.jsonalchemy.storage import Storage class SQLAlchemyStorage(Storage): """ Implements database backend for SQLAlchemy model storage. """ #FIXME: This storage engine should use transactions! def __init__(self, model, **kwards): """ - TBC - See also :meth:`~invenio.utils.jsonalchemy.storage:Storage.__init__` + See also :meth:`~invenio.modules.jsonalchemy.storage:Storage.__init__` """ self.__db = kwards.get('sqlalchemy_backend', 'invenio.ext.sqlalchemy:db') self.__model = model - if not self.db.engine.dialect.has_table(self.db.engine, - self.model.__tablename__): - self.model.__table__.create(bind=self.db.engine) - self.db.session.commit() @locked_cached_property def db(self): """Returns SQLAlchemy database object.""" - if isinstance(self.__db, basestring): - return import_string(self.__db) + if isinstance(self.__db, six.string_types): + self.__db = import_string(self.__db) + if not self.__db.engine.dialect.has_table(self.__db.engine, + self.model.__tablename__): + self.model.__table__.create(bind=self.__db.engine) + self.__db.session.commit() return self.__db @locked_cached_property def model(self): """Returns SQLAchemy model.""" - if isinstance(self.__model, basestring): + if isinstance(self.__model, six.string_types): return import_string(self.__model) return self.__model def save_one(self, json, id=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.save_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.save_one`""" if id is None: - id = json_record['_id'] + id = json['_id'] self.db.session.add(self.model(id=id, json=json)) self.db.session.commit() def save_many(self, jsons, ids=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.save_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.save_many`""" if ids is None: - ids = imap(lambda j: j['_id'], jsons) + ids = map(lambda j: j['_id'], jsons) self.db.session.add_all([self.model(id=id, json=json) - for id, json in izip(ids, jsons)]) + for id, json in zip(ids, jsons)]) self.db.session.commit() def update_one(self, json, id=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.update_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.update_one`""" #FIXME: what if we get only the fields that have change if id is None: id = json['id'] self.db.session.merge(self.model(id=id, json=json)) self.db.session.commit() def update_many(self, jsons, ids=None): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.update_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.update_many`""" #FIXME: what if we get only the fields that have change if ids is None: - ids = imap(lambda j: j['_id'], jsons) + ids = map(lambda j: j['_id'], jsons) - for id, json in izip(ids, jsons): + for id, json in zip(ids, jsons): self.db.session.merge(self.model(id=id, json=json)) self.db.session.commit() def get_one(self, id): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_one`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_one`""" return self.db.session.query(self.model.json)\ .filter_by(id=id).one().json def get_many(self, ids): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_many`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_many`""" for json in self.db.session.query(self.model.json)\ .filter(RecordMetadata.id.in_(ids))\ .all(): yield json[0] def get_field_values(recids, field, repetitive_values=True, count=False, include_recid=False, split_by=0): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_field_values`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_field_values`""" #TODO raise NotImplementedError() def get_fields_values(recids, fields, repetitive_values=True, count=False, include_recid=False, split_by=0): - """See :meth:`~invenio.utils.jsonalchemy.storage:Storage.get_fields_values`""" + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.get_fields_values`""" #TODO raise NotImplementedError() + + def search(query): + """See :meth:`~invenio.modules.jsonalchemy.storage:Storage.search`""" + raise NotImplementedError() diff --git a/invenio/utils/jsonalchemy/__init__.py b/invenio/modules/jsonalchemy/jsonext/functions/__init__.py similarity index 100% rename from invenio/utils/jsonalchemy/__init__.py rename to invenio/modules/jsonalchemy/jsonext/functions/__init__.py diff --git a/invenio/legacy/bibfield/functions/is_local_url.py b/invenio/modules/jsonalchemy/jsonext/functions/is_local_url.py similarity index 100% rename from invenio/legacy/bibfield/functions/is_local_url.py rename to invenio/modules/jsonalchemy/jsonext/functions/is_local_url.py diff --git a/invenio/legacy/bibfield/functions/util_split.py b/invenio/modules/jsonalchemy/jsonext/functions/util_split.py similarity index 100% rename from invenio/legacy/bibfield/functions/util_split.py rename to invenio/modules/jsonalchemy/jsonext/functions/util_split.py diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/jsonext/parsers/__init__.py similarity index 100% copy from invenio/legacy/bibfield/functions/__init__.py copy to invenio/modules/jsonalchemy/jsonext/parsers/__init__.py diff --git a/invenio/modules/jsonalchemy/jsonext/parsers/checker_parser.py b/invenio/modules/jsonalchemy/jsonext/parsers/checker_parser.py new file mode 100644 index 000000000..0de9d2887 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/parsers/checker_parser.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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., +## 60 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +from pyparsing import Optional, Suppress, OneOrMore, indentedBlock, nestedExpr,\ + originalTextFor + +from invenio.modules.jsonalchemy.parser import BaseExtensionParser, \ + function_call + + +class CheckerParser(BaseExtensionParser): + """ + """ + + @classmethod + def parse_element(cls, indent_stack): + master_format = (Suppress("@master_format") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("master_format")\ + .setParseAction(lambda toks: toks[0]) + checker_body = indentedBlock((Optional (master_format) + \ + function_call.setResultsName('func')), indent_stack) + + return (Suppress('checker:') + OneOrMore(checker_body))\ + .setResultsName('checker')\ + .setParseAction(lambda toks: toks[0]) + + @classmethod + def create_element(cls, rule, namespace): + return None + +CheckerParser.__name__ = 'checker' +parser = CheckerParser diff --git a/invenio/modules/jsonalchemy/jsonext/parsers/description_parser.py b/invenio/modules/jsonalchemy/jsonext/parsers/description_parser.py new file mode 100644 index 000000000..b0acb4586 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/parsers/description_parser.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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., +## 60 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +from pyparsing import QuotedString, Suppress, indentedBlock + +from invenio.modules.jsonalchemy.parser import BaseExtensionParser + + +class DescriptionParser(BaseExtensionParser): + """ + + """ + + @classmethod + def parse_element(cls, indent_stack): + doc_double = QuotedString(quoteChar='"""', multiline=True) + doc_single = QuotedString(quoteChar="'''", multiline=True) + doc_string = indentedBlock((doc_double | doc_single), indent_stack) + description = (Suppress('description:') + doc_string).\ + setParseAction(lambda toks: toks[0][0]) + return (description | doc_double | doc_single).setResultsName('description') + + + @classmethod + def create_element(cls, rule, namespace): + return rule.description + +DescriptionParser.__name__ = 'description' +parser = DescriptionParser diff --git a/invenio/modules/jsonalchemy/jsonext/parsers/json_extra_parser.py b/invenio/modules/jsonalchemy/jsonext/parsers/json_extra_parser.py new file mode 100644 index 000000000..fe1cdc038 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/parsers/json_extra_parser.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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., +## 60 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +from pyparsing import Optional, Suppress, indentedBlock, Each + +from invenio.base.utils import try_to_eval + +from invenio.modules.jsonalchemy.registry import functions +from invenio.modules.jsonalchemy.parser import BaseExtensionParser, FieldParser, \ + python_allowed_expr + + +class JsonExtraParser(BaseExtensionParser): + """ + Class to parse and store the information related with how to load and dump + a non-json object. + + It parses something like this:: + + json: + loads, function_to_load(field) + dumps, function_to_dump(field) + + The functions to load and dump must have one parameter which is the field + to parse. + """ + + @classmethod + def parse_element(cls, indent_stack): + json_dumps = (Suppress('dumps') + Suppress(',') + python_allowed_expr)\ + .setResultsName("dumps")\ + .setParseAction(lambda toks: toks.value[0]) + json_loads = (Suppress("loads") + Suppress(",") + python_allowed_expr)\ + .setResultsName("loads")\ + .setParseAction(lambda toks: toks.value[0]) + + func = indentedBlock(Each((json_dumps, json_loads)), indent_stack) + return (Suppress('json:') + func)\ + .setResultsName('json_ext')\ + .setParseAction(lambda toks: toks[0][0]) + + @classmethod + def create_element(cls, rule, namespace): + json_id = rule.json_id[0] + + return {'loads': try_to_eval(rule.json_ext.loads.strip(), functions(namespace)), + 'dumps': try_to_eval(rule.json_ext.dumps.strip(), functions(namespace))} + + @classmethod + def add_info_to_field(cls, json_id, rule): + info = {} + if 'json_ext' in rule: + info['dumps'] = (json_id, 'json_ext', 'dumps') + info['loads'] = (json_id, 'json_ext', 'loads') + return info + + +JsonExtraParser.__name__ = 'json_ext' +parser = JsonExtraParser diff --git a/invenio/modules/jsonalchemy/jsonext/parsers/producer_parser.py b/invenio/modules/jsonalchemy/jsonext/parsers/producer_parser.py new file mode 100644 index 000000000..40c4066c4 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/parsers/producer_parser.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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., +## 60 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +from pyparsing import Optional, Suppress, OneOrMore, Word, alphas, alphanums, \ + indentedBlock, nestedExpr, originalTextFor + +from invenio.modules.jsonalchemy.parser import BaseExtensionParser, FieldParser,\ + python_allowed_expr + + +class ProducerParser(BaseExtensionParser): + """ + """ + + @classmethod + def parse_element(cls, indent_stack): + producer_code = (Word(alphas, alphanums + "_")\ + + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName('producer_code') + producer_rule = (Suppress(',') + python_allowed_expr)\ + .setResultsName('producer_rule')\ + .setParseAction(lambda toks: toks[0]) + producer_body = indentedBlock(producer_code + producer_rule, indent_stack)\ + .setParseAction(lambda toks: toks[0]) + + return (Suppress('producer:') + OneOrMore(producer_body))\ + .setResultsName('producer') + + @classmethod + def create_element(cls, rule, namespace): + json_id = rule.json_id[0] + assert json_id in FieldParser.field_definitions(namespace) + + producers = {} + for producer in rule.producer: + if producer.producer_code[0] not in producers: + producers[producer.producer_code[0]] = [] + producers[producer.producer_code[0]].append( + (eval(producer.producer_code[1]), eval(producer.producer_rule)))#FIXME: remove eval + return producers + +ProducerParser.__name__ = 'producer' +parser = ProducerParser diff --git a/invenio/legacy/bibfield/functions/get_cited_by_count.py b/invenio/modules/jsonalchemy/jsonext/parsers/schema_parser.py similarity index 52% rename from invenio/legacy/bibfield/functions/get_cited_by_count.py rename to invenio/modules/jsonalchemy/jsonext/parsers/schema_parser.py index 36dcd1258..14c9a5636 100644 --- a/invenio/legacy/bibfield/functions/get_cited_by_count.py +++ b/invenio/modules/jsonalchemy/jsonext/parsers/schema_parser.py @@ -1,31 +1,44 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. +## 60 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +from pyparsing import QuotedString, Suppress, indentedBlock -def get_cited_by_count(recid): - """ - Return how many records cite given record. +from invenio.modules.jsonalchemy.parser import BaseExtensionParser, \ + dict_def - @param recid: - @return: Number of records citing given record +class SchemaParser(BaseExtensionParser): + """ + """ - from invenio.legacy.bibrank.citation_searcher import get_cited_by_count - if recid: - return get_cited_by_count(recid) + + @classmethod + def parse_element(cls, indent_stack): + schema = indentedBlock(dict_def,indent_stack) + return (Suppress('schema:') + schema)\ + .setParseAction(lambda toks: toks[0][0][0])\ + .setResultsName('schema') + + + @classmethod + def create_element(cls, rule, namespace): + return eval(rule.schema) + +SchemaParser.__name__ = 'schema' +parser = SchemaParser diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/jsonext/producers/__init__.py similarity index 100% copy from invenio/legacy/bibfield/functions/__init__.py copy to invenio/modules/jsonalchemy/jsonext/producers/__init__.py diff --git a/invenio/core/record/recordext/producers/marc.py b/invenio/modules/jsonalchemy/jsonext/producers/json_for_marc.py similarity index 87% rename from invenio/core/record/recordext/producers/marc.py rename to invenio/modules/jsonalchemy/jsonext/producers/json_for_marc.py index 260507242..9782cbe79 100644 --- a/invenio/core/record/recordext/producers/marc.py +++ b/invenio/modules/jsonalchemy/jsonext/producers/json_for_marc.py @@ -1,64 +1,64 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. - def produce(self, fields=None): """ Export the record in marc format. @param tags: list of tags to include in the output, if None or empty list all available tags will be included. """ - from invenio.legacy.bibfield.bibfield_utils import get_producer_rules + from invenio.modules.jsonalchemy.parser import get_producer_rules if not fields: fields = self.keys() out = [] for field in fields: if field.startswith('__'): continue try: - marc_rules = get_producer_rules(field, 'xm') + marc_rules = get_producer_rules(field, 'json_for_marc') for rule in marc_rules: field = self.get(rule[0], None) if field is None: continue if not isinstance(field, list): field = [field, ] for f in field: for r in rule[1]: tmp_dict = {} - for key, subfield in r.iteritems(): + #FIXME: check field meta_metadata + for key, subfield in r[1].iteritems(): if not subfield: tmp_dict[key] = f else: try: tmp_dict[key] = f[subfield] except: try: tmp_dict[key] = self._try_to_eval(subfield, value=f) - except Exception,e: + except Exception as e: self['__error_messages.cerror[n]'] = 'Producer CError - Unable to produce %s - %s' % (field, str(e)) if tmp_dict: out.append(tmp_dict) except KeyError: self['__error_messages.cerror[n]'] = 'Producer CError - No producer rule for field %s' % field return out diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/jsonext/readers/__init__.py similarity index 100% copy from invenio/legacy/bibfield/functions/__init__.py copy to invenio/modules/jsonalchemy/jsonext/readers/__init__.py diff --git a/invenio/modules/jsonalchemy/jsonext/readers/json_reader.py b/invenio/modules/jsonalchemy/jsonext/readers/json_reader.py new file mode 100644 index 000000000..ce3a49af1 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/readers/json_reader.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.jsonext.readers.json_reader + -------------------------------------------------------- + +""" +import re + +from invenio.modules.jsonalchemy.reader import Reader + +class JsonReader(Reader): + """Default reader""" + + __master_format__ = 'json' + + def __init__(self, blob, **kwargs): + """ + :param blob: + """ + super(JsonReader, self).__init__(blob=blob, **kwargs) + self._additional_info['master_format'] = 'json' + + @staticmethod + def split_blob(blob, schema=None, **kwargs): + """ + In case of several records inside the blob this method specify how to + split then and work one by one afterwards. + """ + return blob.splitlines() + + def _prepare_blob(self, *args, **kwargs): + self.json.update(self.blob) + + def _get_elements_from_blob(self, regex_key): + if regex_key in ('entire_record', '*'): + return self.blob + return [self.blob.get(key) for key in regex_key] + + def _apply_rules(self, json_id, field_name, rule_def): + try: + info = self._find_meta_metadata(json_id, field_name, 'creator', {'source_tag':json_id}, rule_def) + if 'json_ext' in rule_def and field_name in self.json: + self.json[field_name] = rule_def['json_ext']['dumps'](self.json[field_name]) + self.json['__meta_metadata__.%s' % (field_name, )] = info + except KeyError: + self._set_default_value(json_id, field_name) + except Exception, e: + self.json['__meta_metadata__']['__errors__']\ + .append('Rule Error - Unable to apply rule for field %s - %s' % (field_name, str(e)),) + return False + return True + + def _apply_virtual_rules(self, json_id, field_name, rule_def): + if field_name in self.json: + try: + info = self._find_meta_metadata(json_id, field_name, rule_type, rule, rule_def) + if rule_type == 'derived' or rule['memoize']: + if 'json_ext' in rule_def: + self.json[field_name] = rule_def['json_ext']['dumps'](self.json[field_name]) + else: + self.json[field_name] = None + except Exception, e: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append('Virtual Rule CError - Unable to evaluate %s - %s' % (field_name, str(e))) + return False + else: + return super(JsonReader, self)._apply_virtual_rules(json_id, field_name, rule_def) + +reader = JsonReader diff --git a/invenio/modules/jsonalchemy/jsonext/readers/marc_reader.py b/invenio/modules/jsonalchemy/jsonext/readers/marc_reader.py new file mode 100644 index 000000000..cf933b3b7 --- /dev/null +++ b/invenio/modules/jsonalchemy/jsonext/readers/marc_reader.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.jsonext.readers.marc_reader + -------------------------------------------------------- + +""" +import re + +from invenio.modules.jsonalchemy.reader import Reader + +class MarcReader(Reader): + """Marc reader""" + + __master_format__ = 'marc' + + split_marc = re.compile('.*?
    ', re.DOTALL) + + def __init__(self, blob=None, **kwargs): + """ + :param blob: + """ + super(MarcReader, self).__init__(blob=blob, **kwargs) + self._additional_info['master_format'] = 'marc' + + @staticmethod + def split_blob(blob, schema=None, **kwargs): + """ + Splits the blob using .*? as pattern. + + Note 1: Taken from invenio.legacy.bibrecord:create_records + Note 2: Use the DOTALL flag to include newlines. + """ + if schema in (None, 'xml'): + for match in MarcReader.split_marc.finditer(blob): + yield match.group() + else: + raise StopIteration() + + def _get_elements_from_blob(self, regex_key): + if regex_key in ('entire_record', '*'): + return self.rec_tree + elements = [] + for k in regex_key: + regex = re.compile(k) + keys = filter(regex.match, self.rec_tree.keys()) + values = [] + for key in keys: + values.append(self.rec_tree.get(key)) + elements.extend(values) + return elements + + def _prepare_blob(self, *args, **kwargs): + #FIXME stop using recstruct! + from invenio.legacy.bibrecord import create_record + + class SaveDict(dict): + __getitem__ = dict.get + + def dict_extend_helper(d, key, value): + """ + If the key is present inside the dictionary it creates a list (it not + present) and extends it with the new value. Almost as in C{list.extend} + """ + if key in d: + current_value = d.get(key) + if not isinstance(current_value, list): + current_value = [current_value] + current_value.append(value) + value = current_value + d[key] = value + + self.rec_tree = SaveDict() + tmp = create_record(self.blob)[0] + for key, values in tmp.iteritems(): + if key < '010' and key.isdigit(): + self.rec_tree[key] = [value[3] for value in values] + else: + for value in values: + field = SaveDict() + for subfield in value[0]: + dict_extend_helper(field, subfield[0], subfield[1]) + dict_extend_helper(self.rec_tree, (key + value[1] + value[2]).replace(' ', '_'), field) + +reader = MarcReader diff --git a/invenio/modules/jsonalchemy/parser.py b/invenio/modules/jsonalchemy/parser.py new file mode 100644 index 000000000..54aee4b5d --- /dev/null +++ b/invenio/modules/jsonalchemy/parser.py @@ -0,0 +1,724 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.parser + --------------------------------- + + Fields and models configuration loader. + + This module uses `pyparsing ` to read from + thedifferent configuration files the field and model definitions. + + Extensions to both parsers could be added inside jsonext.parsers +""" +import os +import re +import six + +from pyparsing import ParseException, FollowedBy, Suppress, OneOrMore, Word, \ + LineEnd, ZeroOrMore, Optional, Literal, alphas, alphanums, \ + originalTextFor, oneOf, nestedExpr, quotedString, removeQuotes, lineEnd, \ + empty, col, restOfLine, delimitedList, Each, indentedBlock + + +from .errors import FieldParserException, ModelParserException +from .registry import fields_definitions, models_definitions, parsers + +json_id = (Word(alphas + "_", alphanums + "_") + Optional(oneOf("[0] [n]")))\ + .setResultsName("json_id", listAllMatches=True)\ + .setParseAction(lambda toks: "".join(toks)) + +ident = Word(alphas + "_", alphanums + "_") +dict_def = originalTextFor(nestedExpr('{', '}')) +list_def = originalTextFor(nestedExpr('[', ']')) +dict_access = list_access = originalTextFor(ident + nestedExpr('[', ']')) +function_call = originalTextFor(ZeroOrMore(ident + ".") + ident + nestedExpr('(', ')')) + +python_allowed_expr = (dict_def ^ list_def ^ dict_access ^ \ + list_access ^ function_call ^ restOfLine)\ + .setResultsName("value", listAllMatches=True) + +def _create_record_field_parser(): + """ + Creates the base parser that can handle field definitions and adds any + extension placed inside jsonext.parsers. + + BFN like base libray:: + line ::= python_comment | include | field_def + + include ::= "include(" PATH ")" + + field_def ::= [persitent_identifier] [inherit_from] [override] [extend] json_id ["[0]" | "[n]"] "," aliases":" INDENT field_def_body UNDENT + field_def_body ::= [default] (creator | derived | calculated) + aliases ::= json_id ["[0]" | "[n]"] ["," aliases] + json_id ::= (alphas + '_') (alphanums + '_') + + creator ::= "creator:" INDENT creator_body+ UNDENT + creator_body ::= [decorators] source_format "," source_tags "," python_allowed_expr + source_format ::= MASTER_FORMATS + source_tag ::= QUOTED_STRING #reStrucuredText + + derived ::= "derived:" INDENT derived_calculated_body UNDENT + calculated ::= "calculated:" INDENT derived_calculated_body UNDENT + derived_calculated_body ::= [decorators] "," python_allowed_exp + + decorators ::= (legacy | memoize | parse_first | depends_on | only_if | only_if_master_value)* + legacy ::= "@legacy(" correspondences+ ")" + correspondences ::= "(" source_tag [ "," field_tag_name ] "," subfield ")" # If no subfield needed empty string + parse_first ::= "@parse_first(" json_id+ ")" + depends_on ::= "@depends_on(" json_id+ ")" + only_if ::= "@only_if(" python_condition+ ")" + only_if_master_value ::= "@only_if_master_value(" python_condition+ ")" + + persistent_identifier ::= @persistent_identifier( level ) + inherit_from ::= "@inherit_from(" json_id+ ")" + override ::= "@override" + extend ::= "@extend" + memoize ::= "@memoize(time)" + + python_allowed_exp ::= ident | list_def | dict_def | list_access | dict_access | function_call | one_line_expr + """ + indent_stack = [1] + + def check_sub_indent(string, location, tokens): + cur_col = col(location, string) + if cur_col > indent_stack[-1]: + indent_stack.append(cur_col) + else: + raise ParseException(string, location, "not a subentry") + + def check_unindent(string, location, tokens): + if location >= len(string): + return + cur_col = col(location, string) + if not(cur_col < indent_stack[-1] and cur_col <= indent_stack[-2]): + raise ParseException(string, location, "not an unindent") + + def do_unindent(): + indent_stack.pop() + + INDENT = lineEnd.suppress() + empty + empty.copy().setParseAction(check_sub_indent) + UNDENT = FollowedBy(empty).setParseAction(check_unindent) + UNDENT.setParseAction(do_unindent) + + aliases = delimitedList((Word(alphanums + "_") + Optional(oneOf("[0] [n]")))\ + .setParseAction(lambda toks: "".join(toks)))\ + .setResultsName("aliases") + + persistent_identifier = (Suppress("@persistent_identifier") + \ + nestedExpr("(", ")"))\ + .setResultsName("persistent_identifier") + legacy = (Suppress("@legacy") + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("legacy", listAllMatches=True) + only_if = (Suppress("@only_if") + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("only_if") + only_if_master_value = (Suppress("@only_if_value") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("only_if_master_value") + depends_on = (Suppress("@depends_on") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("depends_on") + parse_first = (Suppress("@parse_first") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("parse_first") + memoize = (Suppress("@memoize") + nestedExpr("(", ")"))\ + .setResultsName("memoize") + field_decorator = parse_first ^ depends_on ^ only_if ^ \ + only_if_master_value ^ memoize ^ legacy + + #Independent decorators + inherit_from = (Suppress("@inherit_from") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("inherit_from") + override = (Suppress("@") + "override")\ + .setResultsName("override") + extend = (Suppress("@") + "extend")\ + .setResultsName("extend") + + derived_calculated_body = (ZeroOrMore(field_decorator) + python_allowed_expr)\ + .setResultsName('derived_calculated_def') + + derived = "derived" + Suppress(":") + \ + INDENT + derived_calculated_body + UNDENT + calculated = "calculated" + Suppress(":") + \ + INDENT + derived_calculated_body + UNDENT + + source_tag = quotedString\ + .setParseAction(removeQuotes)\ + .setResultsName("source_tag", listAllMatches=True) + source_format = Word(alphas, alphanums + "_")\ + .setResultsName("source_format", listAllMatches=True) + creator_body = (ZeroOrMore(field_decorator) + source_format + \ + Suppress(",") + source_tag + Suppress(",") + python_allowed_expr)\ + .setResultsName("creator_def", listAllMatches=True) + creator = "creator" + Suppress(":") + \ + INDENT + OneOrMore(creator_body) + UNDENT + + field_def = (creator | derived | calculated)\ + .setResultsName("type_field", listAllMatches=True) + + body = Each([Optional(field_def),] + \ + [Optional(p.parser.parse_element(indent_stack)) for p in parsers]) + comment = Literal("#") + restOfLine + LineEnd() + include = (Suppress("include") + quotedString)\ + .setResultsName("includes", listAllMatches=True) + rule = (Optional(persistent_identifier) + Optional(inherit_from) + \ + Optional(override) + Optional(extend) +json_id + \ + Optional(Suppress(",") + aliases) + Suppress(":") + \ + INDENT + body + UNDENT)\ + .setResultsName("rules", listAllMatches=True) + + return OneOrMore(rule | include | comment.suppress()) + + +def _create_record_model_parser(): + """ + Creates a parser that can handle model definitions. + + BFN like grammar:: + + record_model ::= python_comment | fields + fields ::= "fields:" INDENT [inherit_from] [list_of_fields] + inherit_from ::= "@inherit_from(" json_id+ ")" + list_of_fields ::= json_id [ "=" json_id ] # new field name = existing field name + + Note: Unlike the field configuration files where you can specify more than + one field inside each file for the record models only one definition is + allowed by file. + """ + indent_stack = [1] + + field_def = (Word(alphas + "_", alphanums + "_") + \ + Optional(Suppress("=") + \ + Word(alphas + "_", alphanums + "_")))\ + .setResultsName("field_definition") + inherit_from = (Suppress("@inherit_from") + \ + originalTextFor(nestedExpr("(", ")")))\ + .setResultsName("inherit_from") + + fields = (Suppress("fields:") + \ + indentedBlock(inherit_from | field_def, indent_stack))\ + .setResultsName("fields") + comment = Literal("#") + restOfLine + LineEnd() + return OneOrMore(comment | Each([fields, ] + \ + [Optional(p.parser.parse_element(indent_stack)) for p in parsers])) + + +class FieldParser(object): + """Field definitions parser""" + + _field_definitions = {} + """Dictionary containing all the rules needed to create and validate json fields""" + + _legacy_field_matchings = {} + """Dictionary containing matching between the legacy master format and the current json""" + + def __init__(self, namespace): + self.files = list(fields_definitions(namespace)) + self.__namespace = namespace + self.__inherit_rules = [] + self.__unresolved_inheritence = [] + self.__override_rules = [] + self.__extend_rules = [] + + @classmethod + def field_definitions(cls, namespace): + if namespace not in cls._field_definitions: + cls.reparse(namespace) + return cls._field_definitions.get(namespace) + + @classmethod + def field_definition_model_based(cls, field_name, model_name, namespace): + if model_name in ModelParser.model_definitions(namespace): + field_name = ModelParser.model_definitions(namespace)[model_name] \ + ['fields'].get(field_name, field_name) + return cls.field_definitions(namespace).get(field_name, None) + + @classmethod + def legacy_field_matchings(cls, namespace): + if namespace not in cls._legacy_field_matchings: + cls.reparse(namespace) + return cls._legacy_field_matchings + + @classmethod + def reparse(cls, namespace): + cls._field_definitions[namespace] = {} + cls._legacy_field_matchings = {} + cls(namespace)._create() + # It invalidates the Model definitions too as they relay on the field definitions + ModelParser.reparse(namespace) + + def _create(self): + """ + Fills up _field_definitions and _legacy_field_matchings dictionary with + the rules defined inside the configuration files. + + It also resolve the includes present inside the configuration files and + recursively the ones in the other files. + + This method should not be used (unless you really know what your are doing), + use instead :meth:`reparse` + """ + already_included = [os.path.basename(f) for f in self.files] + for field_file in self.files: + parser = _create_record_field_parser() + field_descs = parser.parseFile(field_file, parseAll=True) + for include in field_descs.includes: + if include[0] in already_included: + continue + if not os.path.exists(include[0]): + raise FieldParserException("Can't find file: %s" % (include[0], )) + self.files.append(include[0]) + for rule in field_descs.rules: + if rule.override: + self.__override_rules.append(rule) + elif rule.extend: + self.__extend_rules.append(rule) + elif rule.inherit_from: + self.__inherit_rules.append(rule) + else: + self._create_rule(rule) + + self.__resolve_inherit_rules() + self.__resolve_override_rules() + self.__resolve_extend_rules() + + def _create_rule(self, rule, override=False, extend=False): + """ + Creates the field and legacy definitions. + The result looks like this:: + + {key: [key1, key2], + key1: {inherit_from: [], + override: True/False, + extend: True/False, + aliases: [], + persistent_identifier: num/None, + rules: {'master_format_1': [{rule1}, {rule2}, ...], + 'master_format_2': [....], + ...... + 'calculated': [....], + 'derived': [...]} + } + } + + Each of the rule (rule1, rule2, etc.) has the same content:: + + {'source_format' : [translation_rules]/None, + 'parse_first' : (parse_first_json_ids), + 'depends_on' : (depends_on_json_id), + 'only_if' : (only_if_boolean_expressions), + 'only_if_master_value': (only_if_master_value_boolean_expressions), + 'memoize' : time, + 'value' : value coming from master format + } + + """ + json_id = rule.json_id[0] + #Chech duplicate names + if json_id in self.__class__._field_definitions[self.__namespace] and not override and not extend: + raise FieldParserException("Name error: '%s' field name already defined" + % (rule.json_id[0],)) + if not json_id in self.__class__._field_definitions[self.__namespace] and (override or extend): + raise FieldParserException("Name error: '%s' field name not defined" + % (rule.json_id[0],)) + + #Workaround to keep clean doctype files + #Just creates a dict entry with the main json field name and points it to + #the full one i.e.: 'authors' : ['authors[0]', 'authors[n]'] + if '[0]' in json_id or '[n]' in json_id: + main_json_id = re.sub('(\[n\]|\[0\])', '', json_id) + if not main_json_id in self.__class__._field_definitions[self.__namespace]: + self.__class__._field_definitions[self.__namespace][main_json_id] = [] + self.__class__._field_definitions[self.__namespace][main_json_id].append(json_id) + + aliases = [] + if rule.aliases: + aliases = rule.aliases.asList() + + persistent_id = None + if rule.persistent_identifier: + persistent_id = int(rule.persistent_identifier[0][0]) + + inherit_from = None + if rule.inherit_from: + self.__unresolved_inheritence.append(json_id) + inherit_from = eval(rule.inherit_from[0]) + + if extend: + rules = self.__class__._field_definitions[self.__namespace][json_id]['rules'] + else: + rules = {} + + #TODO: check if pyparsing can handle this! + all_type_def = [] + if rule.creator_def: + all_type_def = [r for r in rule.creator_def] + if all_type_def and rule.derived_calculated_def: + all_type_def.append(rule.derived_calculated_def) + elif rule.derived_calculated_def: + all_type_def = [rule.derived_calculated_def] + + for r in all_type_def: + if r.source_format: + source = r.source_format[0] + source_tag = r.source_tag[0].split() + else: + source = rule.type_field[0] + source_tag = None + + if source not in rules: + #Allow several tags point to the same json id + rules[source] = [] + (depends_on, only_if, only_if_master_value, + parse_first, memoize) = self.__create_decorators_content(r) + self._create_legacy_rules(r.legacy, json_id, source) + + rules[source].append({'source_tag' : source_tag, + 'parse_first' : parse_first, + 'depends_on' : depends_on, + 'only_if' : only_if, + 'only_if_master_value': only_if_master_value, + 'memoize' : memoize, + 'value' : compile(r.value[0].strip(), '', 'eval'), + }) + + if override: + self.__class__._field_definitions[self.__namespace][json_id]['override'] = override + self.__class__._field_definitions[self.__namespace][json_id]['rules'].update(rules) + self.__class__._field_definitions[self.__namespace][json_id]['aliases'] = \ + aliases or self.__class__._field_definitions[self.__namespace][json_id]['aliases'] + self.__class__._field_definitions[self.__namespace][json_id]['persistent_identifier'] = \ + persistent_id or self.__class__._field_definitions[self.__namespace][json_id]['persistent_identifier'] + self.__class__._field_definitions[self.__namespace][json_id]['inherit_from'] = \ + inherit_from or self.__class__._field_definitions[self.__namespace][json_id]['inherit_from'] + elif extend: + self.__class__._field_definitions[self.__namespace][json_id]['extend'] = extend + self.__class__._field_definitions[self.__namespace][json_id]['aliases'].extend(aliases) + else: + self.__class__._field_definitions[self.__namespace][json_id] = {'inherit_from' : inherit_from, + 'rules' : rules, + 'aliases' : aliases, + 'persistent_identifier': persistent_id, + 'override' : override, + 'extend' : extend, + } + + self.__resolve_parser_extensions(rule) + + + def _create_legacy_rules(self, legacy_rules, json_id, source_format=None): + """ + Creates the legacy rules dictionary:: + + {'100' : ['authors[0]'], + '100__' : ['authors[0]'], + '100__%': ['authors[0]'], + '100__a': ['auhtors[0].full_name'], + ....... + } + """ + if not legacy_rules: + return + for legacy_rule in legacy_rules: + legacy_rule = eval(legacy_rule[0]) + + if source_format in ('derived', 'calculated'): + inner_source_format = legacy_rule[0] + legacy_rule = legacy_rule[1] + else: + inner_source_format = source_format + + if not inner_source_format in self.__class__._legacy_field_matchings: + self.__class__._legacy_field_matchings[inner_source_format] = {} + + for field_legacy_rule in legacy_rule: + #Allow string and tuple in the config file + legacy_fields = isinstance(field_legacy_rule[0], six.string_types) and (field_legacy_rule[0], ) or field_legacy_rule[0] + json_field = json_id + if field_legacy_rule[-1]: + json_field = '.'.join((json_field, field_legacy_rule[-1])) + for legacy_field in legacy_fields: + if not legacy_field in self.__class__._legacy_field_matchings[inner_source_format]: + self.__class__._legacy_field_matchings[inner_source_format][legacy_field] = [] + self.__class__._legacy_field_matchings[inner_source_format][legacy_field].append(json_field) + + def __resolve_parser_extensions(self, rule): + """ + For each of the extension available it tries to apply it in the incoming + rule + """ + json_id = rule.json_id[0] + assert json_id in self.__class__._field_definitions[self.__namespace] + for parser_extension in parsers: + if getattr(rule, parser_extension.parser.__name__, None): + self.__class__._field_definitions[self.__namespace][json_id][parser_extension.parser.__name__] = \ + parser_extension.parser.create_element(rule, self.__namespace) + + #FIXME: it might be nice to have the decorators also extendibles + def __create_decorators_content(self, rule): + """ + Extracts from the rule all the possible decorators. + """ + depends_on = only_if = only_if_master_value = parse_first = memoize = None + + if rule.depends_on: + depends_on = rule.depends_on[0] + if rule.only_if: + only_if = rule.only_if[0] + if rule.only_if_master_value: + only_if_master_value = rule.only_if_master_value[0] + if rule.parse_first: + parse_first = rule.parse_first[0] + if rule.memoize: + try: + memoize = int(rule.memoize[0][0]) + except IndexError: + memoize = 300 # FIXME: Default value will be used + + return (depends_on, only_if, only_if_master_value, parse_first, memoize) + + def __resolve_inherit_rules(self): + """ + Iterates over all the 'inherit' fields after all the normal field + creation to avoid problem when creating this rules. + """ + def resolve_inheritance(json_id): + rule = self.__class__._field_definitions[self.__namespace][json_id] + inherit_from_list = self.__class__._field_definitions[self.__namespace][json_id]['inherit_from'] + for inherit_json_id in inherit_from_list: + #Check if everithing is fine + if inherit_json_id == json_id: + raise FieldParserException("Inheritance from itself") + if inherit_json_id not in self.__class__._field_definitions[self.__namespace]: + raise FieldParserException("Unable to solve %s inheritance" % (inherit_json_id,)) + if inherit_json_id in self.__unresolved_inheritence: + self._resolve_inheritance(inherit_json_id) + self.__unresolved_inheritence.remove(inherit_json_id) + inherit_rule = self.__class__._field_definitions[self.__namespace][inherit_json_id] + for format in inherit_rule['rules']: + if not format in rule['rules']: + rule['rules'][format] = [] + rule['rules'][format].extend(inherit_rule['rules'][format]) + # rule['checker'].extend(inherit_rule['checker']) + + for rule in self.__inherit_rules: + self._create_rule(rule) + + #Resolve inheritance + for i in xrange(len(self.__unresolved_inheritence) - 1, -1, -1): + resolve_inheritance(self.__unresolved_inheritence[i]) + del self.__unresolved_inheritence[i] + + + def __resolve_override_rules(self): + """ + Iterates over all the 'override' field to override the already created + fields. + """ + for rule in self.__override_rules: + self._create_rule(rule, override=True) + + def __resolve_extend_rules(self): + """ + Iterates over all the 'extend' field to extend the rule definition of this + field. + """ + for rule in self.__extend_rules: + self._create_rule(rule, extend=True) + + +class ModelParser(object): + """Record model parser""" + + _model_definitions = {} + """ """ + + def __init__(self, namespace): + #Autodiscover .cfg files + self.files = list(models_definitions(namespace)) + self.__namespace = namespace + + @classmethod + def model_definitions(cls, namespace): + if namespace not in cls._model_definitions: + cls.reparse(namespace) + return cls._model_definitions.get(namespace) + + @classmethod + def reparse(cls, namespace): + cls._model_definitions[namespace] = {} + cls(namespace)._create() + + def _create(self): + """ + Fills up _model_definitions dictionary with what is written inside the + *.cfg model descriptions + + It also resolve inheritance at creation time and name matching for the + field names present inside the model file + + The result looks like this:: + + {'model': {'fields': {'name_for_fieldfield1': json_id1, + 'name_for_field2': json_id2, + .... + 'name_for_fieldN': fieldN }, + 'inherit_from: [(inherit_from_list), ...] + }, + ... + } + + This method should not be used (unless you really know what your are doing), + use instead :meth:`reparse` + + :raises: ModelParserException in case of missing model definition + (helpful if we use inheritance) or in case of unknown field name. + """ + for model_file in self.files: + parser = _create_record_model_parser() + model_name = os.path.basename(model_file).split('.')[0] + if model_name in self.__class__._model_definitions[self.__namespace]: + raise ModelParserException("Already defined record model: %s" % (model_name,)) + self.__class__._model_definitions[self.__namespace][model_name] = {'fields': {}, + 'super': [], + } + model_definition = parser.parseFile(model_file, parseAll=True) + + if not model_definition.fields: + raise ModelParserException("Field definition needed") + for field_def in model_definition.fields[0]: + if field_def.inherit_from: + self.__class__._model_definitions[self.__namespace][model_name]['super'].extend(eval(field_def[0])) + else: + if len(field_def) == 1: + json_id = field_def[0] + field_name = json_id + else: + field_name = field_def[0] + json_id = field_def[1] + if not json_id in FieldParser.field_definitions(self.__namespace): + raise ModelParserException("Unknown field name: %s" % (json_id,)) + + self.__class__._model_definitions[self.__namespace][model_name]['fields'][field_name] = json_id + + self.__resolve_parser_extensions(model_name, model_definition) + + for model, model_definition in self.__class__._model_definitions[self.__namespace].items(): + model_definition['fields'] = self.__resolve_inheritance(model) + + def __resolve_inheritance(self, model): + """ + Resolves the inheritance + + :param model: name of the super model + :type model: string + + :return: List of new fields to be added to the son model + :raises: ModelParserException if the super model does not exist. + """ + try: + model_definition = self.__class__._model_definitions[self.__namespace][model] + except KeyError: + raise ModelParserException("Missing model definition for %s" % (model,)) + fields = {} + for super_model in model_definition['super']: + fields.update(self.__resolve_inheritance(super_model)) + fields.update(model_definition['fields']) + return fields + + def __resolve_parser_extensions(self, model_name, model_def): + """ + For each of the extension available it tries to apply it in the incoming + rule + """ + assert model_name in self.__class__._model_definitions[self.__namespace] + for parser_extension in parsers: + if getattr(model_def, parser_extension.parser.__name__, None): + self.__class__._model_definitions[self.__namespace][model_name][parser_extension.parser.__name__] = \ + parser_extension.parser.create_element(model_def, self.__namespace) + +def guess_legacy_field_names(fields, master_format, namespace): + """ + Using the legacy rules written in the config file (@legacy) tries to find + the equivalent json field for one or more legacy fields. + + >>> guess_legacy_fields(('100__a', '245'), 'marc') + {'100__a':['authors[0].full_name'], '245':['title']} + """ + res = {} + if isinstance(fields, six.string_types): + fields = (fields, ) + for field in fields: + try: + res[field] = FieldParser.legacy_field_matchings(namespace)[master_format].get(field, []) + except: + res[field] = [] + return res + +def get_producer_rules(field, code, namespace): + """docstring for get_producer_rules""" + + rule = FieldParser.field_definitions(namespace)[field] + if isinstance(rule, list): + if len(rule) == 1: + # case field[n] + return [(rule[0].replace('[n]', ''), FieldParser.field_definitions(namespace)[rule[0]]['producer'].get(code, {}))] + else: + # case field[1], field[n] + rules = [] + for new_field in rule: + rules.append((new_field.replace('[n]', '[1:]'), FieldParser.field_definitions(namespace)[new_field]['producer'].get(code, {}))) + return rules + else: + return [(field, rule['producer'].get(code, {}))] + +#pylint: disable=R0921 +class BaseExtensionParser(object): + """Base class for the configuration file extensions""" + @classmethod + def parse_element(cls, indent_stack): + """ + Using pyparsing defines a piece of the grammar to parse the + extension from configuration file + + :return: pyparsing ParseElement + """ + raise NotImplemented + + @classmethod + def create_element(cls, rule, namespace): + """ + Once the extension is parsed defines the actions that have to be taken + to store inside the field_definitions the information needed or useful. + + :return: content of the key cls.__name__ inside the field_definitions + """ + raise NotImplemented + + @classmethod + def add_info_to_field(cls, json_id, info): + """ + Optional method to define which information goes inside the + __meta_metadata__ dictionary and how. + + :return: dictionary to update the existing one inside __meta_metadata__ + """ + return dict() diff --git a/invenio/modules/jsonalchemy/reader.py b/invenio/modules/jsonalchemy/reader.py new file mode 100644 index 000000000..c84d534d1 --- /dev/null +++ b/invenio/modules/jsonalchemy/reader.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.reader + ---------------------------------- + + Default reader, it could be considered as an interface to be implemented by + the other readers. +""" +import datetime +import six + +from invenio.base.utils import try_to_eval +from invenio.utils.datastructures import SmartDict + +from .errors import ReaderException +from .parser import FieldParser, ModelParser +from .registry import functions, parsers + + +class Reader(object): + """Default reader""" + + def __init__(self, blob=None, **kwargs): + """ + :param blob: + """ + self.blob = blob + self.json = None + self._additional_info = kwargs + self._additional_info['model'] = kwargs.get('model', '__default__') + self._additional_info['namespace'] = kwargs.get('namespace', None) + + if self._additional_info['namespace'] is None: + raise ReaderException('A namespace is needed to instantiate a reader') + + self._parsed = [] + + @staticmethod + def split_blob(blob, schema=None, **kwargs): + """ + In case of several records inside the blob this method specify how to + split then and work one by one afterwards. + """ + raise NotImplementedError() + + @property + def field_definitions(self): + return FieldParser.field_definitions(self._additional_info['namespace']) + + @property + def model_definitions(self): + return ModelParser.model_definitions(self._additional_info['namespace']) + + @property + def functions(self): + return functions(self._additional_info['namespace']) + + def translate(self): + """ + It transforms the incoming blob into a json structure using the rules + described into the field and model definitions. + To apply this rules it takes into account the type of the reader, which + in fact means the type of the source format or `master_format` + + :return: Json structure (typically a dictionary) + """ + if not self.blob: + raise ReaderException("To perform a 'translate' operation a blob is needed") + + # If we already have the json return it, use add or update to modify it + if self.json: + return self.json + + self.json = {} + self.json['__meta_metadata__'] = {} + self.json['__meta_metadata__']['__additional_info__'] = self._additional_info + self.json['__meta_metadata__']['__aliases__'] = {} + self.json['__meta_metadata__']['__errors__'] = [] + self.json['__meta_metadata__']['__continuable_errors__'] = [] + if self._additional_info['model'] == '__default__' or \ + self._additional_info['model'] not in self.model_definitions: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append("Warning - Using 'default' model for 'transalte', given model: '%s'" % (self._additional_info['model'], )) + fields = dict(zip(self.field_definitions.keys(), self.field_definitions.keys())) + else: + fields = self.model_definitions[self._additional_info['model']]['fields'] + + self.add(self.json, self.blob, fields) + return self.json._dict + + def add(self, json, blob, fields): + """Adds the list of fields to the json structure""" + self.json = json if isinstance(json, SmartDict) else SmartDict(json) + self.blob = blob + + if not self.blob or not self.json: + raise ReaderException("To perform an 'add' operation a json structure and a blob are needed") + + if not isinstance(fields, dict): + if isinstance(fields, six.string_types): + fields = (fields, ) + try: + model = self.json['__meta_metadata__']['__additional_info__']['model'] + except KeyError as e: + raise ReaderException('The json structure must contain a model (%s)' % (e, )) + + if model == '__default__' or model not in self.model_definitions: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append("Warning - Using 'default' model for 'add', given model: '%s'" % (model, )) + fields = dict(zip(fields, fields)) + else: + fields = dict((field, self.model_definitions[model]['fields'].get(field, field)) + for field in fields) + + self._prepare_blob() + + for field_name, json_id in fields.items(): + self._unpack_rule(json_id, field_name) + + self._post_process_json() + + + def set(self, json, field): + """ + + """ + self.json = json if isinstance(json, SmartDict) else SmartDict(json) + try: + model = self.json['__meta_metadata__']['__additional_info__']['model'] + except KeyError as e: + raise ReaderException('The json structure must contain a model (%s)' % (e, )) + + if model == '__default__' or model not in self.model_definitions: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append("Warning - Using 'default' model for 'add', given model: '%s'" % (model, )) + json_id = field + else: + json_id = self.model_definitions[model]['fields'].get(field, field) + + try: + rule = self.field_definitions[json_id] + except KeyError: + rule = {} + self.json['__meta_metadata__']['__continuable_errors__']\ + .append("Adding a new field '%s' without definition" % (field)) + + try: + if self.json['__meta_metadata__']['__additional_info__']['master_format'] in rule['rules']: + rule_def = rule['rules'][self.json['__meta_metadata__']['__additional_info__']['master_format']][0] + rule_type = 'creator' + elif 'derived' in rule['rules']: + rule_def = rule['rules']['derived'][0] + rule_type = 'derived' + elif 'calculated' in rule['rules']: + rule_def = rule['rules']['calculated'][0] + rule_type = 'calculated' + else: + rule_def = {} + rule_type = 'UNKNOWN' + except KeyError: + rule_def = {} + rule_type = 'UNKNOWN' + + self.json['__meta_metadata__'][field] = self._find_meta_metadata(json_id, field, rule_type, rule, rule_def) + + def update(self, json, blob, fields=None): + """ + Tries to update the json structure with the fields given. + If no fields are given then it will try to update all the fields inside + the json structure. + """ + + if not blob or not blob: + raise ReaderException("To perform an 'add' operation a json structure and a blob are needed") + + try: + model = json['__meta_metadata__']['__additional_info__']['model'] + except KeyError as e: + raise ReaderException('The json structure must contain a model (%s)' % (e, )) + + if not fields: + fields = dict(zip(json.keys(), json.keys())) + if model == '__default__' or model not in self.model_definitions: + json['__meta_metadata__']['__continuable_errors__']\ + .append("Warning - Using 'default' model for 'update', given model: '%s'" % (model, )) + else: + fields = dict(fields, **self.model_definitions[model]['fields']) + elif not isinstance(fields, dict): + if isinstance(fields, six.string_types): + fields = (fields, ) + if model == '__default__' or model not in self.model_definitions: + json['__meta_metadata__']['__continuable_errors__']\ + .append("Warning - Using 'default' model for 'update', given model: '%s'" % (model, )) + fields = dict(zip(fields, fields)) + else: + fields = dict((field, self.model_definitions[model]['fields'].get(field, field)) + for field in fields) + +# for key in fields.keys(): +# del json['key'] + + self.add(json, blob, fields) + + + def validate(self, reset=True): + """docstring for validate""" + pass + + def _prepare_blob(self, *args, **kwargs): + """ + Responsible of doing any kind of transformation over the blob before the + translation begins + """ + raise NotImplemented + + def _get_elements_from_blob(self, regex_key): + """ + Should handle 'entire_record' and '*' + Not an iterator! + """ + raise NotImplemented + + + def _unpack_rule(self, json_id, field_name=None): + """From the field definitions extract the rules an tries to apply them""" + try: + rule_def = self.field_definitions[json_id] + except KeyError as e: + self.json['__meta_metadata__']['__continuable_errors__'].append("Error - Unable to find '%s' field definition" % (json_id, )) + return False + + if not field_name: + model = self.json['__meta_metadata__']['__additional_info__']['model'] + if model == '__default__' or model not in self.model_definitions: + field_name = json_id + else: + field_name = self.model_definitions[model].get(json_id, json_id) + + # Undo the workaround for [0] and [n] + if isinstance(rule_def, list): + return all(map(self._unpack_rule, rule_def)) + + # Already parsed, avoid doing it again + if (json_id, field_name) in self._parsed: + return field_name in self.json + + self._parsed.append((json_id, field_name)) + return self._apply_rules(json_id, field_name, rule_def) or \ + self._apply_virtual_rules(json_id, field_name, rule_def) + + def _apply_rules(self, json_id, field_name, rule_def): + """Tries to apply a 'creator' rule""" + applied = False + for rule in rule_def['rules'].get( + self.json['__meta_metadata__']['__additional_info__']['master_format'], []): + elements = self._get_elements_from_blob(rule['source_tag']) + if not elements: + self._set_default_value(json_id, field_name) + return False + if not self._evaluate_decorators(rule): + return False + if 'entire_record' in rule['source_tag'] or '*' in rule['source_tag']: + try: + value = try_to_eval(rule['value'], self.functions, value=elements, self=self.json) + info = self._find_meta_metadata(json_id, field_name, 'creator', rule, rule_def) + if 'json_ext' in rule_def: + value = rule_def['json_ext']['dumps'](value) + self.json.set(field_name, value, extend=True) + self.json['__meta_metadata__.%s' % (SmartDict.main_key_pattern.sub('', field_name), )] = info + applied = True + except Exception as e: + self.json['__meta_metadata__']['__errors__']\ + .append('Rule Error - Unable to apply rule for field %s - %s' % (field_name, str(e)),) + applied = False + + else: + for element in elements: + if not isinstance(element, (list, tuple)): + element = (element, ) + applied = False + for e in element: + if rule['only_if_master_value'] and \ + not all(try_to_eval(rule['only_if_master_value'], self.functions, value=e, self=self.json)): + applied = applied or False + else: + try: + value = try_to_eval(rule['value'], self.functions, value=e, self=self.json) + info = self._find_meta_metadata(json_id, field_name, 'creator', rule, rule_def) + if 'json_ext' in rule_def: + value = rule_def['json_ext']['dumps'](value) + self.json.set(field_name, value, extend=True) + self.json['__meta_metadata__.%s' % (SmartDict.main_key_pattern.sub('', field_name), )] = info + applied = applied or True + except Exception as e: + self.json['__meta_metadata__']['__errors__']\ + .append('Rule Error - Unable to apply rule for field %s - %s' % (field_name, str(e)),) + applied = applied or False + + if field_name not in self.json or not applied: + self._set_default_value(json_id, field_name) + return applied + + def _apply_virtual_rules(self, json_id, field_name, rule_def): + """Tries to apply either a 'derived' or 'calculated' rule""" + rules = [] + rules.append(('calculated', rule_def['rules'].get('calculated', []))) + rules.append(('derived', rule_def['rules'].get('derived', []))) + for (rule_type, rrules) in rules: + for rule in rrules: + if not self._evaluate_decorators(rule): + return False + try: + info = self._find_meta_metadata(json_id, field_name, rule_type, rule, rule_def) + if rule_type == 'derived' or rule['memoize']: + value = try_to_eval(rule['value'], self.functions, self=self.json) + if 'json_ext' in rule_def: + value = rule_def['json_ext']['dumps'](value) + else: + value = None + + self.json.set(field_name, value, extend=True) + self.json['__meta_metadata__.%s' % (SmartDict.main_key_pattern.sub('', field_name), )] = info + except Exception as e: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append('Virtual Rule CError - Unable to evaluate %s - %s' % (field_name, str(e))) + return False + + if field_name not in self.json: + self._set_default_value(json_id, field_name) + return True + + def _evaluate_decorators(self, rule): + """Evaluates all 'decorators' related with the current rule""" + if rule['parse_first']: + map(self._unpack_rule, try_to_eval(rule['parse_first'])) + if rule['depends_on']: + for key in try_to_eval(rule['depends_on']): + if key in self.json: + continue + main_key = SmartDict.main_key_pattern.sub('', key) + if not self._unpack_rule(main_key): + return False + if rule['only_if'] and not all(try_to_eval(rule['only_if'], self.functions, self=self.json)): + return False + return True + + def _find_meta_metadata(self, json_id, field_name, rule_type, rule, rule_def): + """Given one rule fills up the parallel dictionary with the needed meta-metadata""" + for alias in rule_def.get('aliases', []): + self.json['__meta_metadata__.__aliases__.%s' % (alias, )] = field_name + info = {} + info['timestamp'] = datetime.datetime.now().isoformat() + if rule_def.get('persistent_identifier', None) is not None: + info['pid'] = rule_def['persistent_identifier'] + info['memoize'] = rule.get('memoize', None) + info['type'] = rule_type + if rule_type in ('calculated', 'derived'): + info['function'] = (json_id, 'rules', rule_type, 0, 'value') + elif rule_type == 'UNKNOWN': + info['function'] = 'UNKNOWN' + info['source_tag'] = 'UNKNOWN' + else: + info['source_tag'] = rule['source_tag'] + + #Check the extensions + for parser_extension in parsers: + info.update(parser_extension.parser.add_info_to_field(json_id, rule_def)) + + return info + + def _set_default_value(self, json_id, field_name): + """ + Finds the default value inside the schema, if any + + :return: tuple containing if the value is required and the default value. + """ + schema = self.field_definitions[json_id].get('schema', {}).get(json_id) + if schema and 'default' in schema: + try: + value = schema['default']() + try: + value = self.field_definitions[json_id]['json_ext']['dumps'](value) + except KeyError: + pass + self.json.set(field_name, value, extend=True) + except Exception, e: + self.json['__meta_metadata__']['__continuable_errors__']\ + .append('Default Value CError - Unable to set default value for %s - %s' % (field_name, str(e))) + + + def _post_process_json(self): + """ + Responsible of doing any kind of transformation over the json structure + after it is created, e.g. pruning the json to delete None values or + singletons. + """ + def remove_none_values(obj): + if isinstance(obj, dict): + for key, value in obj.items(): + if value is None: + del obj[key] + else: + remove_none_values(value) + if isinstance(obj, list): + for element in obj: + if element is None: + obj.remove(element) + else: + remove_none_values(element) + + map(remove_none_values, [value for key, value in self.json.items() if not key == '__meta_metadata__']) + diff --git a/invenio/modules/jsonalchemy/registry.py b/invenio/modules/jsonalchemy/registry.py new file mode 100644 index 000000000..d1bf7612b --- /dev/null +++ b/invenio/modules/jsonalchemy/registry.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +from invenio.ext.registry import AutoDiscoverRegistry, AutoDiscoverSubRegistry, \ + PkgResourcesDiscoverRegistry, RegistryProxy +from invenio.utils.datastructures import LazyDict + +jsonext = lambda namespace: RegistryProxy(namespace, AutoDiscoverRegistry, namespace) + +fields_definitions = lambda namespace: RegistryProxy( + namespace + '.fields', PkgResourcesDiscoverRegistry, 'fields', + registry_namespace=jsonext(namespace)) + +models_definitions = lambda namespace: RegistryProxy( + namespace + '.models', PkgResourcesDiscoverRegistry, 'models', + registry_namespace=jsonext(namespace)) + +function_proxy = lambda namespace: RegistryProxy( + namespace + '.functions', AutoDiscoverSubRegistry, 'functions', + registry_namespace=jsonext(namespace)) +def functions(namespace): + funcs = dict((module.__name__.split('.')[-1], + getattr(module, module.__name__.split('.')[-1], '')) + for module in function_proxy('jsonext')) + funcs.update((module.__name__.split('.')[-1], + getattr(module, module.__name__.split('.')[-1], '')) + for module in function_proxy(namespace)) + return funcs + +parsers = RegistryProxy('jsonext.parsers', AutoDiscoverSubRegistry, + 'parsers', registry_namespace=jsonext('jsonext')) + +producers_proxy = RegistryProxy('jsonext.producers', AutoDiscoverSubRegistry, + 'producers', registry_namespace=jsonext('jsonext')) +producers = LazyDict(lambda: dict((module.__name__.split('.')[-1], module.produce) + for module in producers_proxy)) + +readers_proxy = RegistryProxy('jsonext.readers', AutoDiscoverSubRegistry, + 'readers', registry_namespace=jsonext('jsonext')) +readers = LazyDict(lambda: dict((module.reader.__master_format__, module.reader) + for module in readers_proxy)) diff --git a/invenio/utils/jsonalchemy/storage.py b/invenio/modules/jsonalchemy/storage.py similarity index 68% rename from invenio/utils/jsonalchemy/storage.py rename to invenio/modules/jsonalchemy/storage.py index a1e09b6b6..6b8081698 100644 --- a/invenio/utils/jsonalchemy/storage.py +++ b/invenio/modules/jsonalchemy/storage.py @@ -1,90 +1,98 @@ # -*- coding: utf-8 -*- - +## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ - invenio.utils.jsonalchemy.storage - --------------------------- + invenio.modules.jsonalchemy.storage + ----------------------------------- - Record storage interface. + Json storage engine interface """ class Storage(object): - """ - Defatul storage interface - """ + """Default storage engine interface""" + #TODO: set return values on success and error and setup a log function + #TODO: create a query class to mimic SQLAlchemy query object def __init__(self, model, **kargs): - """docstring for __init__""" - raise NotImplementedError() + """ + :param: model: + """ + raise NotImplemented def save_one(self, json, id=None): """Stores one json in the storage system""" - raise NotImplementedError() def save_many(self, jsons, ids=None): """ Stores many json in the storage system, as elements on the iterable jsons. """ - raise NotImplementedError() + raise NotImplemented def update_one(self, json, id=None): """ - Updates one json , if id is None a field representing the id is + Updates one json , if id is None a field representing the id is expected inside the json object. """ - raise NotImplementedError() + raise NotImplemented def update_many(self, jsons, ids=None): """Update many json objects following the same rule as update_one""" - raise NotImplementedError() + raise NotImplemented def get_one(self, id): """Returns the json matching the id""" - raise NotImplementedError() + raise NotImplemented def get_many(self, ids): """Returns an iterable of json objects which id is inside ids""" - raise NotImplementedError() + raise NotImplemented def get_field_values(ids, field, repetitive_values=True, count=False, - include_recid=False, split_by=0): + include_recid=False, split_by=0): """ Returns a list of field values for field for the given ids. :param ids: list (or iterable) of integers :param repetitive_values: if set to True, returns all values even if they are doubled. If set to False, then return unique values only. :param count: in combination with repetitive_values=False, adds to the result the number of occurrences of the field. :param split: specifies the size of the output. """ - raise NotImplementedError() + raise NotImplemented def get_fields_values(ids, fields, repetitive_values=True, count=False, - include_recid=False, split_by=0): + include_recid=False, split_by=0): """ - As in :meth:`get_field_values` but in this case returns a dictionary with each - of the fields and the list of field values. + As in :meth:`get_field_values` but in this case returns a dictionary + with each of the fields and the list of field values. """ - raise NotImplementedError() - + raise NotImplemented + def search(query): + """ + Retrieves all entries which match the query JSON prototype document. + This method should not be used on storage engines without native JSON + support (e.g., MySQL). Returns a cursor over the matched documents. + :param query: dictionary specifying the search prototype document + """ + raise NotImplemented diff --git a/invenio/core/__init__.py b/invenio/modules/jsonalchemy/testsuite/__init__.py similarity index 83% copy from invenio/core/__init__.py copy to invenio/modules/jsonalchemy/testsuite/__init__.py index eb537e874..0fef19ac6 100644 --- a/invenio/core/__init__.py +++ b/invenio/modules/jsonalchemy/testsuite/__init__.py @@ -1,18 +1,23 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. -## Copyright (C) 2013 CERN. +## Copyright (C) 2014 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. + +from .test_parser import * +from .test_reader import * +from .test_storage_engine import * +from .test_wrapper import * diff --git a/invenio/modules/jsonalchemy/testsuite/fields/authors.cfg b/invenio/modules/jsonalchemy/testsuite/fields/authors.cfg new file mode 100644 index 000000000..c56766f06 --- /dev/null +++ b/invenio/modules/jsonalchemy/testsuite/fields/authors.cfg @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2014 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. + +#Test for loops +include "authors.cfg" + +authors[0], creator: + creator: + @legacy((("100", "100__", "100__%"), ""), + ("100__a", "first author name", "full_name"), + ("100__e", "relator_name"), + ("100__h", "CCID"), + ("100__i", "INSPIRE_number"), + ("100__u", "first author affiliation", "affiliation")) + marc, "100__", { 'full_name':value['a'], 'first_name':util_split(value['a'],',',1), 'last_name':util_split(value['a'],',',0), 'relator_name':value['e'], 'CCID':value['h'], 'INSPIRE_number':value['i'], 'affiliation':value['u'] } + checker: + check_field_existence(0,1) + check_field_type('str') + producer: + json_for_marc(), {"100__a": "full_name", "100__e": "relator_name", "100__h": "CCID", "100__i": "INSPIRE_number", "100__u": "affiliation"} + json_for_dc(), {"dc:creator": "full_name"} + description: + """Main Author""" + +authors[n], contributor: + creator: + @legacy((("700", "700__", "700__%"), ""), + ("700__a", "additional author name", "full_name"), + ("700__u", "additional author affiliation", "affiliation")) + marc, "700__", {'full_name': value['a'], 'first_name':util_split(value['a'],',',1), 'last_name':util_split(value['a'],',',0), 'relator_name':value['e'], 'CCID':value['h'], 'INSPIRE_number':value['i'], 'affiliation':value['u'] } + checker: + check_field_existence(0,'n') + check_field_type('str') + producer: + json_for_marc(), {"700__a": "full_name", "700__e": "relator_name", "700__h": "CCID", "700__i": "INSPIRE_number", "700__u": "affiliation"} + json_for_dc(), {"dc:contributor": "full_name"} + description: + """Authors""" diff --git a/invenio/modules/jsonalchemy/testsuite/fields/fields.cfg b/invenio/modules/jsonalchemy/testsuite/fields/fields.cfg new file mode 100644 index 000000000..0401f5cdf --- /dev/null +++ b/invenio/modules/jsonalchemy/testsuite/fields/fields.cfg @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2014 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. + +abstract: + creator: + @legacy((("520", "520__", "520__%"), "abstract", ""), + ("520__a", "abstract", "summary"), + ("520__b", "expansion"), + ("520__9", "number")) + marc, "520__", {'summary':value['a'], 'expansion':value['b'], 'number':value['9']} + producer: + json_for_marc(), {"520__a": "summary", "520__b": "expansion", "520__9": "number"} + json_for_dc(), {"dc:description":"summary"} + +collection: + creator: + @legacy((("980", "980__", "980__%"), ""), + ("980__%", "collection identifier", ""), + ("980__a", "primary"), + ("980__b", "secondary"), + ("980__c", "deleted")) + marc, "980__", { 'primary':value['a'], 'secondary':value['b'], 'deleted':value['c'] } + producer: + json_for_marc(), {"980__a":"primary", "980__b":"secondary", "980__c":"deleted"} + +@persistent_identifier(3) +doi: + creator: + @legacy((("024", "0247_", "0247_%"), ""), + ("0247_a", "")) + marc, "0247_", get_doi(value) + checker: + check_field_existence(0,1) + producer: + json_for_marc(), {'0247_2': 'str("DOI")', '0247_a': ''} + +fft[n]: + creator: + @legacy(("FFT__a", "path"), + ("FFT__d", "description"), + ("FFT__f", "eformat"), + ("FFT__i", "temporary_id"), + ("FFT__m", "new_name"), + ("FFT__o", "flag"), + ("FFT__r", "restriction"), + ("FFT__s", "timestamp"), + ("FFT__t", "docfile_type"), + ("FFT__v", "version"), + ("FFT__x", "icon_path"), + ("FFT__z", "comment"), + ("FFT__w", "document_moreinfo"), + ("FFT__p", "version_moreinfo"), + ("FFT__b", "version_format_moreinfo"), + ("FFT__f", "format_moreinfo")) + marc, "FFT__", {'path': value['a'], + 'description': value['d'], + 'eformat': value['f'], + 'temporary_id': value['i'], + 'new_name': value['m'], + 'flag': value['o'], + 'restriction': value['r'], + 'timestamp': value['s'], + 'docfile_type': value['t'], + 'version': value['v'], + 'icon_path': value['x'], + 'comment': value['z'], + 'document_moreinfo': value['w'], + 'version_moreinfo': value['p'], + 'version_format_moreinfo': value['b'], + 'format_moreinfo': value['u'] + } + @only_if_value((is_local_url(value['u']), )) + marc, "8564_", {'hots_name': value['a'], + 'access_number': value['b'], + 'compression_information': value['c'], + 'path':value['d'], + 'electronic_name': value['f'], + 'request_processor': value['h'], + 'institution': value['i'], + 'formart': value['q'], + 'settings': value['r'], + 'file_size': value['s'], + 'url': value['u'], + 'subformat':value['x'], + 'description':value['y'], + 'comment':value['z']} + producer: + json_for_marc(), {"FFT__a": "path", "FFT__d": "description", "FFT__f": "eformat", "FFT__i": "temporary_id", "FFT__m": "new_name", "FFT__o": "flag", "FFT__r": "restriction", "FFT__s": "timestamp", "FFT__t": "docfile_type", "FFT__v": "version", "FFT__x": "icon_path", "FFT__z": "comment", "FFT__w": "document_moreinfo", "FFT__p": "version_moreinfo", "FFT__b": "version_format_moreinfo", "FFT__f": "format_moreinfo"} + +isbn: + creator: + @legacy((("020", "020__", "020__%"), ""), + ("020__a", "isbn", "isbn"), + ("020__u", "medium")) + marc, "020__", {'isbn':value['a'], 'medium':value['u']} + checker: + check_field_type('isbn', 'isbn') + producer: + json_for_marc(), {"020__a": "isbn", "020__u": "medium"} + +keywords[n]: + creator: + @legacy((("653", "6531_", "6531_%"), ""), + ("6531_a", "keyword", "term"), + ("6531_9", "institute")) + marc, "6531_", { 'term': value['a'], 'institute': value['9'] } + checker: + check_field_existence(0,'n') + check_field_type('str') + producer: + json_for_marc(), {"6531_a": "term", "6531_9": "institute"} + +language: + creator: + @legacy((("041", "041__", "041__%"), ""), + ("041__a", "")) + marc, "041__", value['a'] + producer: + json_for_marc(), {"041__a": ""} + json_for_dc(), {"dc:language": ""} + +modification_date, version_id: + schema: + {'modification_date': {'type': 'datetime', 'required': True, 'default': lambda: __import__('datetime').datetime.now()}} + creator: + @legacy(('005', ''),) + marc, '005', datetime.datetime(*(time.strptime(value, "%Y%m%d%H%M%S.0")[0:6])) + json: + dumps, lambda d: d.isoformat() + loads, lambda d: __import__('datetime').datetime.strptime(d, "%Y-%m-%dT%H:%M:%S") + +primary_report_number: + creator: + @legacy((("037", "037__", "037__%"), ""), + ("037__a", "primary report number", ""), ) + marc, "037__", value['a'] + producer: + json_for_marc(), {"037__a": ""} + +reference: + creator: + @legacy((("999", "999C5", "999C5%"), ""), + ("999C5", "reference", ""), + ("999C5a", "doi"), + ("999C5h", "authors"), + ("999C5m", "misc"), + ("999C5n", "issue_number"), + ("999C5o", "order_number"), + ("999C5p", "page"), + ("999C5r", "report_number"), + ("999C5s", "title"), + ("999C5u", "url"), + ("999C5v", "volume"), + ("999C5y", "year"),) + marc, "999C5", {'doi':value['a'], 'authors':value['h'], 'misc':value['m'], 'issue_number':value['n'], 'order_number':value['o'], 'page':value['p'], 'report_number':value['r'], 'title':value['s'], 'url':value['u'], 'volume':value['v'], 'year':value['y'],} + producer: + json_for_marc(), {"999C5a": "doi", "999C5h": "authors", "999C5m": "misc", "999C5n": "issue_number", "999C5o":"order_number", "999C5p":"page", "999C5r":"report_number", "999C5s":"title", "999C5u":"url", "999C5v":"volume", "999C5y": "year"} + +@persistent_identifier(2) +system_control_number: + creator: + @legacy((("035", "035__", "035__%"), ""), + ("035__a", "system_control_number"), + ("035__9", "institute")) + marc, "035__", {'value': value['a'], 'canceled':value['z'], 'linkpage':value['6'], 'institute':value['9']} + producer: + json_for_marc(), {"035__a": "system_control_number", "035__9": "institute"} + +@persistent_identifier(1) +system_number: + creator: + @legacy((("970", "970__", "970__%"), ""), + ("970__a", "sysno"), + ("970__d", "recid")) + marc, "970__", {'value':value['a'], 'recid':value['d']} + checker: + check_field_existence(0,1) + producer: + json_for_marc(), {"970__a": "sysno", "970__d": "recid"} + +#To be overwritten by test_tiltle.cfg +title: + """Some useless documentation""" + creator: + marc, "245__", value['foo'] + +url: + creator: + @legacy((("856", "8564_", "8564_%"), ""), + ("8564_a", "host_name"), + ("8564_b", "access_number"), + ("8564_c", "compression_information"), + ("8564_d", "path"), + ("8564_f", "electronic_name"), + ("8564_h", "request_processor"), + ("8564_i", "institution"), + ("8564_q", "eformat"), + ("8564_r", "settings"), + ("8564_s", "file_size"), + ("8564_u", "url", "url"), + ("8564_x", "subformat"), + ("8564_y", "caption", "description"), + ("8564_z", "comment")) + @only_if_value((not is_local_url(value['u']), )) + marc, "8564_", {'host_name': value['a'], + 'access_number': value['b'], + 'compression_information': value['c'], + 'path':value['d'], + 'electronic_name': value['f'], + 'request_processor': value['h'], + 'institution': value['i'], + 'eformart': value['q'], + 'settings': value['r'], + 'size': value['s'], + 'url': value['u'], + 'subformat':value['x'], + 'description':value['y'], + 'comment':value['z']} + producer: + json_for_marc(), {"8564_a": "host_name", "8564_b": "access_number", "8564_c": "compression_information", "8564_d": "path", "8564_f": "electronic_name", "8564_h": "request_processor", "8564_i": "institution", "8564_q": "eformat", "8564_r": "settings", "8564_s": "file_size", "8564_u": "url", "8564_x": "subformat", "8564_y": "description", "8564_z": "comment"} + json_for_dc(), {"dc:identifier": "url"} + +_persistent_identifiers_keys: + calculated: + @parse_first(('system_control_number', 'recid', 'doi', 'oai', 'system_number', '_id')) + @memoize() + get_persistent_identifiers_keys(self.keys()) + description: + """ + This field will tell you which fields among all are considered as + persistent identifiers (decorated with @persistent_identifier) + If a new persistent identifier field is added the cached version of this + field must be rebuild. + + Note: If a new persistent idenfier is added the list of fields to parse + before this one should be updated + """ + +_random: + """Checks on the fly fields""" + derived: + random.randint(0,100) + +@persistent_identifier(2) +dummy: + """Dummy number of authors""" + +@extend +dummy: + derived: + @depends_on(('number_of_authors',)) + self.get('nunmber_of_authors', 0) + checker: + check_field_existence(0, 1) + check_field_type('num') diff --git a/invenio/legacy/bibfield/functions/get_bibdoc.py b/invenio/modules/jsonalchemy/testsuite/fields/title.cfg similarity index 56% rename from invenio/legacy/bibfield/functions/get_bibdoc.py rename to invenio/modules/jsonalchemy/testsuite/fields/title.cfg index ed6efae7e..030ad4dc9 100644 --- a/invenio/legacy/bibfield/functions/get_bibdoc.py +++ b/invenio/modules/jsonalchemy/testsuite/fields/title.cfg @@ -1,35 +1,37 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. -## Copyright (C) 2013 CERN. +## Copyright (C) 2014 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. -def get_bibdoc(recid): - """ - Retrieves using BibDoc all the files related with a given record +@override +title: + """Overrided Title""" + creator: + @legacy((("245", "245__","245__%"), ""), + ("245__a", "title", "title"), + ("245__b", "subtitle"), + ("245__k", "form")) + marc, "245__", { 'title':value['a'], 'subtitle':value['b'], 'form':value['k'] } + checker: + check_field_existence(0,1) + check_field_type('str') - @param recid - - @return BibDoc of the given record - """ - if not recid or recid < 0: - return None - - from invenio.legacy.bibdocfile.api import BibDoc, InvenioBibDocFileError - try: - return BibDoc(int(recid)) - except InvenioBibDocFileError: - return None +title_parallel: + creator: + @legacy(("246_1a", "title"), + ("246_1i", "text")) + marc, "246_1", { 'title':value['a'], 'text':value['i']} diff --git a/invenio/core/__init__.py b/invenio/modules/jsonalchemy/testsuite/models/base.cfg similarity index 93% copy from invenio/core/__init__.py copy to invenio/modules/jsonalchemy/testsuite/models/base.cfg index eb537e874..63ebe3d9c 100644 --- a/invenio/core/__init__.py +++ b/invenio/modules/jsonalchemy/testsuite/models/base.cfg @@ -1,18 +1,25 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. + +""" +Common fields +""" + +fields: + modification_date diff --git a/invenio/core/__init__.py b/invenio/modules/jsonalchemy/testsuite/models/test_model.cfg similarity index 86% rename from invenio/core/__init__.py rename to invenio/modules/jsonalchemy/testsuite/models/test_model.cfg index eb537e874..35952c547 100644 --- a/invenio/core/__init__.py +++ b/invenio/modules/jsonalchemy/testsuite/models/test_model.cfg @@ -1,18 +1,29 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. + +""" +Test model +""" + +fields: + @inherit_from(('base', )) + abstract + authors + keywords + title_article=title diff --git a/invenio/modules/jsonalchemy/testsuite/test_parser.py b/invenio/modules/jsonalchemy/testsuite/test_parser.py new file mode 100644 index 000000000..942661074 --- /dev/null +++ b/invenio/modules/jsonalchemy/testsuite/test_parser.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2014 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. + +"""Unit tests for the parser engine.""" + +__revision__ = \ + "$Id$" + +import os +import tempfile + +from invenio.base.wrappers import lazy_import +from invenio.ext.registry import RegistryProxy, ImportPathRegistry, \ + PkgResourcesDiscoverRegistry +from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase + +Field_parser = lazy_import('invenio.modules.jsonalchemy.parser:FieldParser') +Model_parser = lazy_import('invenio.modules.jsonalchemy.parser:ModelParser') +guess_legacy_field_names = lazy_import('invenio.modules.jsonalchemy.parser:guess_legacy_field_names') +get_producer_rules = lazy_import('invenio.modules.jsonalchemy.parser:get_producer_rules') + +TEST_PACKAGE = 'invenio.modules.jsonalchemy.testsuite' + +test_registry = RegistryProxy('testsuite', ImportPathRegistry, + initial=[TEST_PACKAGE]) + +field_definitions = lambda: PkgResourcesDiscoverRegistry( + 'fields', registry_namespace=test_registry) +model_definitions = lambda: PkgResourcesDiscoverRegistry( + 'models', registry_namespace=test_registry) + + +class TestParser(InvenioTestCase): + + def setUp(self): + self.tmp_file_1 = tempfile.NamedTemporaryFile() + config_1 = ''' +@inherit_from(("authors[0]",)) +main_author: + """Just main author""" + ''' + self.tmp_file_1.write(config_1) + self.tmp_file_1.flush() + self.tmp_file_2 = tempfile.NamedTemporaryFile() + config_2 = ''' +include "%s" + +authors[0], creator: + creator: + @legacy((("100", "100__", "100__%%"), ""), + ("100__a", "first author name", "full_name"), + ("100__e", "relator_name"), + ("100__h", "CCID"), + ("100__i", "INSPIRE_number"), + ("100__u", "first author affiliation", "affiliation")) + marc, "100__", { 'full_name':value['a'], 'first_name':util_split(value['a'],',',1), 'last_name':util_split(value['a'],',',0), 'relator_name':value['e'], 'CCID':value['h'], 'INSPIRE_number':value['i'], 'affiliation':value['u'] } + checker: + check_field_existence(0,1) + check_field_type('str') + producer: + json_for_marc(), {"100__a": "full_name", "100__e": "relator_name", "100__h": "CCID", "100__i": "INSPIRE_number", "100__u": "affiliation"} + json_for_dc(), {"dc:creator": "full_name"} + description: + """Main Author""" + +authors[n], contributor: + creator: + @legacy((("700", "700__", "700__%%"), ""), + ("700__a", "additional author name", "full_name"), + ("700__u", "additional author affiliation", "affiliation")) + marc, "700__", {'full_name': value['a'], 'first_name':util_split(value['a'],',',1), 'last_name':util_split(value['a'],',',0), 'relator_name':value['e'], 'CCID':value['h'], 'INSPIRE_number':value['i'], 'affiliation':value['u'] } + checker: + check_field_existence(0,'n') + check_field_type('str') + producer: + json_for_marc(), {"700__a": "full_name", "700__e": "relator_name", "700__h": "CCID", "700__i": "INSPIRE_number", "700__u": "affiliation"} + json_for_dc(), {"dc:contributor": "full_name"} + description: + """Authors""" + + ''' % (self.tmp_file_1.name, ) + self.tmp_file_2.write(config_2) + self.tmp_file_2.flush() + + self.app.extensions['registry']['testsuite.fields'] = field_definitions() + for path in self.app.extensions['registry']['testsuite.fields'].registry: + if os.path.basename(path) == 'authors.cfg': + self.app.extensions['registry']['testsuite.fields'].registry.remove(path) + self.app.extensions['registry']['testsuite.fields'].register(self.tmp_file_2.name) + self.app.extensions['registry']['testsuite.models'] = model_definitions() + + def tearDown(self): + del self.app.extensions['registry']['testsuite.fields'] + del self.app.extensions['registry']['testsuite.models'] + self.tmp_file_1.close() + self.tmp_file_2.close() + + def test_field_rules(self): + """JsonAlchemy - field parser""" + self.assertTrue(len(Field_parser.field_definitions('testsuite')) >= 22) + #Check that all files are parsed + self.assertTrue('authors' in Field_parser.field_definitions('testsuite')) + self.assertTrue('title' in Field_parser.field_definitions('testsuite')) + #Check work arroung for [n] and [0] + self.assertTrue(len(Field_parser.field_definitions('testsuite')['authors']) == 2) + self.assertEqual(Field_parser.field_definitions('testsuite')['authors'], ['authors[0]', 'authors[n]']) + self.assertTrue('authors[0]' in Field_parser.field_definitions('testsuite')) + self.assertTrue('authors[n]' in Field_parser.field_definitions('testsuite')) + self.assertTrue(Field_parser.field_definitions('testsuite')['doi']['persistent_identifier']) + #Check if derived and calulated are well parserd + self.assertTrue('dummy' in Field_parser.field_definitions('testsuite')) + self.assertEquals(Field_parser.field_definitions('testsuite')['dummy']['persistent_identifier'], 2) + self.assertEquals(Field_parser.field_definitions('testsuite')['dummy']['rules'].keys(), ['derived']) + self.assertTrue(Field_parser.field_definitions('testsuite')['_random']) + #Check inheritance + self.assertTrue('main_author' in Field_parser.field_definitions('testsuite')) + self.assertEqual(Field_parser.field_definitions('testsuite')['main_author']['rules'], + Field_parser.field_definitions('testsuite')['authors[0]']['rules']) + #Check override + value = {'a':'a', 'b':'b', 'k':'k'} + self.assertEquals(eval(Field_parser.field_definitions('testsuite')['title']['rules']['marc'][0]['value']), + {'form': 'k', 'subtitle': 'b', 'title': 'a'}) + #Check extras + self.assertTrue('json_ext' in Field_parser.field_definitions('testsuite')['modification_date']) + + tmp = Field_parser.field_definitions('testsuite') + Field_parser.reparse('testsuite') + self.assertEquals(len(Field_parser.field_definitions('testsuite')), len(tmp)) + + def test_model_definitions(self): + """JsonAlchemy - model parser""" + self.assertTrue(len(Model_parser.model_definitions('testsuite')) >= 2) + self.assertTrue('base' in Model_parser.model_definitions('testsuite')) + tmp = Model_parser.model_definitions('testsuite') + Model_parser.reparse('testsuite') + self.assertEquals(len(Model_parser.model_definitions('testsuite')), len(tmp)) + + def test_guess_legacy_field_names(self): + """JsonAlchemy - check legacy field names""" + self.assertEquals(guess_legacy_field_names(('100__a', '245'), 'marc', 'testsuite'), + {'100__a':['authors[0].full_name'], '245':['title']}) + self.assertEquals(guess_legacy_field_names('foo', 'bar', 'baz'), {'foo': []}) + + def test_get_producer_rules(self): + """JsonAlchemy - check producer rules""" + self.assertTrue(get_producer_rules('authors[0]', 'json_for_marc', 'testsuite')[0] in get_producer_rules('authors', 'json_for_marc', 'testsuite')) + self.assertTrue(len(get_producer_rules('keywords', 'json_for_marc', 'testsuite')) == 1) + self.assertRaises(KeyError, lambda: get_producer_rules('foo', 'json_for_marc', 'testsuite')) + + +TEST_SUITE = make_test_suite(TestParser) + +if __name__ == '__main__': + run_test_suite(TEST_SUITE) diff --git a/invenio/modules/jsonalchemy/testsuite/test_reader.py b/invenio/modules/jsonalchemy/testsuite/test_reader.py new file mode 100644 index 000000000..07b0c0b9c --- /dev/null +++ b/invenio/modules/jsonalchemy/testsuite/test_reader.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2014 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. + +"""Unit tests for the parser engine.""" + +__revision__ = \ + "$Id$" + +from invenio.base.wrappers import lazy_import +from invenio.ext.registry import RegistryProxy, ImportPathRegistry, \ + PkgResourcesDiscoverRegistry +from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase + +Field_parser = lazy_import('invenio.modules.jsonalchemy.parser:FieldParser') +Model_parser = lazy_import('invenio.modules.jsonalchemy.parser:ModelParser') +guess_legacy_field_names = lazy_import('invenio.modules.jsonalchemy.parser:guess_legacy_field_names') +get_producer_rules = lazy_import('invenio.modules.jsonalchemy.parser:get_producer_rules') +readers = lazy_import('invenio.modules.jsonalchemy.registry:readers') + +TEST_PACKAGE = 'invenio.modules.jsonalchemy.testsuite' + +test_registry = RegistryProxy('testsuite', ImportPathRegistry, + initial=[TEST_PACKAGE]) + +field_definitions = lambda: PkgResourcesDiscoverRegistry( + 'fields', registry_namespace=test_registry) +model_definitions = lambda: PkgResourcesDiscoverRegistry( + 'models', registry_namespace=test_registry) + + +class TestReaders(InvenioTestCase): + + def setUp(self): + self.app.extensions['registry']['testsuite.fields'] = field_definitions() + self.app.extensions['registry']['testsuite.models'] = model_definitions() + + def tearDown(self): + del self.app.extensions['registry']['testsuite.fields'] + del self.app.extensions['registry']['testsuite.models'] + + def test_marc_reader_translate(self): + """JsonAlchemy - Marc reader""" + xml = """ + + + 8 + SzGeCERN + + astro-ph/9812226 + + + eng + + + Efstathiou, G P + Cambridge University + + + Constraints on $\Omega_{\Lambda}$ and $\Omega_{m}$from Distant Type 1a Supernovae and Cosmic Microwave Background Anisotropies + + + 14 Dec 1998 + + + 6 p + + + We perform a combined likelihood analysis of the latest cosmic microwave background anisotropy data and distant Type 1a Supernova data of Perlmutter etal (1998a). Our analysis is restricted tocosmological models where structure forms from adiabatic initial fluctuations characterised by a power-law spectrum with negligible tensor component. Marginalizing over other parameters, our bestfit solution gives Omega_m = 0.25 (+0.18, -0.12) and Omega_Lambda = 0.63 (+0.17, -0.23) (95 % confidence errors) for the cosmic densities contributed by matter and a cosmological constantrespectively. The results therefore strongly favour a nearly spatially flat Universe with a non-zero cosmological constant. + + + LANL EDS + + + SzGeCERN + Astrophysics and Astronomy + + + Lasenby, A N + + + Hobson, M P + + + Ellis, R S + + + Bridle, S L + + + George Efstathiou <gpe@ast.cam.ac.uk> + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.pdf + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.fig1.ps.gz + Additional + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.fig3.ps.gz + Additional + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.fig5.ps.gz + Additional + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.fig6.ps.gz + Additional + + + http://invenio-software.org/download/invenio-demo-site-files/9812226.fig7.ps.gz + Additional + + + 1998 + + + 11 + + + 1998-12-14 + 50 + 2001-04-07 + BATCH + + + Mon. Not. R. Astron. Soc. + + + SLAC + 4162242 + + + CER + + + n + 200231 + + + PREPRINT + + + Bond, J.R. 1996, Theory and Observations of the Cosmic Background Radiation, in "Cosmology and Large Scale Structure", Les Houches Session LX, August 1993, eds. R. Schaeffer, J. Silk, M. Spiro and J. Zinn-Justin, Elsevier SciencePress, Amsterdam, p469 + + + Bond J.R., Efstathiou G., Tegmark M., 1997 + L33 + Mon. Not. R. Astron. Soc. + 291 + 1997 + Mon. Not. R. Astron. Soc. 291 (1997) L33 + + + Bond, J.R., Jaffe, A. 1997, in Proc. XXXI Rencontre de Moriond, ed. F. Bouchet, Edition Fronti eres, in press + astro-ph/9610091 + + + Bond J.R., Jaffe A.H. and Knox L.E., 1998 + astro-ph/9808264 + Astrophys.J. 533 (2000) 19 + + + Burles S., Tytler D., 1998a, to appear in the Proceedings of the Second Oak Ridge Symposium on Atomic & Nuclear Astrophysics, ed. A. Mezzacappa, Institute of Physics, Bristol + astro-ph/9803071 + + + Burles S., Tytler D., 1998b, Astrophys. J.in press + astro-ph/9712109 + Astrophys.J. 507 (1998) 732 + + + Caldwell, R.R., Dave, R., Steinhardt P.J., 1998 + 1582 + Phys. Rev. Lett. + 80 + 1998 + Phys. Rev. Lett. 80 (1998) 1582 + + + Carroll S.M., Press W.H., Turner E.L., 1992, Ann. Rev. Astr. Astrophys., 30, 499. Chaboyer B., 1998 + astro-ph/9808200 + Phys.Rept. 307 (1998) 23 + + + Devlin M.J., De Oliveira-Costa A., Herbig T., Miller A.D., Netterfield C.B., Page L., Tegmark M., 1998, submitted to Astrophys. J + astro-ph/9808043 + Astrophys. J. 509 (1998) L69-72 + + + Efstathiou G. 1996, Observations of Large-Scale Structure in the Universe, in "Cosmology and Large Scale Structure", Les Houches Session LX, August 1993, eds. R. Schaeffer, J. Silk, M. Spiro and J. Zinn-Justin, Elsevier SciencePress, Amsterdam, p135. Efstathiou G., Bond J.R., Mon. Not. R. Astron. Soc.in press + astro-ph/9807130 + Astrophys. J. 518 (1999) 2-23 + + + Evrard G., 1998, submitted to Mon. Not. R. Astron. Soc + astro-ph/9701148 + Mon.Not.Roy.Astron.Soc. 292 (1997) 289 + + + Freedman J.B., Mould J.R., Kennicutt R.C., Madore B.F., 1998 + astro-ph/9801090 + Astrophys. J. 480 (1997) 705 + + + Garnavich P.M. et al. 1998 + astro-ph/9806396 + Astrophys.J. 509 (1998) 74-79 + + + Goobar A., Perlmutter S., 1995 + 14 + Astrophys. J. + 450 + 1995 + Astrophys. J. 450 (1995) 14 + + + Hamuy M., Phillips M.M., Maza J., Suntzeff N.B., Schommer R.A., Aviles R. 1996 + 2391 + Astrophys. J. + 112 + 1996 + Astrophys. J. 112 (1996) 2391 + + + Hancock S., Gutierrez C.M., Davies R.D., Lasenby A.N., Rocha G., Rebolo R., Watson R.A., Tegmark M., 1997 + 505 + Mon. Not. R. Astron. Soc. + 298 + 1997 + Mon. Not. R. Astron. Soc. 298 (1997) 505 + + + Hancock S., Rocha G., Lasenby A.N., Gutierrez C.M., 1998 + L1 + Mon. Not. R. Astron. Soc. + 294 + 1998 + Mon. Not. R. Astron. Soc. 294 (1998) L1 + + + Herbig T., De Oliveira-Costa A., Devlin M.J., Miller A.D., Page L., Tegmark M., 1998, submitted to Astrophys. J + astro-ph/9808044 + Astrophys.J. 509 (1998) L73-76 + + + Lineweaver C.H., 1998. Astrophys. J.505, L69. Lineweaver, C.H., Barbosa D., 1998a + 624 + Astrophys. J. + 446 + 1998 + Astrophys. J. 446 (1998) 624 + + + Lineweaver, C.H., Barbosa D., 1998b + 799 + Astron. Astrophys. + 329 + 1998 + Astron. Astrophys. 329 (1998) 799 + + + De Oliveira-Costa A., Devlin M.J., Herbig T., Miller A.D., Netterfield C.B. Page L., Tegmark M., 1998, submitted to Astrophys. J + astro-ph/9808045 + Astrophys. J. 509 (1998) L77-80 + + + Ostriker J.P., Steinhardt P.J., 1995 + 600 + Nature + 377 + 1995 + Nature 377 (1995) 600 + + + Peebles P.J.E., 1993, Principles of Physical Cosmology, Princeton University Press, Princeton, New Jersey. Perlmutter S, et al., 1995, In Presentations at the NATO ASI in Aiguablava, Spain, LBL-38400; also published in Thermonuclear Supernova, P. Ruiz-Lapuente, R. Cana and J. Isern (eds), Dordrecht, Kluwer, 1997, p749. Perlmutter S, et al., 1997 + 565 + Astrophys. J. + 483 + 1997 + Astrophys. J. 483 (1997) 565 + + + Perlmutter S. et al., 1998a, Astrophys. J.in press. (P98) + astro-ph/9812133 + Astrophys. J. 517 (1999) 565-586 + + + Perlmutter S. et al., 1998b, In Presentation at the January 1988 Meeting of the American Astronomical Society, Washington D.C., LBL-42230, available at www-supernova.lbl.gov; B.A.A.S., volume : 29 (1997) 1351Perlmutter S, et al., 1998c + 51 + Nature + 391 + 1998 + Nature 391 (1998) 51 + + + Ratra B., Peebles P.J.E., 1988 + 3406 + Phys. Rev., D + 37 + 1988 + Phys. Rev. D 37 (1988) 3406 + + + Riess A. et al. 1998, Astrophys. J.in press + astro-ph/9805201 + Astron. J. 116 (1998) 1009-1038 + + + Seljak U., Zaldarriaga M. 1996 + 437 + Astrophys. J. + 469 + 1996 + Astrophys. J. 469 (1996) 437 + + + Seljak U. & Zaldarriaga M., 1998 + astro-ph/9811123 + Phys. Rev. D60 (1999) 043504 + + + Tegmark M., 1997 + 3806 + Phys. Rev. Lett. + 79 + 1997 + Phys. Rev. Lett. 79 (1997) 3806 + + + Tegmark M. 1998, submitted to Astrophys. J + astro-ph/9809201 + Astrophys. J. 514 (1999) L69-L72 + + + Tegmark, M., Eisenstein D.J., Hu W., Kron R.G., 1998 + astro-ph/9805117 + + + Wambsganss J., Cen R., Ostriker J.P., 1998 + 29 + Astrophys. J. + 494 + 1998 + Astrophys. J. 494 (1998) 29 + + + Webster M., Bridle S.L., Hobson M.P., Lasenby A.N., Lahav O., Rocha, G., 1998, Astrophys. J.in press + astro-ph/9802109 + + + White M., 1998, Astrophys. J.in press + astro-ph/9802295 + Astrophys. J. 506 (1998) 495 + + + Zaldarriaga, M., Spergel D.N., Seljak U., 1997 + 1 + Astrophys. J. + 488 + 1997 + Astrophys. J. 488 (1997) 1 + + + + 33 + + eng + + + + """ + blob = list(readers['marc'].split_blob(xml, schema='foo')) + self.assertTrue(len(blob) == 0) + blob = list(readers['marc'].split_blob(xml))[0] + reader = readers['marc'](blob=blob, namespace='testsuite') + json = reader.translate() + self.assertIsNotNone(json) + self.assertTrue('__meta_metadata__' in json) + self.assertTrue(json['__meta_metadata__']['__additional_info__']['master_format'] == 'marc') + self.assertTrue('authors' in json) + self.assertTrue(json['authors'][0]['full_name'] == "Efstathiou, G P") + self.assertTrue(len(json['authors']) == 5) + self.assertTrue('title' in json) + self.assertTrue(json['title']['title'] == "Constraints on $\Omega_{\Lambda}$ and $\Omega_{m}$from Distant Type 1a Supernovae and Cosmic Microwave Background Anisotropies") + self.assertTrue('abstract' in json) + self.assertTrue(json['abstract']['summary'] == "We perform a combined likelihood analysis of the latest cosmic microwave background anisotropy data and distant Type 1a Supernova data of Perlmutter etal (1998a). Our analysis is restricted tocosmological models where structure forms from adiabatic initial fluctuations characterised by a power-law spectrum with negligible tensor component. Marginalizing over other parameters, our bestfit solution gives Omega_m = 0.25 (+0.18, -0.12) and Omega_Lambda = 0.63 (+0.17, -0.23) (95 % confidence errors) for the cosmic densities contributed by matter and a cosmological constantrespectively. The results therefore strongly favour a nearly spatially flat Universe with a non-zero cosmological constant.") + self.assertTrue('reference' in json) + self.assertTrue(len(json['reference']) == 36) + + reader = readers['marc'](blob=blob, model='test_model', namespace='testsuite') + json = reader.translate() + self.assertTrue(json['__meta_metadata__']['__additional_info__']['model'] == 'test_model') + self.assertTrue(json['__meta_metadata__']['__additional_info__']['namespace'] == 'testsuite') + self.assertTrue('title_article' in json) + + + def test_json_reader(self): + """JsonAlchemy - Json reader""" + pass + + +TEST_SUITE = make_test_suite(TestReaders) + +if __name__ == '__main__': + run_test_suite(TEST_SUITE) diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/testsuite/test_storage_engine.py similarity index 100% copy from invenio/legacy/bibfield/functions/__init__.py copy to invenio/modules/jsonalchemy/testsuite/test_storage_engine.py diff --git a/invenio/legacy/bibfield/functions/__init__.py b/invenio/modules/jsonalchemy/testsuite/test_wrapper.py similarity index 100% rename from invenio/legacy/bibfield/functions/__init__.py rename to invenio/modules/jsonalchemy/testsuite/test_wrapper.py diff --git a/invenio/modules/jsonalchemy/validator.py b/invenio/modules/jsonalchemy/validator.py new file mode 100644 index 000000000..1c04402ad --- /dev/null +++ b/invenio/modules/jsonalchemy/validator.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.validator + ------------------------------------- + + See http://cerberus.readthedocs.org/en/latest +""" + +from cerberus import Validator as ValidatorBase +from cerberus import ValidationError, SchemaError +from cerberus import errors + +from .reader import FieldParser, ModelParser + + +class Validator(ValidatorBase): + """ + + """ + + def __init__(self, schema=None, transparent_schema_rules=True, + ignore_none_values=False, allow_unknown=True): + super(Validator, self).__init__(schema, transparent_schema_rules, + ignore_none_values, allow_unknown) + + def _validate(self, document, schema=None, update=False): + self._errors = {} + self.update = update + + if schema is not None: + self.schema = schema + elif self.schema is None: + raise SchemaError(errors.ERROR_SCHEMA_MISSING) + if not isinstance(self.schema, dict): + raise SchemaError(errors.ERROR_SCHEMA_FORMAT % str(self.schema)) + + if document is None: + raise ValidationError(errors.ERROR_DOCUMENT_MISSING) + if not hasattr(document, 'get'): + raise ValidationError(errors.ERROR_DOCUMENT_FORMAT % str(document)) + self.document = document + + special_rules = ["required", "nullable", "type"] + for field, value in self.document.items(): + + if self.ignore_none_values and value is None: + continue + + definition = self.schema.get(field) + if definition: + if isinstance(definition, dict): + + if definition.get("nullable", False) == True \ + and value is None: # noqa + continue + + if 'type' in definition: + self._validate_type(definition['type'], field, value) + if self.errors: + continue + + definition_rules = [rule for rule in definition.keys() + if rule not in special_rules] + for rule in definition_rules: + validatorname = "_validate_" + rule.replace(" ", "_") + validator = getattr(self, validatorname, None) + if validator: + validator(definition[rule], field, value) + elif not self.transparent_schema_rules: + raise SchemaError(errors.ERROR_UNKNOWN_RULE % + (rule, field)) + else: + raise SchemaError(errors.ERROR_DEFINITION_FORMAT % field) + + else: + if not self.allow_unknown: + self._error(field, errors.ERROR_UNKNOWN_FIELD) + + if not self.update: + self._validate_required_fields() + + return len(self._errors) == 0 + + def _validate_type_objectid(self, field, value): + """ + Enables validation for `objectid` schema attribute. + + :param field: field name. + :param value: field value. + """ + if not re.match('[a-f0-9]{24}', value): + self._error(field, ERROR_BAD_TYPE % 'ObjectId') diff --git a/invenio/modules/jsonalchemy/wrappers.py b/invenio/modules/jsonalchemy/wrappers.py new file mode 100644 index 000000000..c16b3cc60 --- /dev/null +++ b/invenio/modules/jsonalchemy/wrappers.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.jsonalchemy.wrapper + ----------------------------------- +""" +import datetime +import six + +from werkzeug.utils import import_string + +from invenio.base.utils import try_to_eval +from invenio.utils.datastructures import SmartDict + +from .parser import FieldParser, ModelParser +from .registry import functions, readers, producers +from .validator import Validator + + +class SmartJson(SmartDict): + """Base class for Json structures""" + + def __init__(self, json): + super(SmartJson, self).__init__(json) + self._dict_bson = SmartDict() + self._validator = None + + if '__meta_metadata__.__additional_info__.model_meta_classes' in self: + meta_classes = [import_string(str_cls) + for str_cls in self['__meta_metadata__.__additional_info__.model_meta_classes']] + self.__class__ = type(self.__class__.__name__, + [self.__class__] + meta_classes, {}) + + def __getitem__(self, key): + """ + Uses the load capabilities to output the information stored in the DB. + """ + try: + return self._dict_bson[key] + except KeyError: + #We will try to find the key inside the json dict and load it + pass + + main_key = SmartDict.main_key_pattern.sub('', key) + if main_key in self._dict['__meta_metadata__']['__aliases__']: + try: + rest_of_key = SmartDict.main_key_pattern.findall(key)[0] + except IndexError: + rest_of_key = '' + return self[self._dict['__meta_metadata__']['__aliases__'][main_key] + rest_of_key] + try: + if self._dict['__meta_metadata__'][main_key]['type'] == 'calculated': + self._load_precalculated_value(main_key) + else: + self._loads(main_key) + except KeyError: + self._loads(main_key) + + return self._dict_bson[key] + + def __setitem__(self, key, value): + """ + Uses the dumps capabilities to set the items to store them in the DB + """ + main_key = SmartDict.main_key_pattern.sub('', key) + if main_key in self: + self._dict_bson[key] = value + else: + reader = readers[self['__meta_metadata__']['__additional_info__']['master_format']]() + reader.set(self, main_key) + self._dict_bson[key] = value + + self._dumps(main_key) + + def items(self): + for key in self.keys(): + yield (key, self[key]) + + @property + def validation_errors(self): + if self._validator is None: + self.validate() + return self._validator.errors + + def dumps(self): + """ """ + for key in self._dict_bson.keys(): + if key == '__meta_metadata__': + continue + self._dumps(key) + return self._dict + + def loads(self): + """ """ + for key in self._dict.keys(): + if key == '__meta_metadata__': + continue + self._loads(key) + return self._dict_bson._dict + + def produce(self, output, fields=None): + return producers[output](self, fields=fields) + + def validate(self): + + def find_schema(json_id): + schema = FieldParser.field_definitions(self['__meta_metadata__']['__additional_info__']['namespace']).get(json_id, {}) + if isinstance(schema, list): + for jjson_id in schema: + yield FieldParser.field_definitions(self['__meta_metadata__']['__additional_info__']['namespace']).get(jjson_id, {}).get('schema', {}) + raise StopIteration() + yield schema.get('schema', {}) + + if self._validator is None: + schema = {} + model_fields = ModelParser.model_definitions(self['__meta_metadata__']['__additional_info__']['namespace']).get(fields, {}) + if model_fields: + for field in self.document.keys(): + if field not in model_fields: + model_fields[field] = field + model_field = [json_id for json_id in model_fields.values()] + else: + model_fields = self.document.keys() + + for json_id in model_fields: + for schema in find_schema(json_id): + self.schema.update(schema) + self._validator = Validator(schema=shema) + + return self._validator.validate(self) + + def _dumps(self, field): + """ """ + try: + self._dict[field] = reduce(lambda obj, key: obj[key], \ + self._dict['__meta_metadata__'][field]['dumps'], \ + FieldParser.field_definitions(self['__meta_metadata__']['__additional_info__']['namespace']))(self._dict_bson[field]) + except (KeyError, IndexError): + if self['__meta_metadata__'][field]['memoize'] or \ + self['__meta_metadata__'][field]['type'] in ('derived', 'creator', 'UNKNOW'): + self._dict[field] = self._dict_bson[field] + + def _loads(self, field): + """ """ + try: + self._dict_bson[field] = reduce(lambda obj, key: obj[key], \ + self._dict['__meta_metadata__'][field]['loads'], \ + FieldParser.field_definition(self['__meta_metadata__']['__additional_info__']['namespace']))(self._dict[field]) + except (KeyError, IndexError): + self._dict_bson[field] = self._dict[field] + + def _load_precalculated_value(self, field): + """ + + """ + if self._dict['__meta_metadata__'][field]['memoize'] is None: + func = reduce(lambda obj, key: obj[key], \ + self._dict['__meta_metadata__'][field]['function'], \ + FieldParser.field_definitions(self['__meta_metadata__']['__additional_info__']['namespace'])) + self._dict_bson[field] = try_to_eval(func, functions, self=self) + else: + live_time = datetime.timedelta(0, self._dict['__meta_metadata__'][field]['memoize']) + timestamp = datetime.datetime.strptime(self._dict['__meta_metadata__'][field]['timestamp'], "%Y-%m-%dT%H:%M:%S") + if datetime.datetime.now() > timestamp + live_time: + old_value = self._dict_bson[field] + func = reduce(lambda obj, key: obj[key], \ + self._dict['__meta_metadata__'][field]['function'], \ + FieldParser.field_definitions(self['__meta_metadata__']['__additional_info__']['namespace'])) + self._dict_bson[field] = try_to_eval(func, functions, self=self) + if not old_value == self._dict_bson[field]: + #FIXME: trigger update in DB and fire signal to update others + pass + + # Legacy methods, try not to use them as they are already deprecated + + def legacy_export_as_marc(self): + """ + It creates a valid marcxml using the legacy rules defined in the config + file + """ + from collections import Iterable + def encode_for_marcxml(value): + from invenio.utils.text import encode_for_xml + if isinstance(value, unicode): + value = value.encode('utf8') + return encode_for_xml(str(value)) + + export = '' + marc_dicts = self.produce('json_for_marc') + for marc_dict in marc_dicts: + content = '' + tag = '' + ind1 = '' + ind2 = '' + for key, value in marc_dict.iteritems(): + if isinstance(value, six.string_types) or not isinstance(value, Iterable): + value = [value] + for v in value: + if v is None: + continue + if key.startswith('00') and len(key) == 3: + # Control Field (No indicators no subfields) + export += '%s\n' % (key, encode_for_marcxml(v)) + elif len(key) == 6: + if not (tag == key[:3] and ind1 == key[3].replace('_', '') and ind2 == key[4].replace('_', '')): + tag = key[:3] + ind1 = key[3].replace('_', '') + ind2 = key[4].replace('_', '') + if content: + export += '%s\n' % (tag, ind1, ind2, content) + content = '' + content += '%s' % (key[5], encode_for_marcxml(v)) + else: + pass + + if content: + export += '%s\n' % (tag, ind1, ind2, content) + + export += '' + return export + + def legacy_create_recstruct(self): + """ + It creates the recstruct representation using the legacy rules defined in + the configuration file + + #CHECK: it might be a bit overkilling + """ + from invenio.legacy.bibrecord import create_record + return create_record(self.legacy_export_as_marc())[0] + diff --git a/invenio/modules/linkbacks/models.py b/invenio/modules/linkbacks/models.py index 7dc1dd25c..38f5672d2 100644 --- a/invenio/modules/linkbacks/models.py +++ b/invenio/modules/linkbacks/models.py @@ -1,118 +1,118 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012 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 02D111-1307, USA. """ WebLinkBack database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.accounts.models import User class LnkADMINURL(db.Model): """Represents a LnkADMINURL record.""" __tablename__ = 'lnkADMINURL' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False) url = db.Column(db.String(100), nullable=False, unique=True) list = db.Column(db.String(30), nullable=False, index=True) class LnkENTRY(db.Model): """Represents a LnkENTRY record.""" __tablename__ = 'lnkENTRY' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False) origin_url = db.Column(db.String(100), nullable=False) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False) additional_properties = db.Column(db.Binary) type = db.Column(db.String(30), nullable=False, index=True) status = db.Column(db.String(30), nullable=False, server_default='PENDING', index=True) insert_time = db.Column(db.DateTime, server_default='1900-01-01 00:00:00', index=True) @property def title(self): try: return db.object_session(self).query(LnkENTRYURLTITLE).\ filter(db.and_( LnkENTRYURLTITLE.url==self.origin_url, LnkENTRYURLTITLE.title<>"", LnkENTRYURLTITLE.broken==0)).first().title except: return self.origin_url class LnkENTRYURLTITLE(db.Model): """Represents a LnkENTRYURLTITLE record.""" __tablename__ = 'lnkENTRYURLTITLE' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False) url = db.Column(db.String(100), nullable=False, unique=True) title = db.Column(db.String(100), nullable=False, index=True) manual_set = db.Column(db.TinyInteger(1), nullable=False, server_default='0') broken_count = db.Column(db.Integer(5), server_default='0') broken = db.Column(db.TinyInteger(1), nullable=False, server_default='0') class LnkLOG(db.Model): """Represents a LnkLOG record.""" __tablename__ = 'lnkLOG' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id)) action = db.Column(db.String(30), nullable=False, index=True) log_time = db.Column(db.DateTime, server_default='1900-01-01 00:00:00', index=True) class LnkENTRYLOG(db.Model): """Represents a LnkENTRYLOG record.""" __tablename__ = 'lnkENTRYLOG' id_lnkENTRY = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(LnkENTRY.id), nullable=False, primary_key=True) id_lnkLOG = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(LnkLOG.id), nullable=False, primary_key=True) class LnkADMINURLLOG(db.Model): """Represents a LnkADMINURLLOG record.""" __tablename__ = 'lnkADMINURLLOG' id_lnkADMINURL = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(LnkADMINURL.id), primary_key=True, nullable=False) id_lnkLOG = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(LnkLOG.id), primary_key=True, nullable=False) __all__ = [ 'LnkADMINURL', 'LnkADMINURLLOG', 'LnkENTRY', 'LnkENTRYLOG', 'LnkENTRYURLTITLE', 'LnkLOG'] diff --git a/invenio/modules/oai_harvest/models.py b/invenio/modules/oai_harvest/models.py index 91d3de8ee..ba6af2c05 100644 --- a/invenio/modules/oai_harvest/models.py +++ b/invenio/modules/oai_harvest/models.py @@ -1,119 +1,119 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ Oai harvest database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. from invenio.utils.serializers import deserialize_via_marshal #from websearch_model import Collection -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.scheduler.models import SchTASK class OaiHARVEST(db.Model): """Represents a OaiHARVEST record.""" __tablename__ = 'oaiHARVEST' id = db.Column(db.MediumInteger(9, unsigned=True), nullable=False, primary_key=True, autoincrement=True) baseurl = db.Column(db.String(255), nullable=False, server_default='') metadataprefix = db.Column(db.String(255), nullable=False, server_default='oai_dc') arguments = db.Column(db.LargeBinary, nullable=True) comment = db.Column(db.Text, nullable=True) name = db.Column(db.String(255), nullable=False) lastrun = db.Column(db.DateTime, nullable=True) frequency = db.Column(db.MediumInteger(12), nullable=False, server_default='0') postprocess = db.Column(db.String(20), nullable=False, server_default='h') setspecs = db.Column(db.Text, nullable=False) def get_arguments(self): return deserialize_via_marshal(self.arguments) @classmethod def get(cls, *criteria, **filters): """ A wrapper for the filter and filter_by functions of sqlalchemy. Define a dict with which columns should be filtered by which values. look up also sqalchemy BaseQuery's filter and filter_by documentation """ return cls.query.filter(*criteria).filter_by(**filters) class OaiREPOSITORY(db.Model): """Represents a OaiREPOSITORY record.""" __tablename__ = 'oaiREPOSITORY' id = db.Column(db.MediumInteger(9, unsigned=True), nullable=False, primary_key=True, autoincrement=True) setName = db.Column(db.String(255), nullable=False, server_default='') setSpec = db.Column(db.String(255), nullable=False, server_default='') setCollection = db.Column(db.String(255), nullable=False, server_default='') setDescription = db.Column(db.Text, nullable=False) setDefinition = db.Column(db.Text, nullable=False) setRecList = db.Column(db.iLargeBinary, nullable=True) last_updated = db.Column(db.DateTime, nullable=False, server_default='1970-01-01 00:00:00') p1 = db.Column(db.Text, nullable=False) f1 = db.Column(db.Text, nullable=False) m1 = db.Column(db.Text, nullable=False) p2 = db.Column(db.Text, nullable=False) f2 = db.Column(db.Text, nullable=False) m2 = db.Column(db.Text, nullable=False) p3 = db.Column(db.Text, nullable=False) f3 = db.Column(db.Text, nullable=False) m3 = db.Column(db.Text, nullable=False) class OaiHARVESTLOG(db.Model): """Represents a OaiHARVESTLOG record.""" __tablename__ = 'oaiHARVESTLOG' id_oaiHARVEST = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(OaiHARVEST.id), nullable=False) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') bibupload_task_id = db.Column(db.Integer(11), db.ForeignKey(SchTASK.id), nullable=False, server_default='0', primary_key=True) oai_id = db.Column(db.String(40), nullable=False, server_default='', primary_key=True) date_harvested = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', primary_key=True) date_inserted = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') inserted_to_db = db.Column(db.Char(1), nullable=False, server_default='P') bibrec = db.relationship(Bibrec, backref='harvestlogs') schtask = db.relationship(SchTASK) __all__ = ['OaiHARVEST', 'OaiREPOSITORY', 'OaiHARVESTLOG'] diff --git a/invenio/modules/ranker/models.py b/invenio/modules/ranker/models.py index ec32ede61..31f7e7efb 100644 --- a/invenio/modules/ranker/models.py +++ b/invenio/modules/ranker/models.py @@ -1,221 +1,222 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ BibRank database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. from invenio.modules.accounts.models import User -from invenio.modules.record_editor.models import Bibrec, Bibdoc +from invenio.modules.record_editor.models import Bibdoc +from invenio.modules.records.models import Record as Bibrec from invenio.modules.search.models import Collection from flask import g class RnkMETHOD(db.Model): """Represents a RnkMETHOD record.""" __tablename__ = 'rnkMETHOD' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, nullable=False) name = db.Column(db.String(20), unique=True, nullable=False, server_default='') last_updated = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') def get_name_ln(self, ln=None): """Returns localized method name.""" try: if ln is None: ln = g.ln return self.names.filter_by(ln=g.ln, type='ln').one().value except: return self.name class RnkMETHODDATA(db.Model): """Represents a RnkMETHODDATA record.""" __tablename__ = 'rnkMETHODDATA' id_rnkMETHOD = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(RnkMETHOD.id), primary_key=True) relevance_data = db.Column(db.iLargeBinary, nullable=True) class RnkMETHODNAME(db.Model): """Represents a RnkMETHODNAME record.""" __tablename__ = 'rnkMETHODNAME' id_rnkMETHOD = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(RnkMETHOD.id), primary_key=True) ln = db.Column(db.Char(5), primary_key=True, server_default='') type = db.Column(db.Char(3), primary_key=True, server_default='sn') value = db.Column(db.String(255), nullable=False) method = db.relationship(RnkMETHOD, backref=db.backref('names', lazy='dynamic')) class RnkCITATIONDATA(db.Model): """Represents a RnkCITATIONDATA record.""" __tablename__ = 'rnkCITATIONDATA' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True) object_name = db.Column(db.String(20), unique=True, nullable=False) object_value = db.Column(db.iLargeBinary, nullable=True) last_updated = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') class RnkCITATIONDATAERR(db.Model): """Represents a RnkCITATIONDATAERR record.""" __tablename__ = 'rnkCITATIONDATAERR' type = db.Column(db.Enum('multiple-matches', 'not-well-formed'), primary_key=True) citinfo = db.Column(db.String(255), primary_key=True, server_default='') class RnkCITATIONDATAEXT(db.Model): """Represents a RnkCITATIONDATAEXT record.""" __tablename__ = 'rnkCITATIONDATAEXT' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), autoincrement=False, primary_key=True, nullable=False, server_default='0') extcitepubinfo = db.Column(db.String(255), primary_key=True, nullable=False, index=True) class RnkAUTHORDATA(db.Model): """Represents a RnkAUTHORDATA record.""" __tablename__ = 'rnkAUTHORDATA' aterm = db.Column(db.String(50), primary_key=True, nullable=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class RnkDOWNLOADS(db.Model): """Represents a RnkDOWNLOADS record.""" __tablename__ = 'rnkDOWNLOADS' id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=True) download_time = db.Column(db.DateTime, nullable=True, server_default='1900-01-01 00:00:00') client_host = db.Column(db.Integer(10, unsigned=True), nullable=True) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=True) id_bibdoc = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), nullable=True) file_version = db.Column(db.SmallInteger(2, unsigned=True), nullable=True) file_format = db.Column(db.String(50), nullable=True) bibrec = db.relationship(Bibrec, backref='downloads') bibdoc = db.relationship(Bibdoc, backref='downloads') user = db.relationship(User, backref='downloads') class RnkPAGEVIEWS(db.Model): """Represents a RnkPAGEVIEWS record.""" __tablename__ = 'rnkPAGEVIEWS' id = db.Column(db.MediumInteger, primary_key=True, nullable=False, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=True, primary_key=True) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), server_default='0', primary_key=True) client_host = db.Column(db.Integer(10, unsigned=True), nullable=True) view_time = db.Column(db.DateTime, primary_key=True, server_default='1900-01-01 00:00:00') bibrec = db.relationship(Bibrec, backref='pageviews') user = db.relationship(User, backref='pageviews') class RnkWORD01F(db.Model): """Represents a RnkWORD01F record.""" __tablename__ = 'rnkWORD01F' id = db.Column(db.MediumInteger(9, unsigned=True), nullable=False, primary_key=True, autoincrement=True) term = db.Column(db.String(50), nullable=True, unique=True) hitlist = db.Column(db.iLargeBinary, nullable=True) class RnkWORD01R(db.Model): """Represents a RnkWORD01R record.""" __tablename__ = 'rnkWORD01R' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True) termlist = db.Column(db.LargeBinary, nullable=True) type = db.Column(db.Enum('CURRENT', 'FUTURE', 'TEMPORARY'), nullable=False, server_default='CURRENT', primary_key=True) bibrec = db.relationship(Bibrec, backref='word01rs') class RnkEXTENDEDAUTHORS(db.Model): """Represents a RnkEXTENDEDAUTHORS record.""" __tablename__ = 'rnkEXTENDEDAUTHORS' id = db.Column(db.Integer(10, unsigned=True), primary_key=True, nullable=False, autoincrement=False) authorid = db.Column(db.BigInteger(10), primary_key=True, nullable=False, autoincrement=False) class RnkRECORDSCACHE(db.Model): """Represents a RnkRECORDSCACHE record.""" __tablename__ = 'rnkRECORDSCACHE' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=True, primary_key=True) authorid = db.Column(db.BigInteger(10), primary_key=True, nullable=False) class RnkSELFCITES(db.Model): """Represents a RnkSELFCITES record.""" __tablename__ = 'rnkSELFCITES' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=True, primary_key=True) count = db.Column(db.Integer(10, unsigned=True), nullable=False) references = db.Column(db.Text, nullable=False) last_updated = db.Column(db.DateTime, nullable=False) class CollectionRnkMETHOD(db.Model): """Represents a CollectionRnkMETHOD record.""" __tablename__ = 'collection_rnkMETHOD' id_collection = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Collection.id), primary_key=True, nullable=False) id_rnkMETHOD = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(RnkMETHOD.id), primary_key=True, nullable=False) score = db.Column(db.TinyInteger(4, unsigned=True), nullable=False, server_default='0') collection = db.relationship(Collection, backref='rnkMETHODs') rnkMETHOD = db.relationship(RnkMETHOD, backref='collections') __all__ = ['RnkMETHOD', 'RnkMETHODDATA', 'RnkMETHODNAME', 'RnkCITATIONDATA', 'RnkCITATIONDATAERR', 'RnkCITATIONDATAEXT', 'RnkAUTHORDATA', 'RnkDOWNLOADS', 'RnkPAGEVIEWS', 'RnkWORD01F', 'RnkWORD01R', 'RnkEXTENDEDAUTHORS', 'RnkRECORDSCACHE', 'RnkSELFCITES', 'CollectionRnkMETHOD'] diff --git a/invenio/modules/record_editor/models.py b/invenio/modules/record_editor/models.py index cd93e2fcf..aaf59be5b 100644 --- a/invenio/modules/record_editor/models.py +++ b/invenio/modules/record_editor/models.py @@ -1,3549 +1,3459 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012, 2013 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 02D111-1307, USA. """ BibEdit database models. """ # General imports. from invenio.ext.sqlalchemy import db from flask import current_app from werkzeug import cached_property -# Create your models here. - +from invenio.modules.records.models import Record as Bibrec -class Bibrec(db.Model): - """Represents a Bibrec record.""" - def __init__(self): - pass - __tablename__ = 'bibrec' - id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, - nullable=False, - autoincrement=True) - creation_date = db.Column(db.DateTime, nullable=False, - server_default='1900-01-01 00:00:00', - index=True) - modification_date = db.Column(db.DateTime, nullable=False, - server_default='1900-01-01 00:00:00', - index=True) - master_format = db.Column(db.String(16), nullable=False, - server_default='marc') - - @property - def deleted(self): - """ - Return True if record is marked as deleted. - """ - from invenio.legacy.bibrecord import get_fieldvalues - # record exists; now check whether it isn't marked as deleted: - dbcollids = get_fieldvalues(self.id, "980__%") - - return ("DELETED" in dbcollids) or \ - (current_app.config.get('CFG_CERN_SITE') - and "DUMMY" in dbcollids) - - @staticmethod - def _next_merged_recid(recid): - """ Returns the ID of record merged with record with ID = recid """ - from invenio.legacy.bibrecord import get_fieldvalues - merged_recid = None - for val in get_fieldvalues(recid, "970__d"): - try: - merged_recid = int(val) - break - except ValueError: - pass - - if not merged_recid: - return None - else: - return merged_recid - - @cached_property - def merged_recid(self): - """ Return the record object with - which the given record has been merged. - @param recID: deleted record recID - @type recID: int - @return: merged record recID - @rtype: int or None - """ - return Bibrec._next_merged_recid(self.id) - - @property - def merged_recid_final(self): - """ Returns the last record from hierarchy of - records merged with this one """ - - cur_id = self.id - next_id = Bibrec._next_merged_recid(cur_id) - - while next_id: - cur_id = next_id - next_id = Bibrec._next_merged_recid(cur_id) - - return cur_id - - @cached_property - def is_restricted(self): - """Returns True is record is restricted.""" - from invenio.legacy.search_engine import get_restricted_collections_for_recid - - if get_restricted_collections_for_recid(self.id, - recreate_cache_if_needed=False): - return True - elif self.is_processed: - return True - return False - - @cached_property - def is_processed(self): - """Returns True is recods is processed (not in any collection).""" - from invenio.legacy.search_engine import is_record_in_any_collection - return not is_record_in_any_collection(self.id, - recreate_cache_if_needed=False) +# Create your models here. class BibHOLDINGPEN(db.Model): """Represents a BibHOLDINGPEN record.""" def __init__(self): pass __tablename__ = 'bibHOLDINGPEN' changeset_id = db.Column(db.Integer(11), primary_key=True, autoincrement=True) changeset_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', index=True) changeset_xml = db.Column(db.Text, nullable=False) oai_id = db.Column(db.String(40), nullable=False, server_default='') id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') bibrec = db.relationship(Bibrec, backref='holdingpen') class Bibdoc(db.Model): """Represents a Bibdoc record.""" __tablename__ = 'bibdoc' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, nullable=False, autoincrement=True) status = db.Column(db.Text, nullable=False) docname = db.Column(db.String(250), nullable=True, # collation='utf8_bin' server_default='file', index=True) creation_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', index=True) modification_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', index=True) text_extraction_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') doctype = db.Column(db.String(255)) class BibdocBibdoc(db.Model): """Represents a BibdocBibdoc record.""" __tablename__ = 'bibdoc_bibdoc' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True, nullable=False, autoincrement=True) id_bibdoc1 = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), nullable=True) version1 = db.Column(db.TinyInteger(4, unsigned=True)) format1 = db.Column(db.String(50)) id_bibdoc2 = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), nullable=True) version2 = db.Column(db.TinyInteger(4, unsigned=True)) format2 = db.Column(db.String(50)) rel_type = db.Column(db.String(255), nullable=True) bibdoc1 = db.relationship(Bibdoc, backref='bibdoc2s', primaryjoin=Bibdoc.id == id_bibdoc1) bibdoc2 = db.relationship(Bibdoc, backref='bibdoc1s', primaryjoin=Bibdoc.id == id_bibdoc2) class BibrecBibdoc(db.Model): """Represents a BibrecBibdoc record.""" def __init__(self): pass __tablename__ = 'bibrec_bibdoc' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0', primary_key=True) id_bibdoc = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), nullable=False, server_default='0', primary_key=True) docname = db.Column(db.String(250), nullable=False, # collation='utf8_bin' server_default='file', index=True) type = db.Column(db.String(255), nullable=True) bibrec = db.relationship(Bibrec, backref='bibdocs') bibdoc = db.relationship(Bibdoc, backref='bibrecs') class HstDOCUMENT(db.Model): """Represents a HstDOCUMENT record.""" def __init__(self): pass __tablename__ = 'hstDOCUMENT' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False, autoincrement=True) id_bibdoc = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), primary_key=True, nullable=False, autoincrement=False) docname = db.Column(db.String(250), nullable=False, index=True) docformat = db.Column(db.String(50), nullable=False, index=True) docversion = db.Column(db.TinyInteger(4, unsigned=True), nullable=False) docsize = db.Column(db.BigInteger(15, unsigned=True), nullable=False) docchecksum = db.Column(db.Char(32), nullable=False) doctimestamp = db.Column(db.DateTime, nullable=False, index=True) action = db.Column(db.String(50), nullable=False, index=True) job_id = db.Column(db.MediumInteger(15, unsigned=True), nullable=True, index=True) job_name = db.Column(db.String(255), nullable=True, index=True) job_person = db.Column(db.String(255), nullable=True, index=True) job_date = db.Column(db.DateTime, nullable=True, index=True) job_details = db.Column(db.iBinary, nullable=True) class HstRECORD(db.Model): """Represents a HstRECORD record.""" def __init__(self): pass __tablename__ = 'hstRECORD' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), autoincrement=False, nullable=False, primary_key=True) marcxml = db.Column(db.iBinary, nullable=False) job_id = db.Column(db.MediumInteger(15, unsigned=True), nullable=False, index=True) job_name = db.Column(db.String(255), nullable=False, index=True) job_person = db.Column(db.String(255), nullable=False, index=True) job_date = db.Column(db.DateTime, nullable=False, index=True) job_details = db.Column(db.iBinary, nullable=False) affected_fields = db.Column(db.Text, nullable=True) # GENERATED class Bib00x(db.Model): """Represents a Bib00x record.""" def __init__(self): pass __tablename__ = 'bib00x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib00x(db.Model): """Represents a BibrecBib00x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib00x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib00x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib00xs') bibxxx = db.relationship(Bib00x, backref='bibrecs') class Bib01x(db.Model): """Represents a Bib01x record.""" def __init__(self): pass __tablename__ = 'bib01x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib01x(db.Model): """Represents a BibrecBib01x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib01x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib01x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib01xs') bibxxx = db.relationship(Bib01x, backref='bibrecs') class Bib02x(db.Model): """Represents a Bib02x record.""" def __init__(self): pass __tablename__ = 'bib02x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib02x(db.Model): """Represents a BibrecBib02x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib02x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib02x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib02xs') bibxxx = db.relationship(Bib02x, backref='bibrecs') class Bib03x(db.Model): """Represents a Bib03x record.""" def __init__(self): pass __tablename__ = 'bib03x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib03x(db.Model): """Represents a BibrecBib03x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib03x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib03x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib03xs') bibxxx = db.relationship(Bib03x, backref='bibrecs') class Bib04x(db.Model): """Represents a Bib04x record.""" def __init__(self): pass __tablename__ = 'bib04x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib04x(db.Model): """Represents a BibrecBib04x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib04x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib04x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib04xs') bibxxx = db.relationship(Bib04x, backref='bibrecs') class Bib05x(db.Model): """Represents a Bib05x record.""" def __init__(self): pass __tablename__ = 'bib05x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib05x(db.Model): """Represents a BibrecBib05x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib05x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib05x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib05xs') bibxxx = db.relationship(Bib05x, backref='bibrecs') class Bib06x(db.Model): """Represents a Bib06x record.""" def __init__(self): pass __tablename__ = 'bib06x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib06x(db.Model): """Represents a BibrecBib06x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib06x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib06x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib06xs') bibxxx = db.relationship(Bib06x, backref='bibrecs') class Bib07x(db.Model): """Represents a Bib07x record.""" def __init__(self): pass __tablename__ = 'bib07x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib07x(db.Model): """Represents a BibrecBib07x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib07x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib07x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib07xs') bibxxx = db.relationship(Bib07x, backref='bibrecs') class Bib08x(db.Model): """Represents a Bib08x record.""" def __init__(self): pass __tablename__ = 'bib08x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib08x(db.Model): """Represents a BibrecBib08x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib08x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib08x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib08xs') bibxxx = db.relationship(Bib08x, backref='bibrecs') class Bib09x(db.Model): """Represents a Bib09x record.""" def __init__(self): pass __tablename__ = 'bib09x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib09x(db.Model): """Represents a BibrecBib09x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib09x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib09x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib09xs') bibxxx = db.relationship(Bib09x, backref='bibrecs') class Bib10x(db.Model): """Represents a Bib10x record.""" def __init__(self): pass __tablename__ = 'bib10x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib10x(db.Model): """Represents a BibrecBib10x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib10x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib10x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib10xs') bibxxx = db.relationship(Bib10x, backref='bibrecs') class Bib11x(db.Model): """Represents a Bib11x record.""" def __init__(self): pass __tablename__ = 'bib11x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib11x(db.Model): """Represents a BibrecBib11x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib11x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib11x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib11xs') bibxxx = db.relationship(Bib11x, backref='bibrecs') class Bib12x(db.Model): """Represents a Bib12x record.""" def __init__(self): pass __tablename__ = 'bib12x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib12x(db.Model): """Represents a BibrecBib12x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib12x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib12x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib12xs') bibxxx = db.relationship(Bib12x, backref='bibrecs') class Bib13x(db.Model): """Represents a Bib13x record.""" def __init__(self): pass __tablename__ = 'bib13x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib13x(db.Model): """Represents a BibrecBib13x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib13x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib13x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib13xs') bibxxx = db.relationship(Bib13x, backref='bibrecs') class Bib14x(db.Model): """Represents a Bib14x record.""" def __init__(self): pass __tablename__ = 'bib14x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib14x(db.Model): """Represents a BibrecBib14x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib14x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib14x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib14xs') bibxxx = db.relationship(Bib14x, backref='bibrecs') class Bib15x(db.Model): """Represents a Bib15x record.""" def __init__(self): pass __tablename__ = 'bib15x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib15x(db.Model): """Represents a BibrecBib15x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib15x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib15x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib15xs') bibxxx = db.relationship(Bib15x, backref='bibrecs') class Bib16x(db.Model): """Represents a Bib16x record.""" def __init__(self): pass __tablename__ = 'bib16x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib16x(db.Model): """Represents a BibrecBib16x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib16x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib16x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib16xs') bibxxx = db.relationship(Bib16x, backref='bibrecs') class Bib17x(db.Model): """Represents a Bib17x record.""" def __init__(self): pass __tablename__ = 'bib17x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib17x(db.Model): """Represents a BibrecBib17x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib17x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib17x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib17xs') bibxxx = db.relationship(Bib17x, backref='bibrecs') class Bib18x(db.Model): """Represents a Bib18x record.""" def __init__(self): pass __tablename__ = 'bib18x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib18x(db.Model): """Represents a BibrecBib18x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib18x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib18x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib18xs') bibxxx = db.relationship(Bib18x, backref='bibrecs') class Bib19x(db.Model): """Represents a Bib19x record.""" def __init__(self): pass __tablename__ = 'bib19x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib19x(db.Model): """Represents a BibrecBib19x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib19x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib19x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib19xs') bibxxx = db.relationship(Bib19x, backref='bibrecs') class Bib20x(db.Model): """Represents a Bib20x record.""" def __init__(self): pass __tablename__ = 'bib20x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib20x(db.Model): """Represents a BibrecBib20x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib20x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib20x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib20xs') bibxxx = db.relationship(Bib20x, backref='bibrecs') class Bib21x(db.Model): """Represents a Bib21x record.""" def __init__(self): pass __tablename__ = 'bib21x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib21x(db.Model): """Represents a BibrecBib21x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib21x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib21x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib21xs') bibxxx = db.relationship(Bib21x, backref='bibrecs') class Bib22x(db.Model): """Represents a Bib22x record.""" def __init__(self): pass __tablename__ = 'bib22x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib22x(db.Model): """Represents a BibrecBib22x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib22x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib22x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib22xs') bibxxx = db.relationship(Bib22x, backref='bibrecs') class Bib23x(db.Model): """Represents a Bib23x record.""" def __init__(self): pass __tablename__ = 'bib23x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib23x(db.Model): """Represents a BibrecBib23x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib23x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib23x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib23xs') bibxxx = db.relationship(Bib23x, backref='bibrecs') class Bib24x(db.Model): """Represents a Bib24x record.""" def __init__(self): pass __tablename__ = 'bib24x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib24x(db.Model): """Represents a BibrecBib24x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib24x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib24x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib24xs') bibxxx = db.relationship(Bib24x, backref='bibrecs') class Bib25x(db.Model): """Represents a Bib25x record.""" def __init__(self): pass __tablename__ = 'bib25x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib25x(db.Model): """Represents a BibrecBib25x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib25x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib25x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib25xs') bibxxx = db.relationship(Bib25x, backref='bibrecs') class Bib26x(db.Model): """Represents a Bib26x record.""" def __init__(self): pass __tablename__ = 'bib26x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib26x(db.Model): """Represents a BibrecBib26x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib26x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib26x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib26xs') bibxxx = db.relationship(Bib26x, backref='bibrecs') class Bib27x(db.Model): """Represents a Bib27x record.""" def __init__(self): pass __tablename__ = 'bib27x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib27x(db.Model): """Represents a BibrecBib27x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib27x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib27x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib27xs') bibxxx = db.relationship(Bib27x, backref='bibrecs') class Bib28x(db.Model): """Represents a Bib28x record.""" def __init__(self): pass __tablename__ = 'bib28x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib28x(db.Model): """Represents a BibrecBib28x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib28x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib28x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib28xs') bibxxx = db.relationship(Bib28x, backref='bibrecs') class Bib29x(db.Model): """Represents a Bib29x record.""" def __init__(self): pass __tablename__ = 'bib29x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib29x(db.Model): """Represents a BibrecBib29x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib29x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib29x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib29xs') bibxxx = db.relationship(Bib29x, backref='bibrecs') class Bib30x(db.Model): """Represents a Bib30x record.""" def __init__(self): pass __tablename__ = 'bib30x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib30x(db.Model): """Represents a BibrecBib30x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib30x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib30x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib30xs') bibxxx = db.relationship(Bib30x, backref='bibrecs') class Bib31x(db.Model): """Represents a Bib31x record.""" def __init__(self): pass __tablename__ = 'bib31x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib31x(db.Model): """Represents a BibrecBib31x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib31x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib31x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib31xs') bibxxx = db.relationship(Bib31x, backref='bibrecs') class Bib32x(db.Model): """Represents a Bib32x record.""" def __init__(self): pass __tablename__ = 'bib32x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib32x(db.Model): """Represents a BibrecBib32x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib32x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib32x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib32xs') bibxxx = db.relationship(Bib32x, backref='bibrecs') class Bib33x(db.Model): """Represents a Bib33x record.""" def __init__(self): pass __tablename__ = 'bib33x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib33x(db.Model): """Represents a BibrecBib33x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib33x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib33x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib33xs') bibxxx = db.relationship(Bib33x, backref='bibrecs') class Bib34x(db.Model): """Represents a Bib34x record.""" def __init__(self): pass __tablename__ = 'bib34x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib34x(db.Model): """Represents a BibrecBib34x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib34x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib34x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib34xs') bibxxx = db.relationship(Bib34x, backref='bibrecs') class Bib35x(db.Model): """Represents a Bib35x record.""" def __init__(self): pass __tablename__ = 'bib35x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib35x(db.Model): """Represents a BibrecBib35x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib35x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib35x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib35xs') bibxxx = db.relationship(Bib35x, backref='bibrecs') class Bib36x(db.Model): """Represents a Bib36x record.""" def __init__(self): pass __tablename__ = 'bib36x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib36x(db.Model): """Represents a BibrecBib36x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib36x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib36x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib36xs') bibxxx = db.relationship(Bib36x, backref='bibrecs') class Bib37x(db.Model): """Represents a Bib37x record.""" def __init__(self): pass __tablename__ = 'bib37x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib37x(db.Model): """Represents a BibrecBib37x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib37x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib37x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib37xs') bibxxx = db.relationship(Bib37x, backref='bibrecs') class Bib38x(db.Model): """Represents a Bib38x record.""" def __init__(self): pass __tablename__ = 'bib38x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib38x(db.Model): """Represents a BibrecBib38x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib38x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib38x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib38xs') bibxxx = db.relationship(Bib38x, backref='bibrecs') class Bib39x(db.Model): """Represents a Bib39x record.""" def __init__(self): pass __tablename__ = 'bib39x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib39x(db.Model): """Represents a BibrecBib39x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib39x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib39x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib39xs') bibxxx = db.relationship(Bib39x, backref='bibrecs') class Bib40x(db.Model): """Represents a Bib40x record.""" def __init__(self): pass __tablename__ = 'bib40x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib40x(db.Model): """Represents a BibrecBib40x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib40x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib40x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib40xs') bibxxx = db.relationship(Bib40x, backref='bibrecs') class Bib41x(db.Model): """Represents a Bib41x record.""" def __init__(self): pass __tablename__ = 'bib41x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib41x(db.Model): """Represents a BibrecBib41x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib41x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib41x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib41xs') bibxxx = db.relationship(Bib41x, backref='bibrecs') class Bib42x(db.Model): """Represents a Bib42x record.""" def __init__(self): pass __tablename__ = 'bib42x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib42x(db.Model): """Represents a BibrecBib42x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib42x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib42x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib42xs') bibxxx = db.relationship(Bib42x, backref='bibrecs') class Bib43x(db.Model): """Represents a Bib43x record.""" def __init__(self): pass __tablename__ = 'bib43x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib43x(db.Model): """Represents a BibrecBib43x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib43x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib43x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib43xs') bibxxx = db.relationship(Bib43x, backref='bibrecs') class Bib44x(db.Model): """Represents a Bib44x record.""" def __init__(self): pass __tablename__ = 'bib44x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib44x(db.Model): """Represents a BibrecBib44x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib44x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib44x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib44xs') bibxxx = db.relationship(Bib44x, backref='bibrecs') class Bib45x(db.Model): """Represents a Bib45x record.""" def __init__(self): pass __tablename__ = 'bib45x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib45x(db.Model): """Represents a BibrecBib45x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib45x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib45x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib45xs') bibxxx = db.relationship(Bib45x, backref='bibrecs') class Bib46x(db.Model): """Represents a Bib46x record.""" def __init__(self): pass __tablename__ = 'bib46x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib46x(db.Model): """Represents a BibrecBib46x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib46x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib46x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib46xs') bibxxx = db.relationship(Bib46x, backref='bibrecs') class Bib47x(db.Model): """Represents a Bib47x record.""" def __init__(self): pass __tablename__ = 'bib47x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib47x(db.Model): """Represents a BibrecBib47x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib47x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib47x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib47xs') bibxxx = db.relationship(Bib47x, backref='bibrecs') class Bib48x(db.Model): """Represents a Bib48x record.""" def __init__(self): pass __tablename__ = 'bib48x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib48x(db.Model): """Represents a BibrecBib48x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib48x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib48x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib48xs') bibxxx = db.relationship(Bib48x, backref='bibrecs') class Bib49x(db.Model): """Represents a Bib49x record.""" def __init__(self): pass __tablename__ = 'bib49x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib49x(db.Model): """Represents a BibrecBib49x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib49x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib49x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib49xs') bibxxx = db.relationship(Bib49x, backref='bibrecs') class Bib50x(db.Model): """Represents a Bib50x record.""" def __init__(self): pass __tablename__ = 'bib50x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib50x(db.Model): """Represents a BibrecBib50x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib50x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib50x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib50xs') bibxxx = db.relationship(Bib50x, backref='bibrecs') class Bib51x(db.Model): """Represents a Bib51x record.""" def __init__(self): pass __tablename__ = 'bib51x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib51x(db.Model): """Represents a BibrecBib51x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib51x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib51x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib51xs') bibxxx = db.relationship(Bib51x, backref='bibrecs') class Bib52x(db.Model): """Represents a Bib52x record.""" def __init__(self): pass __tablename__ = 'bib52x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib52x(db.Model): """Represents a BibrecBib52x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib52x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib52x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib52xs') bibxxx = db.relationship(Bib52x, backref='bibrecs') class Bib53x(db.Model): """Represents a Bib53x record.""" def __init__(self): pass __tablename__ = 'bib53x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib53x(db.Model): """Represents a BibrecBib53x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib53x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib53x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib53xs') bibxxx = db.relationship(Bib53x, backref='bibrecs') class Bib54x(db.Model): """Represents a Bib54x record.""" def __init__(self): pass __tablename__ = 'bib54x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib54x(db.Model): """Represents a BibrecBib54x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib54x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib54x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib54xs') bibxxx = db.relationship(Bib54x, backref='bibrecs') class Bib55x(db.Model): """Represents a Bib55x record.""" def __init__(self): pass __tablename__ = 'bib55x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib55x(db.Model): """Represents a BibrecBib55x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib55x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib55x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib55xs') bibxxx = db.relationship(Bib55x, backref='bibrecs') class Bib56x(db.Model): """Represents a Bib56x record.""" def __init__(self): pass __tablename__ = 'bib56x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib56x(db.Model): """Represents a BibrecBib56x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib56x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib56x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib56xs') bibxxx = db.relationship(Bib56x, backref='bibrecs') class Bib57x(db.Model): """Represents a Bib57x record.""" def __init__(self): pass __tablename__ = 'bib57x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib57x(db.Model): """Represents a BibrecBib57x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib57x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib57x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib57xs') bibxxx = db.relationship(Bib57x, backref='bibrecs') class Bib58x(db.Model): """Represents a Bib58x record.""" def __init__(self): pass __tablename__ = 'bib58x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib58x(db.Model): """Represents a BibrecBib58x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib58x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib58x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib58xs') bibxxx = db.relationship(Bib58x, backref='bibrecs') class Bib59x(db.Model): """Represents a Bib59x record.""" def __init__(self): pass __tablename__ = 'bib59x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib59x(db.Model): """Represents a BibrecBib59x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib59x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib59x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib59xs') bibxxx = db.relationship(Bib59x, backref='bibrecs') class Bib60x(db.Model): """Represents a Bib60x record.""" def __init__(self): pass __tablename__ = 'bib60x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib60x(db.Model): """Represents a BibrecBib60x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib60x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib60x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib60xs') bibxxx = db.relationship(Bib60x, backref='bibrecs') class Bib61x(db.Model): """Represents a Bib61x record.""" def __init__(self): pass __tablename__ = 'bib61x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib61x(db.Model): """Represents a BibrecBib61x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib61x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib61x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib61xs') bibxxx = db.relationship(Bib61x, backref='bibrecs') class Bib62x(db.Model): """Represents a Bib62x record.""" def __init__(self): pass __tablename__ = 'bib62x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib62x(db.Model): """Represents a BibrecBib62x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib62x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib62x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib62xs') bibxxx = db.relationship(Bib62x, backref='bibrecs') class Bib63x(db.Model): """Represents a Bib63x record.""" def __init__(self): pass __tablename__ = 'bib63x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib63x(db.Model): """Represents a BibrecBib63x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib63x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib63x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib63xs') bibxxx = db.relationship(Bib63x, backref='bibrecs') class Bib64x(db.Model): """Represents a Bib64x record.""" def __init__(self): pass __tablename__ = 'bib64x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib64x(db.Model): """Represents a BibrecBib64x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib64x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib64x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib64xs') bibxxx = db.relationship(Bib64x, backref='bibrecs') class Bib65x(db.Model): """Represents a Bib65x record.""" def __init__(self): pass __tablename__ = 'bib65x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib65x(db.Model): """Represents a BibrecBib65x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib65x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib65x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib65xs') bibxxx = db.relationship(Bib65x, backref='bibrecs') class Bib66x(db.Model): """Represents a Bib66x record.""" def __init__(self): pass __tablename__ = 'bib66x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib66x(db.Model): """Represents a BibrecBib66x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib66x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib66x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib66xs') bibxxx = db.relationship(Bib66x, backref='bibrecs') class Bib67x(db.Model): """Represents a Bib67x record.""" def __init__(self): pass __tablename__ = 'bib67x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib67x(db.Model): """Represents a BibrecBib67x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib67x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib67x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib67xs') bibxxx = db.relationship(Bib67x, backref='bibrecs') class Bib68x(db.Model): """Represents a Bib68x record.""" def __init__(self): pass __tablename__ = 'bib68x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib68x(db.Model): """Represents a BibrecBib68x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib68x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib68x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib68xs') bibxxx = db.relationship(Bib68x, backref='bibrecs') class Bib69x(db.Model): """Represents a Bib69x record.""" def __init__(self): pass __tablename__ = 'bib69x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib69x(db.Model): """Represents a BibrecBib69x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib69x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib69x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib69xs') bibxxx = db.relationship(Bib69x, backref='bibrecs') class Bib70x(db.Model): """Represents a Bib70x record.""" def __init__(self): pass __tablename__ = 'bib70x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib70x(db.Model): """Represents a BibrecBib70x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib70x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib70x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib70xs') bibxxx = db.relationship(Bib70x, backref='bibrecs') class Bib71x(db.Model): """Represents a Bib71x record.""" def __init__(self): pass __tablename__ = 'bib71x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib71x(db.Model): """Represents a BibrecBib71x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib71x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib71x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib71xs') bibxxx = db.relationship(Bib71x, backref='bibrecs') class Bib72x(db.Model): """Represents a Bib72x record.""" def __init__(self): pass __tablename__ = 'bib72x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib72x(db.Model): """Represents a BibrecBib72x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib72x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib72x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib72xs') bibxxx = db.relationship(Bib72x, backref='bibrecs') class Bib73x(db.Model): """Represents a Bib73x record.""" def __init__(self): pass __tablename__ = 'bib73x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib73x(db.Model): """Represents a BibrecBib73x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib73x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib73x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib73xs') bibxxx = db.relationship(Bib73x, backref='bibrecs') class Bib74x(db.Model): """Represents a Bib74x record.""" def __init__(self): pass __tablename__ = 'bib74x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib74x(db.Model): """Represents a BibrecBib74x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib74x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib74x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib74xs') bibxxx = db.relationship(Bib74x, backref='bibrecs') class Bib75x(db.Model): """Represents a Bib75x record.""" def __init__(self): pass __tablename__ = 'bib75x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib75x(db.Model): """Represents a BibrecBib75x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib75x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib75x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib75xs') bibxxx = db.relationship(Bib75x, backref='bibrecs') class Bib76x(db.Model): """Represents a Bib76x record.""" def __init__(self): pass __tablename__ = 'bib76x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib76x(db.Model): """Represents a BibrecBib76x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib76x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib76x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib76xs') bibxxx = db.relationship(Bib76x, backref='bibrecs') class Bib77x(db.Model): """Represents a Bib77x record.""" def __init__(self): pass __tablename__ = 'bib77x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib77x(db.Model): """Represents a BibrecBib77x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib77x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib77x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib77xs') bibxxx = db.relationship(Bib77x, backref='bibrecs') class Bib78x(db.Model): """Represents a Bib78x record.""" def __init__(self): pass __tablename__ = 'bib78x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib78x(db.Model): """Represents a BibrecBib78x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib78x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib78x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib78xs') bibxxx = db.relationship(Bib78x, backref='bibrecs') class Bib79x(db.Model): """Represents a Bib79x record.""" def __init__(self): pass __tablename__ = 'bib79x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib79x(db.Model): """Represents a BibrecBib79x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib79x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib79x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib79xs') bibxxx = db.relationship(Bib79x, backref='bibrecs') class Bib80x(db.Model): """Represents a Bib80x record.""" def __init__(self): pass __tablename__ = 'bib80x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib80x(db.Model): """Represents a BibrecBib80x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib80x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib80x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib80xs') bibxxx = db.relationship(Bib80x, backref='bibrecs') class Bib81x(db.Model): """Represents a Bib81x record.""" def __init__(self): pass __tablename__ = 'bib81x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib81x(db.Model): """Represents a BibrecBib81x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib81x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib81x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib81xs') bibxxx = db.relationship(Bib81x, backref='bibrecs') class Bib82x(db.Model): """Represents a Bib82x record.""" def __init__(self): pass __tablename__ = 'bib82x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib82x(db.Model): """Represents a BibrecBib82x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib82x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib82x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib82xs') bibxxx = db.relationship(Bib82x, backref='bibrecs') class Bib83x(db.Model): """Represents a Bib83x record.""" def __init__(self): pass __tablename__ = 'bib83x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib83x(db.Model): """Represents a BibrecBib83x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib83x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib83x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib83xs') bibxxx = db.relationship(Bib83x, backref='bibrecs') class Bib84x(db.Model): """Represents a Bib84x record.""" def __init__(self): pass __tablename__ = 'bib84x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib84x(db.Model): """Represents a BibrecBib84x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib84x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib84x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib84xs') bibxxx = db.relationship(Bib84x, backref='bibrecs') class Bib85x(db.Model): """Represents a Bib85x record.""" def __init__(self): pass __tablename__ = 'bib85x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib85x(db.Model): """Represents a BibrecBib85x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib85x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib85x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib85xs') bibxxx = db.relationship(Bib85x, backref='bibrecs') class Bib86x(db.Model): """Represents a Bib86x record.""" def __init__(self): pass __tablename__ = 'bib86x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib86x(db.Model): """Represents a BibrecBib86x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib86x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib86x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib86xs') bibxxx = db.relationship(Bib86x, backref='bibrecs') class Bib87x(db.Model): """Represents a Bib87x record.""" def __init__(self): pass __tablename__ = 'bib87x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib87x(db.Model): """Represents a BibrecBib87x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib87x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib87x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib87xs') bibxxx = db.relationship(Bib87x, backref='bibrecs') class Bib88x(db.Model): """Represents a Bib88x record.""" def __init__(self): pass __tablename__ = 'bib88x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib88x(db.Model): """Represents a BibrecBib88x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib88x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib88x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib88xs') bibxxx = db.relationship(Bib88x, backref='bibrecs') class Bib89x(db.Model): """Represents a Bib89x record.""" def __init__(self): pass __tablename__ = 'bib89x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib89x(db.Model): """Represents a BibrecBib89x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib89x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib89x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib89xs') bibxxx = db.relationship(Bib89x, backref='bibrecs') class Bib90x(db.Model): """Represents a Bib90x record.""" def __init__(self): pass __tablename__ = 'bib90x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib90x(db.Model): """Represents a BibrecBib90x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib90x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib90x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib90xs') bibxxx = db.relationship(Bib90x, backref='bibrecs') class Bib91x(db.Model): """Represents a Bib91x record.""" def __init__(self): pass __tablename__ = 'bib91x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib91x(db.Model): """Represents a BibrecBib91x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib91x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib91x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib91xs') bibxxx = db.relationship(Bib91x, backref='bibrecs') class Bib92x(db.Model): """Represents a Bib92x record.""" def __init__(self): pass __tablename__ = 'bib92x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib92x(db.Model): """Represents a BibrecBib92x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib92x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib92x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib92xs') bibxxx = db.relationship(Bib92x, backref='bibrecs') class Bib93x(db.Model): """Represents a Bib93x record.""" def __init__(self): pass __tablename__ = 'bib93x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib93x(db.Model): """Represents a BibrecBib93x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib93x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib93x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib93xs') bibxxx = db.relationship(Bib93x, backref='bibrecs') class Bib94x(db.Model): """Represents a Bib94x record.""" def __init__(self): pass __tablename__ = 'bib94x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib94x(db.Model): """Represents a BibrecBib94x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib94x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib94x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib94xs') bibxxx = db.relationship(Bib94x, backref='bibrecs') class Bib95x(db.Model): """Represents a Bib95x record.""" def __init__(self): pass __tablename__ = 'bib95x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib95x(db.Model): """Represents a BibrecBib95x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib95x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib95x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib95xs') bibxxx = db.relationship(Bib95x, backref='bibrecs') class Bib96x(db.Model): """Represents a Bib96x record.""" def __init__(self): pass __tablename__ = 'bib96x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib96x(db.Model): """Represents a BibrecBib96x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib96x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib96x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib96xs') bibxxx = db.relationship(Bib96x, backref='bibrecs') class Bib97x(db.Model): """Represents a Bib97x record.""" def __init__(self): pass __tablename__ = 'bib97x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib97x(db.Model): """Represents a BibrecBib97x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib97x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib97x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib97xs') bibxxx = db.relationship(Bib97x, backref='bibrecs') class Bib98x(db.Model): """Represents a Bib98x record.""" def __init__(self): pass __tablename__ = 'bib98x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib98x(db.Model): """Represents a BibrecBib98x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib98x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib98x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib98xs') bibxxx = db.relationship(Bib98x, backref='bibrecs') class Bib99x(db.Model): """Represents a Bib99x record.""" def __init__(self): pass __tablename__ = 'bib99x' id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, autoincrement=True) tag = db.Column(db.String(6), nullable=False, index=True, server_default='') value = db.Column(db.Text(35), nullable=False, index=True) class BibrecBib99x(db.Model): """Represents a BibrecBib99x record.""" def __init__(self): pass __tablename__ = 'bibrec_bib99x' id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True, index=True, server_default='0') id_bibxxx = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bib99x.id), nullable=False, primary_key=True, index=True, server_default='0') field_number = db.Column(db.SmallInteger(5, unsigned=True), primary_key=True) bibrec = db.relationship(Bibrec, backref='bib99xs') bibxxx = db.relationship(Bib99x, backref='bibrecs') __all__ = ['Bibrec', 'Bibfmt', 'BibHOLDINGPEN', 'Bibdoc', 'BibdocBibdoc', 'BibrecBibdoc', 'HstDOCUMENT', 'HstRECORD', 'Bib00x', 'BibrecBib00x', 'Bib01x', 'BibrecBib01x', 'Bib02x', 'BibrecBib02x', 'Bib03x', 'BibrecBib03x', 'Bib04x', 'BibrecBib04x', 'Bib05x', 'BibrecBib05x', 'Bib06x', 'BibrecBib06x', 'Bib07x', 'BibrecBib07x', 'Bib08x', 'BibrecBib08x', 'Bib09x', 'BibrecBib09x', 'Bib10x', 'BibrecBib10x', 'Bib11x', 'BibrecBib11x', 'Bib12x', 'BibrecBib12x', 'Bib13x', 'BibrecBib13x', 'Bib14x', 'BibrecBib14x', 'Bib15x', 'BibrecBib15x', 'Bib16x', 'BibrecBib16x', 'Bib17x', 'BibrecBib17x', 'Bib18x', 'BibrecBib18x', 'Bib19x', 'BibrecBib19x', 'Bib20x', 'BibrecBib20x', 'Bib21x', 'BibrecBib21x', 'Bib22x', 'BibrecBib22x', 'Bib23x', 'BibrecBib23x', 'Bib24x', 'BibrecBib24x', 'Bib25x', 'BibrecBib25x', 'Bib26x', 'BibrecBib26x', 'Bib27x', 'BibrecBib27x', 'Bib28x', 'BibrecBib28x', 'Bib29x', 'BibrecBib29x', 'Bib30x', 'BibrecBib30x', 'Bib31x', 'BibrecBib31x', 'Bib32x', 'BibrecBib32x', 'Bib33x', 'BibrecBib33x', 'Bib34x', 'BibrecBib34x', 'Bib35x', 'BibrecBib35x', 'Bib36x', 'BibrecBib36x', 'Bib37x', 'BibrecBib37x', 'Bib38x', 'BibrecBib38x', 'Bib39x', 'BibrecBib39x', 'Bib40x', 'BibrecBib40x', 'Bib41x', 'BibrecBib41x', 'Bib42x', 'BibrecBib42x', 'Bib43x', 'BibrecBib43x', 'Bib44x', 'BibrecBib44x', 'Bib45x', 'BibrecBib45x', 'Bib46x', 'BibrecBib46x', 'Bib47x', 'BibrecBib47x', 'Bib48x', 'BibrecBib48x', 'Bib49x', 'BibrecBib49x', 'Bib50x', 'BibrecBib50x', 'Bib51x', 'BibrecBib51x', 'Bib52x', 'BibrecBib52x', 'Bib53x', 'BibrecBib53x', 'Bib54x', 'BibrecBib54x', 'Bib55x', 'BibrecBib55x', 'Bib56x', 'BibrecBib56x', 'Bib57x', 'BibrecBib57x', 'Bib58x', 'BibrecBib58x', 'Bib59x', 'BibrecBib59x', 'Bib60x', 'BibrecBib60x', 'Bib61x', 'BibrecBib61x', 'Bib62x', 'BibrecBib62x', 'Bib63x', 'BibrecBib63x', 'Bib64x', 'BibrecBib64x', 'Bib65x', 'BibrecBib65x', 'Bib66x', 'BibrecBib66x', 'Bib67x', 'BibrecBib67x', 'Bib68x', 'BibrecBib68x', 'Bib69x', 'BibrecBib69x', 'Bib70x', 'BibrecBib70x', 'Bib71x', 'BibrecBib71x', 'Bib72x', 'BibrecBib72x', 'Bib73x', 'BibrecBib73x', 'Bib74x', 'BibrecBib74x', 'Bib75x', 'BibrecBib75x', 'Bib76x', 'BibrecBib76x', 'Bib77x', 'BibrecBib77x', 'Bib78x', 'BibrecBib78x', 'Bib79x', 'BibrecBib79x', 'Bib80x', 'BibrecBib80x', 'Bib81x', 'BibrecBib81x', 'Bib82x', 'BibrecBib82x', 'Bib83x', 'BibrecBib83x', 'Bib84x', 'BibrecBib84x', 'Bib85x', 'BibrecBib85x', 'Bib86x', 'BibrecBib86x', 'Bib87x', 'BibrecBib87x', 'Bib88x', 'BibrecBib88x', 'Bib89x', 'BibrecBib89x', 'Bib90x', 'BibrecBib90x', 'Bib91x', 'BibrecBib91x', 'Bib92x', 'BibrecBib92x', 'Bib93x', 'BibrecBib93x', 'Bib94x', 'BibrecBib94x', 'Bib95x', 'BibrecBib95x', 'Bib96x', 'BibrecBib96x', 'Bib97x', 'BibrecBib97x', 'Bib98x', 'BibrecBib98x', 'Bib99x', 'BibrecBib99x'] diff --git a/invenio/modules/records/api.py b/invenio/modules/records/api.py new file mode 100644 index 000000000..975b8b44b --- /dev/null +++ b/invenio/modules/records/api.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +""" + invenio.modules.records.api + --------------------------- + +""" + +from invenio.modules.jsonalchemy.jsonext.engines.sqlalchemy import SQLAlchemyStorage +from invenio.modules.jsonalchemy.parser import FieldParser +from invenio.modules.jsonalchemy.registry import readers +from invenio.modules.jsonalchemy.wrappers import SmartJson + +from .models import RecordMetadata as RecordMetadataModel +from .models import Record as RecordModel + + +class Record(SmartJson): + """ + Default/Base record class + """ + + storage_engine = SQLAlchemyStorage(RecordMetadataModel) + + @classmethod + def create(cls, blob, master_format, **kwargs): + reader = readers[master_format](blob, namespace='recordext', **kwargs) + return cls(reader.translate()) + + @classmethod + def get_record(cls, recid): + try: + json = cls.storage_engine.get_one(recid) + return Record(json) + except: + # try to retrieve the record from the master format if any + # this might be deprecated in the near future as soon as json will + # become the master format, until then ... + blob = cls.get_blob(recid) + record_model = RecordModel.query.get(recid) + if record_model is None or blob is None: + return None + additional_info = record_model.additional_info \ + if record_model.additional_info else {'master_format': 'marc'} + record = cls.create(blob, record_model.master_format, **additional_info) + record._save() + record_model.additional_info = \ + record.get('__meta_metadata__.__additional_info__', {'master_format': 'marc'}) + record_model.commit() + return record + + @staticmethod + def get_blob(recid): + #FIXME: start using bibarchive or bibingest for this + from invenio.modules.formatter.models import Bibfmt + from zlib import decompress + + record_blob = Bibfmt.query.get((recid, 'xm')) + if record_blob is None: + return None + return decompress(record_blob.value) + + def _save(self): + self.storage_engine.save_one(self.dumps()) + +# Functional interface +create_record = Record.create +get_record = Record.get_record + + + + + + + + + + + + diff --git a/invenio/modules/records/models.py b/invenio/modules/records/models.py new file mode 100644 index 000000000..8802c687c --- /dev/null +++ b/invenio/modules/records/models.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2014 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. + +""" + invenio.modules.record.models + ----------------------------- +""" + +from invenio.ext.sqlalchemy import db +from flask import current_app +from werkzeug import cached_property + +class Record(db.Model): + """Represents a record object inside the SQL database""" + + __tablename__ = 'bibrec' + + id = db.Column(db.MediumInteger(8, unsigned=True), primary_key=True, + nullable=False, autoincrement=True) + creation_date = db.Column(db.DateTime, nullable=False, + server_default='1900-01-01 00:00:00', + index=True) + modification_date = db.Column(db.DateTime, nullable=False, + server_default='1900-01-01 00:00:00', + index=True) + master_format = db.Column(db.String(16), nullable=False, + server_default='marc') + additional_info = db.Column(db.JSON) + + #FIXME: remove this from the model and add them to the record class, all? + + @property + def deleted(self): + """ + Return True if record is marked as deleted. + """ + from invenio.legacy.bibrecord import get_fieldvalues + # record exists; now check whether it isn't marked as deleted: + dbcollids = get_fieldvalues(self.id, "980__%") + + return ("DELETED" in dbcollids) or \ + (current_app.config.get('CFG_CERN_SITE') + and "DUMMY" in dbcollids) + + @staticmethod + def _next_merged_recid(recid): + """ Returns the ID of record merged with record with ID = recid """ + from invenio.legacy.bibrecord import get_fieldvalues + merged_recid = None + for val in get_fieldvalues(recid, "970__d"): + try: + merged_recid = int(val) + break + except ValueError: + pass + + if not merged_recid: + return None + else: + return merged_recid + + @cached_property + def merged_recid(self): + """ Return the record object with + which the given record has been merged. + @param recID: deleted record recID + @type recID: int + @return: merged record recID + @rtype: int or None + """ + return Record._next_merged_recid(self.id) + + @property + def merged_recid_final(self): + """ Returns the last record from hierarchy of + records merged with this one """ + + cur_id = self.id + next_id = Record._next_merged_recid(cur_id) + + while next_id: + cur_id = next_id + next_id = Record._next_merged_recid(cur_id) + + return cur_id + + @cached_property + def is_restricted(self): + """Returns True is record is restricted.""" + from invenio.legacy.search_engine import get_restricted_collections_for_recid + + if get_restricted_collections_for_recid(self.id, + recreate_cache_if_needed=False): + return True + elif self.is_processed: + return True + return False + + @cached_property + def is_processed(self): + """Returns True is recods is processed (not in any collection).""" + from invenio.legacy.search_engine import is_record_in_any_collection + return not is_record_in_any_collection(self.id, + recreate_cache_if_needed=False) + + +class RecordMetadata(db.Model): + """Represents a json record inside the SQL database""" + + __tablename__ = 'record_metadata' + + id = db.Column(db.MediumInteger(8, unsigned=True), + db.ForeignKey(Record.id), + primary_key=True, + nullable=False, + autoincrement=True) + json = db.Column(db.MarshalBinary(default_value={}, force_type=dict), + nullable=False) + + record = db.relationship(Record, backref='record_metadata') + +__all__ = ['Record', + 'RecordMetadata', + ] diff --git a/invenio/core/record/backends/__init__.py b/invenio/modules/records/recordext/__init__.py similarity index 100% rename from invenio/core/record/backends/__init__.py rename to invenio/modules/records/recordext/__init__.py diff --git a/invenio/modules/records/recordext/fields/base.cfg b/invenio/modules/records/recordext/fields/base.cfg new file mode 100644 index 000000000..91bd19cf9 --- /dev/null +++ b/invenio/modules/records/recordext/fields/base.cfg @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 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. + +@persistent_identifier(0) +_id: + """ + This is the main persistent identifier of th record and will be + used internally as this, therefore the pid weight must always be '0'. + + """ + schema: + {'_id': {'type':'integer', 'min': 1}} + creator: + @legacy(('001', ''), ) + marc, '001', int(value) + producer: + json_for_marc(), {'001': ''} + +@persistent_identifier(0) +recid: + """ + Hard link to ```_id``` + """ + derived: + @depends_on(('_id', )) + self['_id'] + +modification_date: + """ Modification date """ + schema: + {'modification_date': {'type': 'datetime', 'required': True, 'default': lambda: __import__('datetime').datetime.now()}} + creator: + @legacy(('005', ''),) + marc, '005', datetime.datetime(*(time.strptime(value, "%Y%m%d%H%M%S.0")[0:6])) + json: + dumps, lambda d: d.isoformat() + loads, lambda d: __import__('datetime').datetime.strptime(d, "%Y-%m-%dT%H:%M:%S") + +creation_date: + """ Creation date """ + schema: + {'creation_date': {'type': 'datetime', 'required': True, 'default': lambda: __import__('datetime').datetime.now()}} + json: + dumps, lambda d: d.isoformat() + loads, lambda d: __import__('datetime').datetime.strptime(d, "%Y-%m-%dT%H:%M:%S") diff --git a/invenio/core/record/recordext/__init__.py b/invenio/modules/records/recordext/functions/__init__.py similarity index 96% rename from invenio/core/record/recordext/__init__.py rename to invenio/modules/records/recordext/functions/__init__.py index eb537e874..0eab0f888 100644 --- a/invenio/core/record/recordext/__init__.py +++ b/invenio/modules/records/recordext/functions/__init__.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. -## Copyright (C) 2013 CERN. +## Copyright (C) 2014 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. diff --git a/invenio/core/record/recordext/models/common.cfg b/invenio/modules/records/recordext/models/base.cfg similarity index 84% rename from invenio/core/record/recordext/models/common.cfg rename to invenio/modules/records/recordext/models/base.cfg index 3df2b52be..86379c1e6 100644 --- a/invenio/core/record/recordext/models/common.cfg +++ b/invenio/modules/records/recordext/models/base.cfg @@ -1,32 +1,31 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. +""" + invenio.modules.records.model.record_base + ----------------------------------------- + + Common fields in all the records inside any Invenio installation. +""" + fields: recid _id modification_date - _modification_date creation_date -documentation: - """ - Common fields in all the records inside any Invenio installation - - See: invenio.core.record.recordext.corefields for a better explanation about - the purpose of this fields. - """ diff --git a/invenio/modules/records/testsuite/deprecated_test_bibfield_config_engine.py b/invenio/modules/records/testsuite/deprecated_test_bibfield_config_engine.py deleted file mode 100644 index 44e02efaf..000000000 --- a/invenio/modules/records/testsuite/deprecated_test_bibfield_config_engine.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2012, 2013 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. - -""" -BibFieldParser Unit tests. -""" -#FIXME: Not working now! A new version of the config parser its out there! - -from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase - - -class BibFieldParserUnitTests(InvenioTestCase): - """ - Test to verify the correct creation of bibfield_config.py from the rules and - doctypes files. - """ - def setUp(self): - """Loads bibfield configuration test files""" - super(BibFieldParserUnitTests, self).setUp() - from invenio.legacy.bibfield.config_engine import BibFieldParser - parser = BibFieldParser(main_config_file="test_bibfield.cfg") - self.config_rules = parser.config_rules - - def test_bibfield_rules_parser(self): - """BibField - configuration rules building process""" - self.assertTrue(len(self.config_rules) >= 20) - #Check imports - self.assertTrue('authors' in self.config_rules) - self.assertTrue('title' in self.config_rules) - #Check work arroung for [n] and [0] - self.assertTrue(len(self.config_rules['authors']) == 2) - self.assertEqual(self.config_rules['authors'], ['authors[0]', 'authors[n]']) - self.assertTrue('authors[0]' in self.config_rules) - self.assertTrue('authors[n]' in self.config_rules) - self.assertTrue(self.config_rules['doi']['persistent_identifier']) - #Check if derived and calulated are well parserd - self.assertTrue('dummy' in self.config_rules) - self.assertTrue(self.config_rules['dummy']['type'] == 'derived') - self.assertTrue(self.config_rules['dummy']['persistent_identifier']) - self.assertTrue(self.config_rules['_number_of_copies']['type'] == 'calculated') - self.assertTrue(self.config_rules['authors[0]']['type'] == 'real') - self.assertTrue(self.config_rules['_random']['rules']['do_not_cache']) - self.assertFalse(self.config_rules['_number_of_copies']['rules']['do_not_cache']) - #Check inheritance - self.assertTrue('main_author' in self.config_rules) - self.assertEqual(self.config_rules['main_author']['rules'], - self.config_rules['authors[0]']['rules']) - - def test_bibfield_docytpes_parser(self): - #TODO: next iteration will come with this - pass - - def test_writing_bibfield_config_file(self): - #TODO: tests - pass - - -TEST_SUITE = make_test_suite(BibFieldParserUnitTests) - -if __name__ == "__main__": - run_test_suite(TEST_SUITE) diff --git a/invenio/modules/records/testsuite/deprecated_test_bibfield_utils.py b/invenio/modules/records/testsuite/deprecated_test_bibfield_utils.py deleted file mode 100644 index 5268d3da6..000000000 --- a/invenio/modules/records/testsuite/deprecated_test_bibfield_utils.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2013 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. - -""" -BibFieldUtils Unit tests. -""" - -from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase - - -class BibFieldCoolListDictUnitTests(InvenioTestCase): - """ - Test class to verify the correct behaviour of the classes involved into the - intermediate structure - """ - - def test_cool_list(self): - """Bibfield Utils, CoolList - Unit tests""" - from invenio.legacy.bibfield.utils import CoolList - ll = CoolList() - ll.append(1) - ll.append(2) - ll.append(3) - self.assertFalse(ll.consumed) - ll[1] - self.assertEqual(ll._consumed, [False, True, False]) - self.assertFalse(ll.consumed) - [i for i in ll] - self.assertTrue(ll.consumed) - ll[1] = [4, 5, 6] - self.assertFalse(ll.consumed) - self.assertEqual(ll._consumed, [True, [False, False, False], True]) - [i for i in ll] - self.assertFalse(ll.consumed) - self.assertEqual(ll._consumed, [True, [False, False, False], True]) - ll[1] - self.assertFalse(ll.consumed) - [i for i in ll[1]] - self.assertTrue(ll.consumed) - - def test_cool_dict(self): - """Bibfield Utils, CoolDict - Unit tests""" - from invenio.legacy.bibfield.utils import CoolDict, CoolList - d = CoolDict() - d['a'] = 1 - d['b'] = 2 - d['c'] = 3 - self.assertFalse(d.consumed) - d['a'] - self.assertFalse(d.consumed) - [v for dummy_k, v in d.iteritems()] - self.assertTrue(d.consumed) - d['b'] = {'d': 1} - self.assertFalse(d.consumed) - d['b'] - self.assertFalse(d.consumed) - [v for dummy_k, v in d['b'].iteritems()] - self.assertTrue(d.consumed) - d.extend('a', 11) - self.assertFalse(d.consumed) - self.assertTrue(isinstance(d['a'], CoolList)) - [i for i in d['a']] - self.assertTrue(d.consumed) - - def test_cool_list_and_dict(self): - """Bibfield Utils, CoolList and CoolDict - Unit tests""" - from invenio.legacy.bibfield.utils import CoolDict, CoolList - d = CoolDict() - l = CoolList() - d['a'] = l - self.assertTrue(d.consumed) - l.append(1) - l.append(2) - d['a'] = l - self.assertFalse(d.consumed) - d['b'] = CoolList([{'a': 1}, {'a': 2}]) - self.assertFalse(d.consumed) - [v for dummy_k, v in d.iteritems()] - self.assertFalse(d.consumed) - [i for i in d['a']] - [v for i in d['b'] for dummy_k, v in i.iteritems()] - self.assertTrue(d.consumed) - - -class BibFieldUtilsUnitTests(InvenioTestCase): - """ - Test class for bibfield utilities - """ - - def test_prepare_field_keys(self): - """BibField Utils, prepare_field_keys - Unit Test""" - from invenio.legacy.bibfield.utils import prepare_field_keys - key = 'authors' - self.assertEqual(prepare_field_keys(key), ['["authors"]']) - self.assertEqual(prepare_field_keys(key, write=True), ['["authors"]']) - key = 'authors[0]' - self.assertEqual(prepare_field_keys(key), ['["authors"][0]']) - self.assertEqual(prepare_field_keys(key, True), ['["authors"]', '[0]']) - key = 'authors[n]' - self.assertEqual(prepare_field_keys(key), ['["authors"][-1]']) - self.assertEqual(prepare_field_keys(key, True), ['["authors"]', '[-1]']) - key = 'authors.ln' - self.assertEqual(prepare_field_keys(key), ['["authors"]', '["ln"]']) - self.assertEqual(prepare_field_keys(key, True), ['["authors"]', '["ln"]']) - - key = 'a[1].b[0].c.d[n]' - self.assertEqual(prepare_field_keys(key), ['["a"][1]', '["b"][0]', '["c"]', '["d"][-1]']) - self.assertEqual(prepare_field_keys(key, True), ['["a"]', '[1]', '["b"]', '[0]', '["c"]', '["d"]', '[-1]']) - - def test_build_data_structure(self): - """BibField Utils, build_data_structure - Unit Test""" - from invenio.legacy.bibfield.utils import build_data_structure - d = dict() - build_data_structure(d, 'authors') - self.assertEqual(d, {'authors': None}) - build_data_structure(d, 'authors[0]') - self.assertEqual(d, {'authors': [None]}) - build_data_structure(d, 'authors[n]') - self.assertEqual(d, {'authors': [None, None]}) - - d = dict() - build_data_structure(d, 'a[0].b[n].c.d[n]') - self.assertEqual(d, {'a': [{'b': [{'c': {'d': [None]}}]}]}) - - -class BibFieldDictUnitTest(InvenioTestCase): - """ - Test class for bibfield base dictionary - """ - - def test_bibfielddict(self): - """BibFieldDict - Unit Test""" - import random - from invenio.legacy.bibfield.utils import BibFieldDict - d = BibFieldDict() - d['foo'] = {'a': 'world', 'b': 'hello'} - d['a'] = [{'b': 1}, {'b': 2}, {'b': 3}] - d['_c'] = 1 - d['_cc'] = random.random() - - d['__do_not_cache'].append('_cc') - d['__calculated_functions']['_c'] = "random.random()" - d['__calculated_functions']['_cc'] = "random.random()" - d['__aliases']['aa'] = 'a' - - self.assertTrue(len(d.keys()) == 7) - self.assertTrue('foo' in d) - self.assertTrue('a.b' in d) - - self.assertEqual(d['foo'], {'a': 'world', 'b': 'hello'}) - self.assertEqual(d['a'], d.get('a')) - self.assertEqual(d['a[-1].b'], 3) - - self.assertEqual(d['a'], d['aa']) - self.assertEqual(d['a[1].b'], d['aa[1].b']) - - self.assertEqual(d['_c'], 1) - self.assertNotEqual(d['_c'], d.get('_c', reset_cache=True)) - - self.assertNotEqual(d['_cc'], 1) - self.assertNotEqual(d['_cc'], d.get('_cc')) - - #Python 2.5 or higher - #self.assertEqual('hello world!', d.get('foo', formatstring="{0[b]} {0[a]}!")) - - def dummy(s): - return s.upper() - self.assertEqual('HELLO', d.get('foo.b', formatfunction=dummy)) - - -TEST_SUITE = make_test_suite(BibFieldCoolListDictUnitTests, BibFieldUtilsUnitTests, BibFieldDictUnitTest) - -if __name__ == "__main__": - run_test_suite(TEST_SUITE) diff --git a/invenio/modules/records/views.py b/invenio/modules/records/views.py index 33ba35186..285cacfd1 100644 --- a/invenio/modules/records/views.py +++ b/invenio/modules/records/views.py @@ -1,232 +1,232 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 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. """WebSearch Flask Blueprint""" from functools import wraps from flask import g, render_template, request, flash, redirect, url_for, \ current_app, abort, Blueprint from flask.ext.login import current_user from invenio.base.decorators import wash_arguments from invenio.base.globals import cfg from invenio.config import CFG_SITE_RECORD from invenio.ext.template.context_processor import \ register_template_context_processor from invenio.modules.search.models import Collection from invenio.modules.search.signals import record_viewed -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.base.i18n import _ from invenio.utils import apache from flask.ext.breadcrumbs import default_breadcrumb_root blueprint = Blueprint('record', __name__, url_prefix="/" + CFG_SITE_RECORD, static_url_path='/record', template_folder='templates', static_folder='static') default_breadcrumb_root(blueprint, '.') def request_record(f): @wraps(f) def decorated(recid, *args, **kwargs): from invenio.modules.access.mailcookie import \ mail_cookie_create_authorize_action from invenio.modules.access.local_config import VIEWRESTRCOLL from invenio.legacy.search_engine import guess_primary_collection_of_a_record, \ check_user_can_view_record from invenio.legacy.websearch.adminlib import get_detailed_page_tabs,\ get_detailed_page_tabs_counts # ensure recid to be integer recid = int(recid) g.collection = collection = Collection.query.filter( Collection.name == guess_primary_collection_of_a_record(recid)).\ one() (auth_code, auth_msg) = check_user_can_view_record(current_user, recid) # only superadmins can use verbose parameter for obtaining debug information if not current_user.is_super_admin and 'verbose' in kwargs: kwargs['verbose'] = 0 if auth_code and current_user.is_guest: cookie = mail_cookie_create_authorize_action(VIEWRESTRCOLL, { 'collection': g.collection.name}) url_args = {'action': cookie, 'ln': g.ln, 'referer': request.url} flash(_("Authorization failure"), 'error') return redirect(url_for('webaccount.login', **url_args)) elif auth_code: flash(auth_msg, 'error') abort(apache.HTTP_UNAUTHORIZED) - from invenio.legacy.bibfield import get_record + from invenio.modules.records.api import get_record from invenio.legacy.search_engine import record_exists, get_merged_recid # check if the current record has been deleted # and has been merged, case in which the deleted record # will be redirect to the new one record_status = record_exists(recid) merged_recid = get_merged_recid(recid) if record_status == -1 and merged_recid: return redirect(url_for('record.metadata', recid=merged_recid)) elif record_status == -1: abort(apache.HTTP_GONE) # The record is gone! g.bibrec = Bibrec.query.get(recid) record = get_record(recid) if record is None: return render_template('404.html') title = record.get('title.title', '') # b = [(_('Home'), '')] + collection.breadcrumbs()[1:] # b += [(title, 'record.metadata', dict(recid=recid))] # current_app.config['breadcrumbs_map'][request.endpoint] = b g.record_tab_keys = [] tabs = [] counts = get_detailed_page_tabs_counts(recid) for k, v in get_detailed_page_tabs(collection.id, recid, g.ln).iteritems(): t = {} b = 'record' if k == '': k = 'metadata' if k == 'comments' or k == 'reviews': b = 'comments' if k == 'linkbacks': b = 'weblinkback' k = 'index' t['key'] = b + '.' + k t['count'] = counts.get(k.capitalize(), -1) t.update(v) tabs.append(t) if v['visible']: g.record_tab_keys.append(b+'.'+k) if cfg.get('CFG_WEBLINKBACK_TRACKBACK_ENABLED'): @register_template_context_processor def trackback_context(): from invenio.legacy.weblinkback.templates import get_trackback_auto_discovery_tag return dict(headerLinkbackTrackbackLink=get_trackback_auto_discovery_tag(recid)) def _format_record(recid, of='hd', user_info=current_user, *args, **kwargs): from invenio.legacy.search_engine import print_record return print_record(recid, format=of, user_info=user_info, *args, **kwargs) @register_template_context_processor def record_context(): from invenio.modules.comments.api import get_mini_reviews return dict(recid=recid, record=record, tabs=tabs, title=title, get_mini_reviews=lambda *args, **kwargs: get_mini_reviews(*args, **kwargs).decode('utf8'), collection=collection, format_record=_format_record ) return f(recid, *args, **kwargs) return decorated @blueprint.route('//metadata', methods=['GET', 'POST']) @blueprint.route('//', methods=['GET', 'POST']) @blueprint.route('/', methods=['GET', 'POST']) @wash_arguments({'of': (unicode, 'hd')}) @request_record def metadata(recid, of='hd'): from invenio.legacy.bibrank.downloads_similarity import register_page_view_event from invenio.modules.formatter import get_output_format_content_type register_page_view_event(recid, current_user.get_id(), str(request.remote_addr)) if get_output_format_content_type(of) != 'text/html': return redirect('/%s/%d/export/%s' % (CFG_SITE_RECORD, recid, of)) # Send the signal 'document viewed' record_viewed.send( current_app._get_current_object(), recid=recid, id_user=current_user.get_id(), request=request) return render_template('records/metadata.html', of=of) @blueprint.route('//references', methods=['GET', 'POST']) @request_record def references(recid): return render_template('records/references.html') @blueprint.route('//files', methods=['GET', 'POST']) @request_record def files(recid): def get_files(): from invenio.legacy.bibdocfile.api import BibRecDocs for bibdoc in BibRecDocs(recid).list_bibdocs(): for file in bibdoc.list_all_files(): yield file.get_url() return render_template('records/files.html', files=list(get_files())) @blueprint.route('//citations', methods=['GET', 'POST']) @request_record def citations(recid): from invenio.legacy.bibrank.citation_searcher import calculate_cited_by_list,\ get_self_cited_by, calculate_co_cited_with_list citations = dict( citinglist=calculate_cited_by_list(recid), selfcited=get_self_cited_by(recid), co_cited=calculate_co_cited_with_list(recid) ) return render_template('records/citations.html', citations=citations) @blueprint.route('//keywords', methods=['GET', 'POST']) @request_record def keywords(recid): from invenio.legacy.bibclassify.webinterface import record_get_keywords found, keywords, record = record_get_keywords(recid) return render_template('records/keywords.html', found=found, keywords=keywords) @blueprint.route('//usage', methods=['GET', 'POST']) @request_record def usage(recid): from invenio.legacy.bibrank.downloads_similarity import calculate_reading_similarity_list from invenio.legacy.bibrank.downloads_grapher import create_download_history_graph_and_box viewsimilarity = calculate_reading_similarity_list(recid, "pageviews") downloadsimilarity = calculate_reading_similarity_list(recid, "downloads") downloadgraph = create_download_history_graph_and_box(recid) return render_template('records/usage.html', viewsimilarity=viewsimilarity, downloadsimilarity=downloadsimilarity, downloadgraph=downloadgraph) @blueprint.route('/', methods=['GET', 'POST']) def no_recid(): return redirect("/") diff --git a/invenio/modules/sword/models.py b/invenio/modules/sword/models.py index 569bc3117..115a416ab 100644 --- a/invenio/modules/sword/models.py +++ b/invenio/modules/sword/models.py @@ -1,96 +1,96 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2011, 2012 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 02D111-1307, USA. """ bibsword database models. """ # General imports. from invenio.ext.sqlalchemy import db # Create your models here. from invenio.modules.accounts.models import User -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec class SwrREMOTESERVER(db.Model): """Represents a SwrREMOTESERVER record.""" def __init__(self): pass __tablename__ = 'swrREMOTESERVER' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) name = db.Column(db.String(50), nullable=False, unique=True) host = db.Column(db.String(50), nullable=False) username = db.Column(db.String(50), nullable=False) password = db.Column(db.String(50), nullable=False) email = db.Column(db.String(50), nullable=False) realm = db.Column(db.String(50), nullable=False) url_base_record = db.Column(db.String(50), nullable=False) url_servicedocument = db.Column(db.String(80), nullable=False) xml_servicedocument = db.Column(db.LargeBinary, nullable=True) last_update = db.Column(db.Integer(15, unsigned=True), nullable=False) class SwrCLIENTDATA(db.Model): """Represents a SwrCLIENTDATA record.""" def __init__(self): pass __tablename__ = 'swrCLIENTDATA' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_swrREMOTESERVER = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(SwrREMOTESERVER.id), nullable=False) id_record = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False) report_no = db.Column(db.String(50), nullable=False) id_remote = db.Column(db.String(50), nullable=False) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False) user_name = db.Column(db.String(100), nullable=False) user_email = db.Column(db.String(100), nullable=False) xml_media_deposit = db.Column(db.LargeBinary, nullable=False) xml_metadata_submit = db.Column(db.LargeBinary, nullable=False) submission_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') publication_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') removal_date = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') link_medias = db.Column(db.String(150), nullable=False) link_metadata = db.Column(db.String(150), nullable=False) link_status = db.Column(db.String(150), nullable=False) status = db.Column(db.String(150), nullable=False, server_default='submitted') last_update = db.Column(db.DateTime, nullable=False) remoteserver = db.relationship(SwrREMOTESERVER, backref='clientdata') user = db.relationship(User, backref='clientdata') bibrec = db.relationship(Bibrec) __all__ = ['SwrREMOTESERVER', 'SwrCLIENTDATA'] diff --git a/invenio/modules/tags/forms.py b/invenio/modules/tags/forms.py index fc09c8108..856c97658 100644 --- a/invenio/modules/tags/forms.py +++ b/invenio/modules/tags/forms.py @@ -1,277 +1,277 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """WebTag Forms""" from invenio.base.i18n import _ from invenio.base.globals import cfg from invenio.utils.forms import InvenioBaseForm from flask.ext.login import current_user from wtforms import \ IntegerField, \ BooleanField, \ SelectField, \ HiddenField, \ TextField, \ SelectMultipleField, \ SelectField, \ validators #Models -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.accounts.models import User, Usergroup, UserUsergroup # Internal from invenio.ext.sqlalchemy import db from .models import \ WtgTAG, \ WtgTAGRecord, \ wash_tag_silent, \ wash_tag_blocking def validate_tag_name(dummy_form, field): """ Check validity of tag name """ max_len = cfg['CFG_TAGS_NAME_MAX_LENGTH'] max_char = cfg['CFG_TAGS_MAX_CHARACTER'] if field.data: suggested_silent = wash_tag_silent(field.data) suggested = wash_tag_blocking(suggested_silent) field.data = suggested_silent if suggested != suggested_silent: raise validators.ValidationError( _('Forbidden characters. Try ') + suggested + '.') if len(suggested) <= 0: raise validators.ValidationError( _('The name must contain valid characters.')) if len(suggested_silent) > max_len: raise validators.ValidationError( _('The name cannot exeed ') + str(max_len) + _(' characters.')) if max(ord(letter) for letter in suggested_silent) > max_char: raise validators.ValidationError( _('Forbidden character.')) def validate_name_available(dummy_form, field): """ Check if the user already has tag named this way """ if field.data: uid = current_user.get_id() copy_count = db.session.query(WtgTAG).\ filter_by(id_user=uid, name=field.data).count() if copy_count > 0: raise validators.ValidationError( _('Tag with that name already exists.')) def validate_tag_exists(dummy_form, field): """ Check if id_tag matches a tag in database """ if field.data: try: field.data = int(field.data) except ValueError: raise validators.ValidationError(_('Tag ID must be an integer.')) if not db.session.query(WtgTAG).get(field.data): raise validators.ValidationError(_('Tag does not exist.')) def validate_user_owns_tag(dummy_form, field): """ Check if id_tag matches a tag in database """ if field.data: tag = db.session.query(WtgTAG).get(field.data) if tag and tag.id_user != current_user.get_id(): raise validators.ValidationError( _('You are not the owner of this tag.')) def validate_bibrec_exists(dummy_form, field): """ Check if id_bibrec matches a bibrec in database """ if field.data: try: field.data = int(field.data) except ValueError: raise validators.ValidationError(_('Bibrec ID must be an integer.')) record = db.session.query(Bibrec).get(field.data) if (not record): raise validators.ValidationError(_('Bibrec does not exist.')) # Switch to merged record if present merged_id = record.merged_recid_final if merged_id != record.id: record = db.session.query(Bibrec).get(merged_id) field.data = merged_id if record.deleted: raise validators.ValidationError(_('Bibrec has been deleted.')) def validate_user_can_see_bibrec(dummy_form, field): """ Check if user has rights to view bibrec """ if field.data: from invenio.legacy.search_engine import check_user_can_view_record (auth_code, msg) = check_user_can_view_record(current_user, field.data) if auth_code > 0: raise validators.ValidationError( _('Unauthorized to view record: ')+msg) def validate_not_already_attached(form, dummy_field): """ Check if the pair (tag, bibrec) is already connected """ if form: if ('id_tag' in form.data) and ('id_bibrec' in form.data): tag_record = db.session.query(WtgTAGRecord)\ .get((form.data['id_tag'], form.data['id_bibrec'])) if tag_record is not None: raise validators.ValidationError(_('Tag already attached.')) def validate_already_attached(form, dummy_field): """ Check if the pair (tag, bibrec) is already connected """ if form: if ('id_tag' in form.data) and ('id_bibrec' in form.data): tag_record = db.session.query(WtgTAGRecord)\ .get((form.data['id_tag'], form.data['id_bibrec'])) if tag_record is None: raise validators.ValidationError(_('Tag not attached.')) class CreateTagForm(InvenioBaseForm): """Defines form for creating a new tag.""" name = TextField(_('Name'), [validators.Required(), validate_tag_name, validate_name_available]) # Ajax requests only: # Send a record ID if the tag should be attached to the record # right after creation id_bibrec = HiddenField('Tagged record', [validate_bibrec_exists, validate_user_can_see_bibrec]) class DeleteTagForm(InvenioBaseForm): """Defines form for deleting a tag.""" id_tag = SelectMultipleField('Tag ID', [validators.Required(), validate_tag_exists, validate_user_owns_tag]) class AttachTagForm(InvenioBaseForm): """Defines a form validating attaching a tag to record""" # Ajax requests only: id_tag = IntegerField('Tag ID', [validators.Required(), validate_tag_exists, validate_not_already_attached, validate_user_owns_tag]) # validate user rights on tag id_bibrec = IntegerField('Record ID', [validate_bibrec_exists, validate_user_can_see_bibrec]) class DetachTagForm(InvenioBaseForm): """Defines a form validating detaching a tag from record""" # Ajax requests only: id_tag = IntegerField('Tag ID', [validators.Required(), validate_tag_exists, validate_already_attached, validate_user_owns_tag]) # validate user rights on tag id_bibrec = IntegerField('Record ID', [validators.Required(), validate_bibrec_exists, validate_user_can_see_bibrec]) class TagAnnotationForm(InvenioBaseForm): """Defines a form validating attaching a tag to record""" # Ajax requests only: id_tag = IntegerField('Tag ID', [validators.Required(), validate_tag_exists, validate_already_attached, validate_user_owns_tag]) # validate user rights on tag id_bibrec = IntegerField('Record ID', [validate_bibrec_exists, validate_user_can_see_bibrec]) annotation_value = TextField('Annotation') class GetGroupOptions(object): def __iter__(self): id_user = current_user.get_id() options = [('0', _('Private'))] options += db.session.query(Usergroup.id, Usergroup.name)\ .join(UserUsergroup)\ .filter(UserUsergroup.id_user == id_user)\ .all() for (gid, name) in options: yield (str(gid), name) class EditTagForm(InvenioBaseForm): """Defines form for editing an existing tag.""" name = TextField(_('Name'), [validators.Required(), validate_tag_name]) id_usergroup = SelectField( _('Group sharing options'), choices=GetGroupOptions()) group_access_rights = SelectField( _('Group access rights'), choices=[ (str(WtgTAG.ACCESS_LEVELS['View']), 'View'), (str(WtgTAG.ACCESS_LEVELS['Add and remove']), 'Attach to documents') ]) class WebTagUserSettingsForm(InvenioBaseForm): """User's personal settings influencing WebTag module""" display_tags = BooleanField(_('Display tags with records')) display_tags_group = BooleanField(_('Show group tags')) display_tags_public = BooleanField(_('Show public tags')) diff --git a/invenio/modules/tags/models.py b/invenio/modules/tags/models.py index 682ab68f5..8104b4f28 100644 --- a/invenio/modules/tags/models.py +++ b/invenio/modules/tags/models.py @@ -1,346 +1,346 @@ # -*- coding: utf-8 -*- # ## This file is part of Invenio. ## Copyright (C) 2013 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. """ WebTag database models. """ # Database from invenio.ext.sqlalchemy import db from sqlalchemy.ext.associationproxy import association_proxy # Related models -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.accounts.models import User, Usergroup # Functions from invenio.base.globals import cfg from werkzeug import cached_property from invenio.utils.text import wash_for_xml from datetime import datetime, date import re class Serializable(object): """Class which can present its fields as dict for json serialization""" # Set of fields which are to be serialized __public__ = set([]) def _serialize_field(self, value): """ Converts value of a field to format suitable for json """ if type(value) in (datetime, date): return value.isoformat() elif hasattr(value, '__iter__'): result = [] for element in value: result.append(self._serialize_field(element)) return result elif Serializable in value.__class__.__bases__: return value.get_public() else: return value def serializable_fields(self, fields=None): """Returns model's fields (__public__) or intersection(fields, __public__) for jsonify""" data = {} keys = self._sa_instance_state.attrs.items() public = set() if self.__public__: public = public.union(self.__public__) if fields: public = public.intersection(fields) for key, field in keys: if key in public: value = self._serialize_field(field.value) if value: data[key] = value return data # # TAG # class WtgTAG(db.Model, Serializable): """ Represents a Tag """ __tablename__ = 'wtgTAG' __public__ = set(['id', 'name', 'id_owner']) # # Access Rights # ACCESS_NAMES = { 0: 'Nothing', 10: 'View', 20: 'Add', 30: 'Add and remove', 40: 'Manage', } ACCESS_LEVELS = \ dict((v, k) for (k, v) in ACCESS_NAMES.iteritems()) ACCESS_RIGHTS = { 0: [], 10: ['view'], 20: ['view', 'add'], 30: ['view', 'add', 'remove'], 40: ['view', 'add', 'remove', 'edit'], } ACCESS_OWNER_DEFAULT = ACCESS_LEVELS['Manage'] ACCESS_GROUP_DEFAULT = ACCESS_LEVELS['View'] # Primary key id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False, autoincrement=True) # Name name = db.Column(db.String(255), nullable=False, server_default='', index=True) # Owner id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), server_default='0') # Access rights of owner user_access_rights = db.Column(db.Integer(2, unsigned=True), nullable=False, default=ACCESS_OWNER_DEFAULT) # Group # equal to 0 for private tags id_usergroup = db.Column( db.Integer(15, unsigned=True), db.ForeignKey(Usergroup.id), server_default='0') # Group access rights group_access_rights = db.Column( db.Integer(2, unsigned=True), nullable=False, default=ACCESS_GROUP_DEFAULT) # Access rights of everyone public_access_rights = db.Column(db.Integer(2, unsigned=True), nullable=False, default=ACCESS_LEVELS['Nothing']) # Visibility in document description show_in_description = db.Column(db.Boolean, nullable=False, default=True) # Relationships user = db.relationship(User, backref=db.backref('tags', cascade='all')) user_query = db.relationship(User, backref=db.backref('tags_query', cascade='all', lazy='dynamic')) usergroup = db.relationship( Usergroup, backref=db.backref('tags', cascade='all')) # association proxy of "user_keywords" collection # to "keyword" attribute records = association_proxy('records_association', 'bibrec') #Calculated fields @db.hybrid_property def record_count(self): return self.records_association_query.count() @record_count.expression def record_count(cls): return db.select([db.func.count(WtgTAGRecord.id_bibrec)]).\ where(WtgTAGRecord.id_tag == cls.id).\ label('record_count') @db.validates('user_access_rights') @db.validates('group_access_rights') @db.validates('public_access_rights') def validate_user_access_rights(self, key, value): """ Check if the value is among defined levels """ assert value in WtgTAG.ACCESS_NAMES return value # # TAG - RECORD # class WtgTAGRecord(db.Model, Serializable): """ Represents a connection between Tag and Record """ __tablename__ = 'wtgTAG_bibrec' __public__ = set(['id_tag', 'id_bibrec', 'date_added']) # tagTAG.id id_tag = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(WtgTAG.id), nullable=False, primary_key=True) # Bibrec.id id_bibrec = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, primary_key=True) # Annotation annotation = db.Column( db.Text(convert_unicode=True), default='') # Creation date date_added = db.Column(db.DateTime, default=datetime.now) # Relationships tag = db.relationship(WtgTAG, backref=db.backref('records_association', cascade='all')) tag_query = db.relationship(WtgTAG, backref=db.backref('records_association_query', cascade='all', lazy='dynamic')) bibrec = db.relationship(Bibrec, backref=db.backref('tags_association', cascade='all')) bibrec_query = db.relationship(Bibrec, backref=db.backref('tags_association_query', cascade='all', lazy='dynamic')) # Constructor def __init__(self, bibrec=None, **kwargs): super(WtgTAGRecord, self).__init__(**kwargs) self.bibrec = bibrec # Compiling once should improve regexp speed class ReplacementList(object): def __init__(self, config_name): self.config_name = config_name @cached_property def replacements(self): return cfg.get(self.config_name, []) @cached_property def compiled(self): return [(re.compile(exp), repl) for (exp, repl) in self.replacements] def apply(self, text): """Applies a list of regular expression replacements to a string. :param replacements: list of pairs (compiled_expression, replacement) """ for (reg_exp, replacement) in self.compiled: text = re.sub(reg_exp, replacement, text) return text COMPILED_REPLACEMENTS_SILENT = \ ReplacementList('CFG_TAGS_NAME_REPLACEMENTS_SILENT') COMPILED_REPLACEMENTS_BLOCKING = \ ReplacementList('CFG_TAGS_NAME_REPLACEMENTS_BLOCKING') def wash_tag_silent(tag_name): """ Whitespace and character cleanup. :param tag_name: Single tag. :return: Tag Unicode string with all whitespace characters replaced with Unicode single space (' '), no whitespace at the start and end of the tags, no duplicate whitespace, and only characters valid in XML 1.0. Also applies list of replacements from CFG_WEBTAG_REPLACEMENTS_SILENT. Examples: >>> print(_tag_cleanup('Well formatted string: Should not be changed')) Well formatted string: Should not be changed >>> print(_tag_cleanup('double space characters')) double space characters >>> print(_tag_cleanup('All\\tthe\\ndifferent\\x0bwhitespace\\x0cin\\rone go')) All the different whitespace in one go >>> print(_tag_cleanup(' Preceding whitespace')) Preceding whitespace >>> print(_tag_cleanup('Trailing whitespace ')) Trailing whitespace >>> print(_tag_cleanup(' Preceding and trailing double whitespace ')) Preceding and trailing double whitespace >>> _tag_cleanup(unichr(CFG_WEBTAG_LAST_MYSQL_CHARACTER)) u'' >>> from string import whitespace >>> _tag_cleanup(whitespace) '' """ if tag_name is None: return None # convert to string if type(tag_name) == unicode: tag_name = tag_name.encode('utf-8') # wash_for_xml tag_name = wash_for_xml(tag_name) # replacements tag_name = COMPILED_REPLACEMENTS_SILENT.apply(tag_name) return tag_name def wash_tag_blocking(tag_name): """ Applies list of replacements from CFG_WEBTAG_REPLACEMENTS_BLOCKING """ if tag_name is None: return None # replacements tag_name = COMPILED_REPLACEMENTS_BLOCKING.apply(tag_name) return tag_name def wash_tag(tag_name): """ Applies all washing procedures in order """ return wash_tag_blocking(wash_tag_silent(tag_name)) diff --git a/invenio/modules/tags/user_settings.py b/invenio/modules/tags/user_settings.py index 63c2b689e..d983e0912 100644 --- a/invenio/modules/tags/user_settings.py +++ b/invenio/modules/tags/user_settings.py @@ -1,80 +1,80 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """WebTag User Settings""" # Flask from flask import url_for from invenio.ext.template import render_template_to_string from invenio.base.i18n import _ from flask.ext.login import current_user from invenio.modules.dashboard.settings import \ Settings, \ UserSettingsAttributeStorage # Related models from invenio.modules.accounts.models import User -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec # Internal from .models import WtgTAG, WtgTAGRecord from .forms import WebTagUserSettingsForm class WebTagSettings(Settings): keys = \ [ 'display_tags', 'display_tags_group', 'display_tags_public', ] form_builder = WebTagUserSettingsForm storage_builder = UserSettingsAttributeStorage('webtag') def __init__(self): super(WebTagSettings, self).__init__() self.icon = 'tags' self.title = _('Tags') self.view = url_for('webtag.display_cloud') self.edit = url_for('webaccount.edit', name=self.name) def widget(self): user = User.query.get(current_user.get_id()) tag_count = user.tags_query.count() record_count = Bibrec.query.join(WtgTAGRecord)\ .join(WtgTAG)\ .filter(WtgTAG.user == user).count() return render_template_to_string( 'tags/user_settings.html', tag_count=tag_count, record_count=record_count) widget.size = 4 @property def is_authorized(self): return current_user.is_authenticated() # and current_user.is_authorized('usebaskets') ## Compulsory plugin interface settings = WebTagSettings diff --git a/invenio/modules/tags/views.py b/invenio/modules/tags/views.py index df2d5bb6a..93026d978 100644 --- a/invenio/modules/tags/views.py +++ b/invenio/modules/tags/views.py @@ -1,478 +1,478 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ invenio.modules.tags.blueprint ------------------------------ Tagging interface. """ # Flask from werkzeug import LocalProxy from flask import render_template, request, flash, redirect, url_for, \ jsonify, Blueprint from invenio.base.i18n import _ from invenio.base.decorators import wash_arguments, templated from flask.ext.login import current_user, login_required from invenio.base.globals import cfg # External imports from invenio.modules.accounts.models import User -from invenio.modules.record_editor.models import Bibrec +from invenio.modules.records.models import Record as Bibrec from invenio.modules.search.models import Collection from invenio.modules.search.views.search import response_formated_records from flask.ext.menu import register_menu from flask.ext.breadcrumbs import default_breadcrumb_root, register_breadcrumb from invenio.ext.sqlalchemy import db # Internal imports from .models import \ WtgTAG, \ WtgTAGRecord, \ wash_tag # Forms from .forms import \ CreateTagForm, \ AttachTagForm, \ DetachTagForm, \ EditTagForm, \ TagAnnotationForm, \ validate_tag_exists, \ validate_user_owns_tag, \ validators # Uset settings user_settings = LocalProxy(lambda: current_user['settings'].get('webtag', cfg['CFG_WEBTAG_DEFAULT_USER_SETTINGS'])) blueprint = Blueprint('webtag', __name__, url_prefix='/yourtags', template_folder='templates', static_folder='static') default_breadcrumb_root(blueprint, '.webaccount.tags') @blueprint.route('/', methods=['GET', 'POST']) @blueprint.route('/display', methods=['GET', 'POST']) @blueprint.route('/display/cloud', methods=['GET', 'POST']) @login_required @templated('tags/display_cloud.html') @register_menu(blueprint, 'personalize.tags', _('Your Tags')) @register_breadcrumb(blueprint, '.', _('Your Tags')) def display_cloud(): """ List of user's private/group/public tags """ user = User.query.get(current_user.get_id()) tags = user.tags_query.order_by(WtgTAG.name).all() # Calculate document count for each tag min_count = 0 max_count = 0 for tag in tags: if tag.record_count > max_count: max_count = tag.record_count if tag.record_count < min_count: min_count = tag.record_count difference = float(max_count - min_count) if not difference: difference = 1.0 # Assign sizes min_size = 1.0 max_size = 2.0 for tag in tags: size = min_size + \ float(max_size - min_size) * \ float(tag.record_count - min_count) / difference tag.css_size = str(size*100) return dict(user_tags=tags, display_mode='cloud') @blueprint.route('/display/list', methods=['GET', 'POST']) @login_required @templated('tags/display_list.html') @wash_arguments({'sort_by': (unicode, 'name'), 'order': (unicode, '')}) @register_breadcrumb(blueprint, '.list', _('Display as List')) def display_list(sort_by, order): """ List of user's private/group/public tags """ tags = User.query.get(current_user.get_id()).tags_query sort_by = str(sort_by) order = str(order) if sort_by == 'record_count': tags = tags.order_by(WtgTAG.record_count) else: tags = tags.order_by(WtgTAG.name) tags = tags.all() if order == 'desc': tags.reverse() return dict(user_tags=tags, display_mode='list') @blueprint.route('/tag//records', methods=['GET', 'POST']) @login_required @register_breadcrumb(blueprint, '.tags_details', _('Associated Records')) def tag_details(id_tag): """ List of documents attached to this tag """ if not id_tag: flash(_('Invalid tag id'), "error") return redirect(url_for('.display_cloud')) tag = WtgTAG.query.get(id_tag) if not tag: flash(_('Invalid tag id'), "error") return redirect(url_for('.display_cloud')) if tag.id_user != current_user.get_id(): flash(_('You are not authorized to view this tag'), "error") return redirect(url_for('.display_cloud')) if not tag.records: flash(_('There are no documents tagged with ') + tag.name) return redirect(url_for('.display_cloud')) return response_formated_records([bibrec.id for bibrec in tag.records], Collection.query.get(1), 'hb') @blueprint.route('/tag//edit', methods=['GET', 'POST']) @login_required @templated('tags/record_tags_test.html') # FIXME: tag editor template missing? @register_breadcrumb(blueprint, '.tag_edit', _('Edit tag')) def tag_edit(id_tag): """ List of documents attached to this tag """ id_user = current_user.get_id() tag = WtgTAG.query.get(id_tag) if not tag: flash(_('Invalid tag id'), "error") return redirect(url_for('.display_cloud')) if tag.id_user != id_user: flash(_('You are not authorized to view this tag'), "error") return redirect(url_for('.display_cloud')) form = EditTagForm(request.values, csrf_enabled=False, obj=tag) if form.validate_on_submit(): form.populate_obj(tag) name_count = db.session.query(WtgTAG).\ filter_by(id_user=id_user, name=tag.name).count() if name_count == 1: db.session.add(tag) db.session.commit() flash(_('Tag Successfully edited.'), 'success') else: flash(_('Tag name') + ' ' + tag.name + ' ' + _('is already in use.'), 'error') return dict(tag=tag, form=form) @blueprint.route('/tag//annotations/', methods=['GET', 'POST']) @login_required def update_annotation(id_tag, id_bibrec): """ Change the annotation on relationship between record and tag """ from werkzeug.datastructures import MultiDict values = MultiDict(request.values) values['id_tag'] = id_tag values['id_bibrec'] = id_bibrec form = TagAnnotationForm(values, csrf_enabled=False) if form.validate(): relation = db.session.query(WtgTAGRecord)\ .filter(WtgTAGRecord.id_tag == id_tag)\ .filter(WtgTAGRecord.id_bibrec == id_bibrec)\ .one() relation.annotation = form.data.get('annotation_value', '') db.session.add(relation) db.session.commit() return relation.annotation else: return str(form.errors) @blueprint.route('/record//tags', methods=['GET', 'POST']) @login_required @templated('tags/record_tags_test.html') @register_breadcrumb(blueprint, '.tags_details', _('Tag list')) def record_tags(id_bibrec): """ List of documents attached to this tag """ from .template_context_functions.tfn_webtag_record_tags \ import template_context_function from invenio.ext.template import render_template_to_string return dict( tag_list = template_context_function(id_bibrec, current_user.get_id())) @blueprint.route('/tokenize/', methods=['GET', 'POST']) @login_required @wash_arguments({'q': (unicode, '')}) def tokenize(id_bibrec, q): """ Data for tokeninput """ id_user = current_user.get_id() # Output only tags unattached to this record record = Bibrec.query.get(id_bibrec) tags = WtgTAG.query\ .filter_by(id_user=id_user)\ .filter(WtgTAG.name.like('%' + q + '%'))\ .filter(db.not_(WtgTAG.records.contains(record)))\ .order_by(WtgTAG.name) # If a tag with searched name does not exist, lets suggest creating it # Clean the name new_name = wash_tag(q) add_new_name = True response_tags = [] for tag in tags.all(): tag_json = tag.serializable_fields(set(['id', 'name'])) response_tags.append(tag_json) # Check if it matches the search name if tag_json['name'] == new_name: add_new_name = False #If the name was not found if add_new_name: # Check if a tag with this name is already attached already_attached = WtgTAG.query\ .join(WtgTAGRecord)\ .filter(WtgTAG.name == new_name)\ .filter(WtgTAGRecord.id_bibrec == id_bibrec)\ .count() if not already_attached: tag_json = {'id': 0, 'name': new_name} response_tags.append(tag_json) return jsonify(dict(results=response_tags, query=q)) @blueprint.route('/record//edit', methods=['GET', 'POST']) @login_required def editor(id_bibrec): """Edits your tags for `id_bibrec`. :param id_bibrec: record identifier""" user = db.session.query(User).get(current_user.get_id()) record = db.session.query(Bibrec).get(id_bibrec) tags = db.session.query(WtgTAG)\ .filter_by(user=user)\ .filter(WtgTAG.records.contains(record)) tags_json = [] for tag in tags.all(): fields = tag.serializable_fields(set(['id', 'name'])) fields['can_remove'] = True tags_json.append(fields) # invenio_templated cannot be used, # because this view is requested using AJAX return render_template('tags/record_editor.html', id_bibrec=id_bibrec, record_tags=tags_json) #Temporary solution to call validators, we need a better one class Field(object): def __init__(self, attr, value): setattr(self, attr, value) @blueprint.route('/delete', methods=['GET', 'POST']) @login_required def delete(): """ Delete a tag """ response = {} response['action'] = 'delete' id_tags = request.values.getlist('id_tag', type=int) # Validate for id_tag in id_tags: try: field = Field('data', id_tag) validate_tag_exists(None, field) validate_user_owns_tag(None, field) except validators.ValidationError, ex: flash(ex.message, 'error') for id_tag in id_tags: tag = WtgTAG.query.get(id_tag) db.session.delete(tag) db.session.commit() #WtgTAG.query\ # .filter(WtgTAG.id.in_(id_tags))\ # .delete(synchronize_session=False) flash(_('Successfully deleted tags.'), 'success') return redirect(url_for('.display_list')) # AJAX # reposnse template: # action = name of action 'create' 'attach' 'detach' # success = True / False # # if success: # id_tag = id of tag participating in process # id_bibrec = id of bibrec (if present in action) # items = created or deleted model objects # # else: # errors = dict of errors from form @blueprint.route('/create', methods=['GET', 'POST']) @login_required @register_breadcrumb(blueprint, '.create', _('New tag')) @templated('tags/create.html') def create(): """ Create a new tag """ response = {} response['action'] = 'create' user = User.query.get(current_user.get_id()) form = CreateTagForm(request.values, csrf_enabled=False) if form.validate_on_submit() or\ (request.is_xhr and form.validate()): new_tag = WtgTAG() form.populate_obj(new_tag) new_tag.user = user db.session.add(new_tag) db.session.flush() db.session.refresh(new_tag) if 'id_bibrec' in form.data and form.data['id_bibrec']: record = db.session.query(Bibrec).get(form.data['id_bibrec']) new_tag.records.append(record) db.session.add(new_tag) response['id_bibrec'] = form.data['id_bibrec'] db.session.commit() db.session.refresh(new_tag) response['success'] = True response['id_tag'] = new_tag.id response['items'] = [new_tag.serializable_fields()] if request.is_xhr: return jsonify(response) else: return redirect(url_for('.display_list')) else: if request.is_xhr: response['success'] = False response['errors'] = form.errors return jsonify(response) else: return dict(form=form) @blueprint.route('/attach', methods=['GET', 'POST']) @login_required def attach(): """ Attach a tag to a record """ response = {} response['action'] = 'attach' # Ajax - disable csrf form = AttachTagForm(request.values, csrf_enabled=False) if form.validate(): association = WtgTAGRecord() form.populate_obj(association) db.session.add(association) db.session.commit() response['success'] = True response['id_tag'] = association.id_tag response['id_bibrec'] = association.id_bibrec response['items'] = [association.serializable_fields()] else: response['success'] = False response['errors'] = form.errors return jsonify(response) @blueprint.route('/detach', methods=['GET', 'POST']) @login_required def detach(): """ Detach a tag from a record """ response = {} response['action'] = 'detach' # Ajax - disable csrf form = DetachTagForm(request.values, csrf_enabled=False) if form.validate(): association = db.session.query(WtgTAGRecord)\ .filter_by(id_tag = form.data['id_tag'], id_bibrec = form.data['id_bibrec']).first() if association: db.session.delete(association) db.session.commit() response['success'] = True response['id_tag'] = association.id_tag response['id_bibrec'] = association.id_bibrec response['items'] = [association.serializable_fields()] else: response['success'] = False response['errors'] = form.errors return jsonify(response) diff --git a/invenio/modules/workflows/models.py b/invenio/modules/workflows/models.py index 273479283..2cff16bd3 100644 --- a/invenio/modules/workflows/models.py +++ b/invenio/modules/workflows/models.py @@ -1,559 +1,558 @@ # -*- coding: utf-8 -*- ## This file is part of Invenio. ## Copyright (C) 2012, 2013 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. import os import tempfile import cPickle import base64 import logging import six from datetime import datetime from sqlalchemy import desc from sqlalchemy.orm.exc import NoResultFound from invenio.ext.sqlalchemy import db from invenio.base.globals import cfg from .config import CFG_OBJECT_VERSION from .utils import redis_create_search_entry from .logger import (get_logger, BibWorkflowLogHandler) def get_default_data(): """ Returns the base64 representation of the data default value """ data_default = {} return base64.b64encode(cPickle.dumps(data_default)) def get_default_extra_data(): """ Returns the base64 representation of the extra_data default value """ extra_data_default = {"tasks_results": {}, "owner": {}, "task_counter": {}, "error_msg": "", "last_task_name": "", "latest_object": -1, "widget": None, "redis_search": {}} return base64.b64encode(cPickle.dumps(extra_data_default)) class Workflow(db.Model): __tablename__ = "bwlWORKFLOW" uuid = db.Column(db.String(36), primary_key=True, nullable=False) name = db.Column(db.String(255), default="Default workflow", nullable=False) created = db.Column(db.DateTime, default=datetime.now, nullable=False) modified = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False) id_user = db.Column(db.Integer, default=0, nullable=False) _extra_data = db.Column(db.LargeBinary, nullable=False, default=get_default_extra_data()) status = db.Column(db.Integer, default=0, nullable=False) current_object = db.Column(db.Integer, default="0", nullable=False) objects = db.relationship("BibWorkflowObject", backref="bwlWORKFLOW") counter_initial = db.Column(db.Integer, default=0, nullable=False) counter_halted = db.Column(db.Integer, default=0, nullable=False) counter_error = db.Column(db.Integer, default=0, nullable=False) counter_finished = db.Column(db.Integer, default=0, nullable=False) module_name = db.Column(db.String(64), nullable=False) def __repr__(self): return "" % \ (str(self.name), str(self.module_name), str(self.created), str(self.modified), str(self.id_user), str(self.status)) def __str__(self): return """Workflow: Uuid: %s Name: %s User id: %s Module name: %s Created: %s Modified: %s Status: %s Current object: %s Counters: initial=%s, halted=%s, error=%s, finished=%s Extra data: %s""" % (str(self.uuid), str(self.name), str(self.id_user), str(self.module_name), str(self.created), str(self.modified), str(self.status), str(self.current_object), str(self.counter_initial), str(self.counter_halted), str(self.counter_error), str(self.counter_finished), str(self._extra_data),) @classmethod def get(cls, *criteria, **filters): """ A wrapper for the filter and filter_by functions of sqlalchemy. Define a dict with which columns should be filtered by which values. e.g. Workflow.get(uuid=uuid) Workflow.get(Workflow.uuid != uuid) The function supports also "hybrid" arguments. e.g. Workflow.get(Workflow.module_name != 'i_hate_this_module', user_id=user_id) look up also sqalchemy BaseQuery's filter and filter_by documentation """ return cls.query.filter(*criteria).filter_by(**filters) @classmethod def get_status(cls, uuid=None): """ Returns the status of the workflow """ return cls.get(Workflow.uuid == uuid).one().status @classmethod def get_most_recent(cls, *criteria, **filters): """ Returns the most recently modified workflow. """ most_recent = cls.get(*criteria, **filters).\ order_by(desc(Workflow.modified)).first() if most_recent is None: raise NoResultFound else: return most_recent @classmethod def get_objects(cls, uuid=None): """ Returns the objects of the workflow """ return cls.get(Workflow.uuid == uuid).one().objects def get_extra_data(self, user_id=0, uuid=None, key=None, getter=None): """Returns a json of the column extra_data or if any of the other arguments are defined, a specific value. You can define either the key or the getter function. @param key: the key to access the desirable value @param getter: a callable that takes a dict as param and returns a value """ extra_data = Workflow.get(Workflow.id_user == self.id_user, Workflow.uuid == self.uuid).one()._extra_data extra_data = cPickle.loads(base64.b64decode(extra_data)) if key is not None: return extra_data[key] elif callable(getter): return getter(extra_data) def set_extra_data(self, user_id=0, uuid=None, key=None, value=None, setter=None): """Modifies the json of the column extra_data or if any of the other arguments are defined, a specific value. You can define either the key, value or the setter function. @param key: the key to access the desirable value @param value: the new value @param setter: a callable that takes a dict as param and modifies it """ extra_data = Workflow.get(Workflow.id_user == user_id, Workflow.uuid == uuid).one()._extra_data extra_data = cPickle.loads(base64.b64decode(extra_data)) if key is not None and value is not None: extra_data[key] = value elif callable(setter): setter(extra_data) Workflow.get(Workflow.uuid == self.uuid).update({'_extra_data': base64.b64encode(cPickle.dumps(extra_data))}) @classmethod def delete(cls, uuid=None): cls.get(Workflow.uuid == uuid).delete() db.session.commit() class BibWorkflowObject(db.Model): # db table definition __tablename__ = "bwlOBJECT" id = db.Column(db.Integer, primary_key=True) # Our internal data column. Default is encoded dict. _data = db.Column(db.LargeBinary, nullable=False, default=get_default_data()) _extra_data = db.Column(db.LargeBinary, nullable=False, default=get_default_extra_data()) id_workflow = db.Column(db.String(36), db.ForeignKey("bwlWORKFLOW.uuid"), nullable=True) version = db.Column(db.Integer(3), default=CFG_OBJECT_VERSION.RUNNING, nullable=False) id_parent = db.Column(db.Integer, db.ForeignKey("bwlOBJECT.id"), default=None) child_objects = db.relationship("BibWorkflowObject", remote_side=[id_parent]) created = db.Column(db.DateTime, default=datetime.now, nullable=False) modified = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False) status = db.Column(db.String(255), default="", nullable=False) data_type = db.Column(db.String(150), default="", nullable=True) uri = db.Column(db.String(500), default="") id_user = db.Column(db.Integer, default=0, nullable=False) child_logs = db.relationship("BibWorkflowObjectLog") workflow = db.relationship( Workflow, foreign_keys=[id_workflow], remote_side=Workflow.uuid ) _log = None @property def log(self): if not self._log: db_handler_obj = BibWorkflowLogHandler(BibWorkflowObjectLog, "id") self._log = get_logger(logger_name="object.%s_%s" % (self.id_workflow, self.id), db_handler_obj=db_handler_obj, loglevel=logging.DEBUG, obj=self) return self._log def get_data(self): """ Main method to retrieve data saved to the object. """ return cPickle.loads(base64.b64decode(self._data)) def set_data(self, value): """ Main method to update data saved to the object. """ self._data = base64.b64encode(cPickle.dumps(value)) def get_extra_data(self): """ Main method to retrieve data saved to the object. """ return cPickle.loads(base64.b64decode(self._extra_data)) def set_extra_data(self, value): """ Main method to update data saved to the object. """ self._extra_data = base64.b64encode(cPickle.dumps(value)) def _create_db_obj(self): db.session.add(self) db.session.commit() def __repr__(self): return "" % \ (self.id, self.id_object, self.message, self.created) @classmethod def get(cls, *criteria, **filters): """ A wrapper for the filter and filter_by functions of sqlalchemy. Define a dict with which columns should be filtered by which values. look up also sqalchemy BaseQuery's filter and filter_by documentation """ return cls.query.filter(*criteria).filter_by(**filters) @classmethod def get_most_recent(cls, *criteria, **filters): """ Returns the most recently created log. """ most_recent = cls.get(*criteria, **filters).\ order_by(desc(BibWorkflowObjectLog.created)).first() if most_recent is None: raise NoResultFound else: return most_recent @classmethod def delete(cls, id=None): cls.get(BibWorkflowObjectLog.id == id).delete() db.session.commit() class BibWorkflowEngineLog(db.Model): __tablename__ = "bwlWORKFLOWLOGGING" id = db.Column(db.Integer, primary_key=True) id_object = db.Column(db.String(255), nullable=False) log_type = db.Column(db.Integer, default=0, nullable=False) created = db.Column(db.DateTime, default=datetime.now) message = db.Column(db.TEXT, default="", nullable=False) def __repr__(self): return "" % \ (self.id, self.id_object, self.message, self.created) @classmethod def get(cls, *criteria, **filters): """ A wrapper for the filter and filter_by functions of sqlalchemy. Define a dict with which columns should be filtered by which values. look up also sqalchemy BaseQuery's filter and filter_by documentation """ return cls.query.filter(*criteria).filter_by(**filters) @classmethod def get_most_recent(cls, *criteria, **filters): """ Returns the most recently created log. """ most_recent = cls.get(*criteria, **filters).\ order_by(desc(BibWorkflowEngineLog.created)).first() if most_recent is None: raise NoResultFound else: return most_recent @classmethod def delete(cls, uuid=None): cls.get(BibWorkflowEngineLog.id == uuid).delete() db.session.commit() __all__ = ['Workflow', 'BibWorkflowObject', 'BibWorkflowObjectLog', 'BibWorkflowEngineLog'] diff --git a/invenio/modules/workflows/tasks/marcxml_tasks.py b/invenio/modules/workflows/tasks/marcxml_tasks.py index 47bcb0e1f..7aa260fb6 100644 --- a/invenio/modules/workflows/tasks/marcxml_tasks.py +++ b/invenio/modules/workflows/tasks/marcxml_tasks.py @@ -1,671 +1,669 @@ ## This file is part of Invenio. ## Copyright (C) 2012, 2013 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. import os import random import time import glob import re import traceback from invenio.legacy.bibupload.engine import (find_record_from_recid, find_record_from_sysno, find_records_from_extoaiid, find_record_from_oaiid, find_record_from_doi ) from invenio.legacy.oaiharvest.dblayer import create_oaiharvest_log_str from invenio.base.config import (CFG_TMPSHAREDDIR, CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT, CFG_TMPDIR, CFG_INSPIRE_SITE) from invenio.legacy.oaiharvest.utils import (record_extraction_from_file, collect_identifiers, harvest_step, translate_fieldvalues_from_latex, find_matching_files, ) from invenio.legacy.bibsched.bibtask import (task_sleep_now_if_required, task_low_level_submission ) from invenio.modules.oai_harvest.models import OaiHARVEST -from invenio.legacy.bibfield.bibfield_jsonreader import JsonReader +from invenio.modules.records.api import Record from invenio.modules.workflows.errors import WorkflowError from invenio.legacy.refextract.api import extract_references_from_file_xml from invenio.legacy.bibrecord import (create_records, record_xml_output ) from invenio.utils.plotextractor.output_utils import (create_MARC, create_contextfiles, prepare_image_data, remove_dups ) from invenio.utils.plotextractor.getter import (harvest_single, make_single_directory ) from invenio.utils.plotextractor.cli import (get_defaults, extract_captions, extract_context ) from invenio.utils.shell import (run_shell_command, Timeout ) import invenio.legacy.template from invenio.utils.plotextractor.converter import (untar, convert_images ) from invenio.utils.serializers import deserialize_via_marshal oaiharvest_templates = invenio.legacy.template.load('oaiharvest') REGEXP_REFS = re.compile(".*?.*?(.*?)", re.DOTALL) REGEXP_AUTHLIST = re.compile("", re.DOTALL) def add_metadata_to_extra_data(obj, eng): """ Creates bibrecord from object data and populates extra_data with metadata """ from invenio.legacy.bibrecord import create_record, record_get_field_value record = create_record(obj.data) obj.extra_data['redis_search']['category'] = \ record_get_field_value(record[0], '037', code='c') obj.extra_data['redis_search']['title'] = \ record_get_field_value(record[0], '245', code='a') obj.extra_data['redis_search']['source'] = \ record_get_field_value(record[0], '035', code='9') add_metadata_to_extra_data.__title__ = "Metadata Extraction" add_metadata_to_extra_data.__description__ = "Populates object's extra_data with metadata" def approve_record(obj, eng): """ Will add the approval widget to the record """ obj.extra_data["last_task_name"] = 'Record Approval' eng.log.info("last task name: approve_record") try: obj.extra_data['message'] = 'Record needs approval. Click on widget to resolve.' eng.log.info("Adding the approval widget to %s" % obj.id) obj.extra_data['widget'] = 'approval_widget' eng.halt("Record needs approval") except KeyError: # Log the error obj.extra_data["error_msg"] = 'Could not assign widget' approve_record.__title__ = "Record Approval" approve_record.__description__ = "This task assigns the approval widget to a record." def convert_record_to_bibfield(obj, eng): """ Convert a record in data into a 'dictionary' thanks to BibField """ - from invenio.legacy.bibfield import create_record + from invenio.base.records.api import create_record eng.log.info("last task name: convert_record_to_bibfield") - obj.data = create_record(obj.data).rec_json + obj.data = create_record(obj.data).dumps() eng.log.info("Conversion succeed") def init_harvesting(obj, eng): """ This function gets all the option linked to the task and stores them into the object to be used later. """ eng.log.info("last task name: init_harvesting") try: obj.extra_data["options"] = eng.extra_data["options"] except KeyError: eng.log.error("Non Critical Error: No options", "No options for this task have been found. It is possible" "that the fillowing task could failed or work not as expected") obj.extra_data["options"] = {} eng.log.info("end of init_harvesting") def get_repositories_list(repositories): """ Here we are retrieving the oaiharvest configuration for the task. It will allows in the future to do all the correct operations. """ def _get_repositories_list(obj, eng): eng.log.info("last task name: _get_repositories_list") reposlist_temp = None if repositories: for reposname in repositories: reposlist_temp = OaiHARVEST.get(OaiHARVEST.name == reposname).all() else: reposlist_temp = OaiHARVEST.get(OaiHARVEST.name != "").all() return reposlist_temp return _get_repositories_list def harvest_records(obj, eng): """ Run the harvesting task. The row argument is the oaiharvest task queue row, containing if, arguments, etc. Return 1 in case of success and 0 in case of failure. """ eng.log.info("last task name: harvest_records") obj.extra_data["last_task_name"] = 'harvest_records' harvested_identifier_list = [] harvestpath = "%s_%d_%s_" % ("%s/oaiharvest_%s" % (CFG_TMPSHAREDDIR, eng.uuid), 1, time.strftime("%Y%m%d%H%M%S")) # ## go ahead: check if user requested from-until harvesting try: if "dates" not in obj.extra_data["options"]: obj.extra_data["options"]["dates"] = {} if "identifiers" not in obj.extra_data["options"]: obj.extra_data["options"]["identifiers"] = {} except TypeError: obj.extra_data["options"] = {"dates": {}, "identifiers": {}} task_sleep_now_if_required() arguments = obj.extra_data["repository"].get_arguments() if arguments: eng.log.info("running with post-processes: %r" % (arguments,)) # Harvest phase try: harvested_files_list = harvest_step(obj.data, harvestpath, obj.extra_data["options"]["identifiers"], obj.extra_data["options"]["dates"]) except Exception: eng.log.error("Error while harvesting %s. Skipping." % (obj.data,)) raise WorkflowError("Error while harvesting %r. Skipping." % (obj.data,), id_workflow=eng.uuid) if len(harvested_files_list) == 0: eng.log.error("No records harvested for %s" % (obj.data.name,)) return None # Retrieve all OAI IDs and set active list harvested_identifier_list.append(collect_identifiers(harvested_files_list)) if len(harvested_files_list) != len(harvested_identifier_list[0]): # Harvested files and its identifiers are 'out of sync', abort harvest msg = "Harvested files miss identifiers for %s" % (arguments,) eng.log.info(msg) raise WorkflowError(msg, id_workflow=eng.uuid) eng.log.info("%d files harvested and processed" % (len(harvested_files_list),)) eng.log.info("End harvest records task") harvest_records.__id__ = "h" def get_records_from_file(path=None): def _get_records_from_file(obj, eng): eng.log.info("last task name: _get_records_from_file") if not "LoopData" in eng.extra_data: eng.extra_data["LoopData"] = {} if "get_records_from_file" not in eng.extra_data["LoopData"]: if path: eng.extra_data["LoopData"].update({"get_records_from_file": record_extraction_from_file(path)}) else: eng.extra_data["LoopData"].update({"get_records_from_file": record_extraction_from_file(obj.data)}) return eng.extra_data["LoopData"]["get_records_from_file"] return _get_records_from_file def get_eng_uuid_harvested(obj, eng): """ Simple function which allows to retrieve the uuid of the eng in the workflow for printing by example """ eng.log.info("last task name: get_eng_uuid_harvested") return "*" + str(eng.uuid) + "*.harvested" def get_files_list(path, parameter): def _get_files_list(obj, eng): eng.log.info("last task name: get_files_list") if callable(parameter): unknown = parameter(obj, eng) else: unknown = parameter result = glob.glob1(path, unknown) for i in range(0, len(result)): result[i] = path + os.sep + result[i] return result return _get_files_list def convert_record(stylesheet="oaidc2marcxml.xsl"): def _convert_record(obj, eng): """ Will convert the object data, if XML, using the given stylesheet """ eng.log.info("last task name: convert_record") from invenio.legacy.bibconvert.xslt_engine import convert obj.extra_data["last_task_name"] = 'Convert Record' eng.log.info("Starting conversion using %s stylesheet" % (stylesheet,)) try: obj.data = convert(obj.data, stylesheet) except Exception as e: msg = "Could not convert record: %s\n%s" % \ (str(e), traceback.format_exc()) obj.extra_data["error_msg"] = msg eng.log.error("Error: %s" % (msg,)) raise WorkflowError("Error: %s" % (msg,), id_workflow=eng.uuid) return _convert_record def fulltext_download(obj, eng): """ Performs the fulltext download step. Only for arXiv """ eng.log.info("full-text attachment step started") task_sleep_now_if_required() if "pdf" not in obj.extra_data["options"]["identifiers"]: extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) tarball, pdf = harvest_single(obj.data["system_control_number"]["value"], extract_path, ["pdf"]) time.sleep(CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT) arguments = obj.extra_data["repository"].get_arguments() if not arguments['t_doctype'] == '': doctype = arguments['t_doctype'] else: doctype = 'arXiv' if pdf: obj.extra_data["options"]["identifiers"]["pdf"] = pdf fulltext_xml = (" \n" " %(url)s\n" " %(doctype)s\n" " " ) % {'url': obj.extra_data["options"]["identifiers"]["pdf"], 'doctype': doctype} updated_xml = '\n\n\n' + fulltext_xml + \ '\n' - from invenio.legacy.bibfield import create_record + from invenio.modules.records.api import create_record - new_dict_representation = create_record(updated_xml).rec_json + new_dict_representation = create_record(updated_xml).dumps() try: obj.data['fft'].append(new_dict_representation["fft"]) except: obj.data['fft'] = [new_dict_representation['fft']] def quick_match_record(obj, eng): """ Retrieve the record Id from a record by using tag 001 or SYSNO or OAI ID or DOI tag. opt_mod is the desired mode. 001 fields even in the insert mode """ eng.log.info("last task name: quick_match_record") obj.extra_data["last_task_name"] = 'Quick Match Record' function_dictionnary = {'recid': find_record_from_recid, 'system_number': find_record_from_sysno, 'oaiid': find_record_from_oaiid, 'system_control_number': find_records_from_extoaiid, 'doi': find_record_from_doi} - my_json_reader = JsonReader() - my_json_reader.rec_json = obj.data + my_json_reader = Record(obj.data) try: identifiers = {} #identifiers = my_json_reader.get_persistent_identifiers() except KeyError: identifiers = {} if not "recid" in identifiers: for identifier in identifiers: recid = function_dictionnary[identifier](identifiers[identifier]["value"]) if recid: obj.data['recid']['value'] = recid return True return False else: return True def upload_record(mode="ir"): def _upload_record(obj, eng): eng.log.info("last task name: upload_record") from invenio.legacy.bibsched.bibtask import task_low_level_submission obj.extra_data["last_task_name"] = 'Upload Record' eng.log_info("Saving data to temporary file for upload") filename = obj.save_to_file() params = ["-%s" % (mode,), filename] task_id = task_low_level_submission("bibupload", "bibworkflow", *tuple(params)) eng.log_info("Submitted task #%s" % (task_id,)) _upload_record.__title__ = "Upload Record" _upload_record.__description__ = "Uploads the record using BibUpload" return _upload_record upload_record.__id__ = "u" def plot_extract(plotextractor_types): def _plot_extract(obj, eng): """ Performs the plotextraction step. """ eng.log.info("last task name: plot_extract") obj.extra_data["last_task_name"] = 'plotextraction' eng.log.info("plotextraction step started") # Download tarball for each harvested/converted record, then run plotextrator. # Update converted xml files with generated xml or add it for upload task_sleep_now_if_required() if 'latex' in plotextractor_types: # Run LaTeX plotextractor if "tarball" not in obj.extra_data["options"]["identifiers"]: # turn oaiharvest_23_1_20110214161632_converted -> oaiharvest_23_1_material # to let harvested material in same folder structure extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) tarball, pdf = harvest_single(obj.data["system_control_number"]["value"], extract_path, ["tarball"]) tarball = str(tarball) time.sleep(CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT) if tarball is None: raise WorkflowError("Error harvesting tarball from id: %s %s" % (obj.data["system_control_number"]["value"], extract_path), id_workflow=eng.uuid) obj.extra_data["options"]["identifiers"]["tarball"] = tarball else: tarball = obj.extra_data["options"]["identifiers"]["tarball"] sub_dir, refno = get_defaults(tarball, CFG_TMPDIR, "") tex_files = None image_list = None try: extracted_files_list, image_list, tex_files = untar(tarball, sub_dir) except Timeout: eng.log.error('Timeout during tarball extraction on %s' % (tarball,)) converted_image_list = convert_images(image_list) eng.log.info('converted %d of %d images found for %s' % (len(converted_image_list), len(image_list), os.path.basename(tarball))) extracted_image_data = [] if tex_files == [] or tex_files is None: eng.log.error('%s is not a tarball' % (os.path.split(tarball)[-1],)) run_shell_command('rm -r %s', (sub_dir,)) else: for tex_file in tex_files: # Extract images, captions and labels partly_extracted_image_data = extract_captions(tex_file, sub_dir, converted_image_list) if partly_extracted_image_data: # Add proper filepaths and do various cleaning cleaned_image_data = prepare_image_data(partly_extracted_image_data, tex_file, converted_image_list) # Using prev. extracted info, get contexts for each image found extracted_image_data.extend((extract_context(tex_file, cleaned_image_data))) if extracted_image_data: extracted_image_data = remove_dups(extracted_image_data) create_contextfiles(extracted_image_data) marc_xml = '\n\n' marc_xml = marc_xml + create_MARC(extracted_image_data, tarball, None) marc_xml += "\n" if marc_xml: - from invenio.legacy.bibfield import create_record + from invenio.modules.records.api import create_record # We store the path to the directory the tarball contents live # Read and grab MARCXML from plotextractor run - new_dict_representation = create_record(marc_xml).rec_json + new_dict_representation = create_record(marc_xml).dumps() try: obj.data['fft'].append(new_dict_representation["fft"]) except KeyError: obj.data['fft'] = [new_dict_representation['fft']] return _plot_extract def refextract(obj, eng): """ Performs the reference extraction step. """ eng.log.info("refextraction step started") task_sleep_now_if_required() if "pdf" not in obj.extra_data["options"]["identifiers"]: extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) tarball, pdf = harvest_single(obj.data["system_control_number"]["value"], extract_path, ["pdf"]) time.sleep(CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT) if pdf is not None: obj.extra_data["options"]["identifiers"]["pdf"] = pdf elif not os.path.isfile(obj.extra_data["options"]["identifiers"]["pdf"]): extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) tarball, pdf = harvest_single(obj.data["system_control_number"]["value"], extract_path, ["pdf"]) time.sleep(CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT) if pdf is not None: obj.extra_data["options"]["identifiers"]["pdf"] = pdf if os.path.isfile(obj.extra_data["options"]["identifiers"]["pdf"]): cmd_stdout = extract_references_from_file_xml(obj.extra_data["options"]["identifiers"]["pdf"]) references_xml = REGEXP_REFS.search(cmd_stdout) if references_xml: updated_xml = '\n\n' + references_xml.group(1) + \ "\n" - from invenio.legacy.bibfield import create_record + from invenio.modules.records.api import create_record - new_dict_representation = create_record(updated_xml).rec_json + new_dict_representation = create_record(updated_xml).dumps() try: obj.data['reference'].append(new_dict_representation["reference"]) except KeyError: if 'reference' in new_dict_representation: obj.data['reference'] = [new_dict_representation['reference']] else: obj.log.error("Not able to download and process the PDF ") def author_list(obj, eng): """ Performs the special authorlist extraction step (Mostly INSPIRE/CERN related). """ eng.log.info("last task name: author_list") eng.log.info("authorlist extraction step started") identifiers = obj.data["system_control_number"]["value"] task_sleep_now_if_required() if "tarball" not in obj.extra_data["options"]["identifiers"]: extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) tarball, pdf = harvest_single(obj.data["system_control_number"]["value"], extract_path, ["tarball"]) tarball = str(tarball) time.sleep(CFG_PLOTEXTRACTOR_DOWNLOAD_TIMEOUT) if tarball is None: raise WorkflowError("Error harvesting tarball from id: %s %s" % (identifiers, extract_path), id_workflow=eng.uuid) obj.extra_data["options"]["identifiers"]["tarball"] = tarball sub_dir, dummy = get_defaults(obj.extra_data["options"]["identifiers"]["tarball"], CFG_TMPDIR, "") try: untar(obj.extra_data["options"]["identifiers"]["tarball"], sub_dir) except Timeout: eng.log.error('Timeout during tarball extraction on %s' % (obj.extra_data["options"]["identifiers"]["tarball"])) xml_files_list = find_matching_files(sub_dir, ["xml"]) authors = "" for xml_file in xml_files_list: xml_file_fd = open(xml_file, "r") xml_content = xml_file_fd.read() xml_file_fd.close() match = REGEXP_AUTHLIST.findall(xml_content) if not match == []: authors += match[0] # Generate file to store conversion results if authors is not '': from invenio.legacy.bibconvert.xslt_engine import convert authors = convert(authors, "authorlist2marcxml.xsl") authorlist_record = create_records(authors) if len(authorlist_record) == 1: if authorlist_record[0][0] is None: eng.log.error("Error parsing authorlist record for id: %s" % (identifiers,)) authorlist_record = authorlist_record[0][0] # Convert any LaTeX symbols in authornames translate_fieldvalues_from_latex(authorlist_record, '100', code='a') translate_fieldvalues_from_latex(authorlist_record, '700', code='a') # Look for any UNDEFINED fields in authorlist #key = "UNDEFINED" #matching_fields = record_find_matching_fields(key, authorlist_record, tag='100') +\ # record_find_matching_fields(key, authorlist_record, tag='700') #if len(matching_fields) > 0: # UNDEFINED found. Create ticket in author queue # ticketid = create_authorlist_ticket(matching_fields, \ # identifiers, arguments.get('a_rt-queue')) # if ticketid: # eng.log.info("authorlist RT ticket %d submitted for %s" % (ticketid, identifiers)) # else: # eng.log.error("Error while submitting RT ticket for %s" % (identifiers,)) updated_xml = '\n\n' + record_xml_output(authorlist_record) \ + '' if not None == updated_xml: - from invenio.legacy.bibfield import create_record + from invenio.base.records.api import create_record # We store the path to the directory the tarball contents live # Read and grab MARCXML from plotextractor run - new_dict_representation = create_record(updated_xml).rec_json + new_dict_representation = create_record(updated_xml).dumps() obj.data['authors'] = new_dict_representation["authors"] obj.data['number_of_authors'] = new_dict_representation["number_of_authors"] author_list.__id__ = "u" def upload_step(obj, eng): """ Perform the upload step. """ eng.log.info("upload step started") uploaded_task_ids = [] #Work comment: # #Prepare in case of filtering the files to up, #no filtering, no other things to do - new_dict_representation = JsonReader() - new_dict_representation.rec_json = obj.data + new_dict_representation = Record(obj.data) marcxml_value = new_dict_representation.legacy_export_as_marc() task_id = None # Get a random sequence ID that will allow for the tasks to be # run in order, regardless if parallel task execution is activated sequence_id = random.randrange(1, 4294967296) task_sleep_now_if_required() extract_path = make_single_directory(CFG_TMPSHAREDDIR, eng.uuid) # Now we launch BibUpload tasks for the final MARCXML files filepath = extract_path + os.sep + str(obj.id) file_fd = open(filepath, 'w') file_fd.write(marcxml_value) file_fd.close() mode = ["-r", "-i"] arguments = obj.extra_data["repository"].get_arguments() if os.path.exists(filepath): try: args = mode if sequence_id: args.extend(['-I', str(sequence_id)]) if arguments.get('u_name', ""): args.extend(['-N', arguments.get('u_name', "")]) if arguments.get('u_priority', 5): args.extend(['-P', str(arguments.get('u_priority', 5))]) args.append(filepath) task_id = task_low_level_submission("bibupload", "oaiharvest", *tuple(args)) create_oaiharvest_log(task_id, obj.extra_data["repository"].id, filepath) except Exception, msg: eng.log.error("An exception during submitting oaiharvest task occured : %s " % (str(msg))) return None else: eng.log.error("marcxmlfile %s does not exist" % (filepath,)) if task_id is None: eng.log.error("an error occurred while uploading %s from %s" % (filepath, obj.extra_data["repository"].name)) else: uploaded_task_ids.append(task_id) eng.log.info("material harvested from source %s was successfully uploaded" % (obj.extra_data["repository"].name,)) if CFG_INSPIRE_SITE: # Launch BibIndex,Webcoll update task to show uploaded content quickly bibindex_params = ['-w', 'collection,reportnumber,global', '-P', '6', '-I', str(sequence_id), '--post-process', 'bst_run_bibtask[taskname="webcoll", user="oaiharvest", P="6", c="HEP"]'] task_low_level_submission("bibindex", "oaiharvest", *tuple(bibindex_params)) eng.log.info("end of upload") def create_oaiharvest_log(task_id, oai_src_id, marcxmlfile): """ Function which creates the harvesting logs @param task_id bibupload task id """ file_fd = open(marcxmlfile, "r") xml_content = file_fd.read(-1) file_fd.close() create_oaiharvest_log_str(task_id, oai_src_id, xml_content) diff --git a/invenio/testsuite/__init__.py b/invenio/testsuite/__init__.py index 547639d6e..1afd6788c 100644 --- a/invenio/testsuite/__init__.py +++ b/invenio/testsuite/__init__.py @@ -1,1195 +1,1195 @@ # This file is part of Invenio. # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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=E1102 from __future__ import with_statement """ Helper functions for building and running test suites. """ CFG_TESTUTILS_VERBOSE = 1 import os import sys import time pyv = sys.version_info if pyv[0] == 2 and pyv[1] < 7: import unittest2 as unittest else: import unittest import cgi import subprocess import binascii import StringIO from flask import url_for from functools import wraps from warnings import warn from urlparse import urlsplit, urlunsplit from urllib import urlencode from itertools import chain, repeat try: from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait except ImportError: # web tests will not be available, but unit and regression tests will: pass #try: # from nose.tools import nottest #except ImportError: # def nottest(f): # """Helper decorator to mark a function as not to be tested by nose.""" # f.__test__ = False # return f nottest = unittest.skip('nottest') #@nottest def warn_user_about_tests(test_suite_type='regression'): """ Display a standard warning about running tests that might modify user data, and wait for user confirmation, unless --yes-i-know was specified in the comman line. """ # Provide a command line option to avoid having to type the # confirmation every time during development. if '--yes-i-know' in sys.argv: return if test_suite_type == 'web': sys.stderr.write("""\ ********************************************************************** ** ** ** A B O U T T H E W E B T E S T S U I T E ** ** ** ** The web test suite will be launched in Firefox. You must have ** ** the Selenium IDE extension installed to be able to run the web ** ** test suite. If you do, please check out the results of the web ** ** test suite in the Selenium IDE window. ** ** ** ********************************************************************** """) sys.stderr.write("""\ ********************************************************************** ** ** ** I M P O R T A N T W A R N I N G ** ** ** ** The %s test suite needs to be run on a clean demo site ** ** that you can obtain by doing: ** ** ** ** $ inveniocfg --drop-demo-site ** ** $ inveniocfg --create-demo-site ** ** $ inveniocfg --load-demo-records ** ** ** ** Note that DOING THE ABOVE WILL ERASE YOUR ENTIRE DATABASE. ** ** ** ** In addition, due to the write nature of some of the tests, ** ** the demo DATABASE will be ALTERED WITH JUNK DATA, so that ** ** it is recommended to rebuild the demo site anew afterwards. ** ** ** ********************************************************************** Please confirm by typing 'Yes, I know!': """ % test_suite_type) answer = raw_input('') if answer != 'Yes, I know!': sys.stderr.write("Aborted.\n") raise SystemExit(0) return #@nottest def make_test_suite(*test_cases): """ Build up a test suite given separate test cases""" return unittest.TestSuite([unittest.makeSuite(case, 'test') for case in test_cases]) from invenio.base.factory import create_app import pyparsing # pylint: disable=W0611 # pyparsinf needed to import here before flask.ext.testing # in order to avoid pyparsing troubles due to twill from flask.ext.testing import TestCase class InvenioFixture(object): def __init__(self, fixture_builder=None): self.fixture = None self.fixture_builder = fixture_builder def with_data(self, *datatypes): def dictate(func): @wraps(func) def patched(*args, **kwargs): if self.fixture is None: self.fixture = self.fixture_builder() @self.fixture.with_data(*datatypes) def with_data_func(data): return func(data, *args, **kwargs) return with_data_func() return patched return dictate class InvenioTestCase(TestCase, unittest.TestCase): @property def config(self): cfg = { 'engine': 'CFG_DATABASE_TYPE', 'host': 'CFG_DATABASE_HOST', 'port': 'CFG_DATABASE_PORT', 'username': 'CFG_DATABASE_USER', 'password': 'CFG_DATABASE_PASS', 'database': 'CFG_DATABASE_NAME', } out = {} for (k, v) in cfg.iteritems(): if hasattr(self, k): out[v] = getattr(self, k) return out def create_app(self): app = create_app(**self.config) app.testing = True return app def login(self, username, password): from invenio.config import CFG_SITE_SECURE_URL #from invenio.utils.url import rewrite_to_secure_url return self.client.post(url_for('webaccount.login'), base_url=CFG_SITE_SECURE_URL, #rewrite_to_secure_url(request.base_url), data=dict(nickname=username, password=password), follow_redirects=True) def logout(self): from invenio.config import CFG_SITE_SECURE_URL return self.client.get(url_for('webaccount.logout'), base_url=CFG_SITE_SECURE_URL, follow_redirects=True) def shortDescription(self): return class FlaskSQLAlchemyTest(InvenioTestCase): def setUp(self): from invenio.ext.sqlalchemy import db db.create_all() def tearDown(self): from invenio.ext.sqlalchemy import db db.session.expunge_all() db.session.rollback() db.drop_all() #@nottest def make_flask_test_suite(*test_cases): """ Build up a Flask test suite given separate test cases""" from operator import add from invenio.config import CFG_DEVEL_TEST_DATABASE_ENGINES create_type = lambda c: [type(k + c.__name__, (c,), d) for k, d in CFG_DEVEL_TEST_DATABASE_ENGINES.iteritems()] return unittest.TestSuite([unittest.makeSuite(case, 'test') for case in reduce(add, map(create_type, test_cases))]) -#@nottest +@nottest def run_test_suite(testsuite, warn_user=False): """ Convenience function to embed in test suites. Run given testsuite and eventually ask for confirmation of warn_user is True. """ if warn_user: warn_user_about_tests() res = unittest.TextTestRunner(descriptions=False, verbosity=2).run(testsuite) return res.wasSuccessful() def make_url(path, **kargs): """ Helper to generate an absolute invenio URL with query arguments""" from invenio.config import CFG_SITE_URL url = CFG_SITE_URL + path if kargs: url += '?' + urlencode(kargs, doseq=True) return url def make_surl(path, **kargs): """ Helper to generate an absolute invenio Secure URL with query arguments""" from invenio.config import CFG_SITE_SECURE_URL url = CFG_SITE_SECURE_URL + path if kargs: url += '?' + urlencode(kargs, doseq=True) return url def base64_to_file(base64_file, filepath): """ Write a base64 encoded version of a file to disk. """ with open(filepath, 'wb') as f: f.write(binascii.a2b_base64(base64_file)) def file_to_base64(filepath): """ Get base64 encoded version of a file. Useful to encode a test file for inclusion in tests. """ with open(filepath, 'rb') as f: return binascii.b2a_base64(f.read()) def stringio_to_base64(stringio_obj): """ Get base64 encoded version of a StringIO object. """ return binascii.b2a_base64(stringio_obj.getvalue()) def make_file_fixture(filename, base64_file): """ Generate a file fixture suitable for use with the Flask test client. @param base64_file: A string encoding a file in base64. Use file_to_base64() to get the base64 encoding of a file. If not provided a PDF file be generated instead, including """ return ( StringIO.StringIO(binascii.a2b_base64(base64_file)), filename ) def make_pdf_fixture(filename, text=None): """ Generates a PDF fixture suitable for use with Werkzeug test client and Flask test request context. Use of this function requires that reportlab have been installed. @param filename: Desired filename. @param text: Text to include in PDF. Defaults to "Filename: ", if not specified. """ if text is None: text = "Filename: %s" % filename # Generate simple PDF from reportlab.pdfgen import canvas output = StringIO.StringIO() c = canvas.Canvas(output) c.drawString(100, 100, text) c.showPage() c.save() return make_file_fixture(filename, stringio_to_base64(output)) class InvenioTestUtilsBrowserException(Exception): """Helper exception for the regression test suite browser.""" pass #@nottest def test_web_page_existence(url): """ Test whether URL exists and is well accessible. Return True or raise exception in case of problems. """ import mechanize browser = mechanize.Browser() try: browser.open(url) except: raise return True def get_authenticated_mechanize_browser(username="guest", password=""): """ Return an instance of a mechanize browser already authenticated to Invenio """ try: import mechanize except ImportError: raise InvenioTestUtilsBrowserException( 'ERROR: Cannot import mechanize.') browser = mechanize.Browser() browser.set_handle_robots(False) # ignore robots.txt, since we test gently if username == "guest": return browser from invenio.config import CFG_SITE_SECURE_URL browser.open(CFG_SITE_SECURE_URL + "/youraccount/login") browser.select_form(nr=0) browser['nickname'] = username browser['password'] = password browser.submit() username_account_page_body = browser.response().read() try: username_account_page_body.index("You are logged in as %s." % username) except ValueError: raise InvenioTestUtilsBrowserException( 'ERROR: Cannot login as %s.' % username) return browser #@nottest def test_web_page_content(url, username="guest", password="", expected_text="", unexpected_text="", expected_link_target=None, expected_link_label=None, require_validate_p=None): """Test whether web page URL as seen by user USERNAME contains text EXPECTED_TEXT and, eventually, contains a link to EXPECTED_LINK_TARGET (if set) labelled EXPECTED_LINK_LABEL (if set). The EXPECTED_TEXT is checked via substring matching, the EXPECTED_LINK_TARGET and EXPECTED_LINK_LABEL via exact string matching. EXPECTED_TEXT, EXPECTED_LINK_LABEL and EXPECTED_LINK_TARGET can either be strings or list of strings (in order to check multiple values inside same page). Before doing the tests, login as USERNAME with password PASSWORD. E.g. interesting values for USERNAME are "guest" or "admin". Return empty list in case of no problems, otherwise list of error messages that may have been encountered during processing of page. """ from invenio.utils.w3c_validator import w3c_validate, w3c_errors_to_str, \ CFG_TESTS_REQUIRE_HTML_VALIDATION if require_validate_p is None: require_validate_p = CFG_TESTS_REQUIRE_HTML_VALIDATION try: import mechanize except ImportError: raise InvenioTestUtilsBrowserException( 'ERROR: Cannot import mechanize.') if '--w3c-validate' in sys.argv: require_validate_p = True sys.stderr.write('Required validation\n') error_messages = [] try: browser = get_authenticated_mechanize_browser(username, password) try: browser.open(url) except mechanize.HTTPError, msg: if msg.code != 401: raise msg error_messages.append('ERROR: Page %s (login %s) not accessible. %s' % \ (url, username, msg)) url_body = browser.response().read() # now test for EXPECTED_TEXT: # first normalize expected_text if isinstance(expected_text, str): expected_texts = [expected_text] else: expected_texts = expected_text # then test for cur_expected_text in expected_texts: try: url_body.index(cur_expected_text) except ValueError: raise InvenioTestUtilsBrowserException, \ 'ERROR: Page %s (login %s) does not contain %s, but contains %s' % \ (url, username, cur_expected_text, url_body) # now test for UNEXPECTED_TEXT: # first normalize unexpected_text if isinstance(unexpected_text, str): if unexpected_text: unexpected_texts = [unexpected_text] else: unexpected_texts = [] else: unexpected_texts = unexpected_text # then test for cur_unexpected_text in unexpected_texts: try: url_body.index(cur_unexpected_text) raise InvenioTestUtilsBrowserException, \ 'ERROR: Page %s (login %s) contains %s.' % \ (url, username, cur_unexpected_text) except ValueError: pass # now test for EXPECTED_LINK_TARGET and EXPECTED_LINK_LABEL: if expected_link_target or expected_link_label: # first normalize expected_link_target and expected_link_label if isinstance(expected_link_target, str) or \ expected_link_target is None: expected_link_targets = [expected_link_target] else: expected_link_targets = expected_link_target if isinstance(expected_link_label, str) or \ expected_link_label is None: expected_link_labels = [expected_link_label] else: expected_link_labels = expected_link_label max_links = max( len(expected_link_targets), len(expected_link_labels)) expected_link_labels = chain(expected_link_labels, repeat(None)) expected_link_targets = chain(expected_link_targets, repeat(None)) # then test for dummy in range(0, max_links): cur_expected_link_target = expected_link_targets.next() cur_expected_link_label = expected_link_labels.next() try: browser.find_link(url=cur_expected_link_target, text=cur_expected_link_label) except mechanize.LinkNotFoundError: raise InvenioTestUtilsBrowserException, \ 'ERROR: Page %s (login %s) does not contain link to %s entitled %s.' % \ (url, username, cur_expected_link_target, cur_expected_link_label) # now test for validation if required if require_validate_p: valid_p, errors, warnings = w3c_validate(url_body) if not valid_p: error_text = 'ERROR: Page %s (login %s) does not validate:\n %s' % \ (url, username, w3c_errors_to_str( errors, warnings)) from invenio.config import CFG_LOGDIR open('%s/w3c-markup-validator.log' % CFG_LOGDIR, 'a').write(error_text) raise InvenioTestUtilsBrowserException, error_text except InvenioTestUtilsBrowserException, msg: error_messages.append( 'ERROR: Page %s (login %s) led to an error: %s.' % (url, username, msg)) try: # logout after tests: from invenio.config import CFG_SITE_SECURE_URL browser.open(CFG_SITE_SECURE_URL + "/youraccount/logout") browser.response().read() browser.close() except UnboundLocalError: pass except mechanize.HTTPError: # Raises 401 if you were not logged in before. pass if CFG_TESTUTILS_VERBOSE >= 9: print "%s test_web_page_content(), tested page `%s', login `%s', expected text `%s', errors `%s'." % \ (time.strftime("%Y-%m-%d %H:%M:%S -->", time.localtime()), url, username, expected_text, ",".join(error_messages)) return error_messages def merge_error_messages(error_messages): """If the ERROR_MESSAGES list is non-empty, merge them and return nicely formatted string suitable for printing. Otherwise return empty string. """ out = "" if error_messages: out = "\n*** " + "\n*** ".join(error_messages) return out #@nottest def build_and_run_unit_test_suite(): """ Detect all Invenio modules with names ending by '*_unit_tests.py', build a complete test suite of them, and run it. Called by 'inveniocfg --run-unit-tests'. """ from invenio.config import CFG_PYLIBDIR from invenio.pluginutils import PluginContainer test_modules_map = PluginContainer( os.path.join(CFG_PYLIBDIR, 'invenio', '*_unit_tests.py'), lambda plugin_name, plugin_code: getattr(plugin_code, "TEST_SUITE")) test_modules = [test_modules_map[name] for name in test_modules_map] broken_tests = test_modules_map.get_broken_plugins() broken_unit_tests = ['%s (reason: %s)' % (name, broken_tests[name][1]) for name in broken_tests] if broken_unit_tests: warn("Broken unit tests suites found: %s" % ', '.join(broken_unit_tests)) complete_suite = unittest.TestSuite(test_modules) res = unittest.TextTestRunner(descriptions=False, verbosity=2).run(complete_suite) return res.wasSuccessful() #@nottest def build_and_run_js_unit_test_suite(): """ Init the JsTestDriver server, detect all Invenio JavaScript files with names ending by '*_tests.js' and run them. Called by 'inveniocfg --run-js-unit-tests'. """ from invenio.config import CFG_PREFIX, CFG_WEBDIR, CFG_JSTESTDRIVER_PORT def _server_init(server_process): """ Init JsTestDriver server and check if it succedeed """ output_success = "Finished action run" output_error = "Server failed" read_timeout = 30 start_time = time.time() elapsed_time = 0 while 1: stdout_line = server_process.stdout.readline() if output_success in stdout_line: print '* JsTestDriver server ready\n' return True elif output_error in stdout_line or elapsed_time > read_timeout: print '* ! JsTestDriver server init failed\n' print server_process.stdout.read() return False elapsed_time = time.time() - start_time def _find_and_run_js_test_files(): """ Find all JS files installed in Invenio lib directory and run them on the JsTestDriver server """ from invenio.utils.shell import run_shell_command errors_found = 0 for candidate in os.listdir(CFG_WEBDIR + "/js"): base, ext = os.path.splitext(candidate) if ext != '.js' or not base.endswith('_tests'): continue print "Found test file %s. Running tests... " % (base + ext) dummy_current_exitcode, cmd_stdout, dummy_err_msg = run_shell_command( cmd="java -jar %s/JsTestDriver.jar --config %s --tests all" % (CFG_PREFIX + "/lib/java/js-test-driver", CFG_WEBDIR + "/js/" + base + '.conf')) print cmd_stdout if "Fails: 0" not in cmd_stdout: errors_found += 1 print errors_found return errors_found print "Going to start JsTestDriver server..." server_process = subprocess.Popen(["java", "-jar", "%s/JsTestDriver.jar" % ( CFG_PREFIX + "/lib/java/js-test-driver"), "--runnerMode", "INFO", "--port", "%d" % CFG_JSTESTDRIVER_PORT], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: from invenio.config import CFG_SITE_URL if not _server_init(server_process): # There was an error initialising server return 1 print "Now you can capture the browsers where you would " \ "like to run the tests by opening the following url:\n" \ "%s:%d/capture \n" % (CFG_SITE_URL, CFG_JSTESTDRIVER_PORT) print "Press enter when you are ready to run tests" raw_input() exitcode = _find_and_run_js_test_files() finally: server_process.kill() return exitcode #@nottest def build_and_run_regression_test_suite(): """ Detect all Invenio modules with names ending by '*_regression_tests.py', build a complete test suite of them, and run it. Called by 'inveniocfg --run-regression-tests'. """ from invenio.config import CFG_PYLIBDIR from invenio.pluginutils import PluginContainer test_modules_map = PluginContainer( os.path.join(CFG_PYLIBDIR, 'invenio', '*_regression_tests.py'), lambda plugin_name, plugin_code: getattr(plugin_code, "TEST_SUITE")) test_modules = test_modules_map.values() broken_tests = test_modules_map.get_broken_plugins() broken_regression_tests = ['%s (reason: %s)' % (name, broken_tests[name][1]) for name in broken_tests] if broken_regression_tests: warn("Broken regression tests suites found: %s" % ', '.join(broken_regression_tests)) warn_user_about_tests() complete_suite = unittest.TestSuite(test_modules) res = unittest.TextTestRunner(descriptions=False, verbosity=2).run(complete_suite) return res.wasSuccessful() #@nottest def build_and_run_web_test_suite(): """ Detect all Invenio modules with names ending by '*_web_tests.py', build a complete test suite of them, and run it. Called by 'inveniocfg --run-web-tests'. """ from invenio.config import CFG_PYLIBDIR from invenio.pluginutils import PluginContainer test_modules_map = PluginContainer( os.path.join(CFG_PYLIBDIR, 'invenio', '*_web_tests.py'), lambda plugin_name, plugin_code: getattr(plugin_code, "TEST_SUITE")) test_modules = test_modules_map.values() broken_tests = test_modules_map.get_broken_plugins() broken_web_tests = ['%s (reason: %s)' % (name, broken_tests[name][1]) for name in broken_tests] if broken_web_tests: warn("Broken web tests suites found: %s" % ', '.join(broken_web_tests)) warn_user_about_tests() complete_suite = unittest.TestSuite(test_modules) res = unittest.TextTestRunner(descriptions=False, verbosity=2).run(complete_suite) return res.wasSuccessful() class InvenioWebTestCase(InvenioTestCase): """ Helper library of useful web test functions for web tests creation. """ def setUp(self): """Initialization before tests.""" # Let's default to English locale profile = webdriver.FirefoxProfile() profile.set_preference('intl.accept_languages', 'en-us, en') profile.update_preferences() # the instance of Firefox WebDriver is created self.browser = webdriver.Firefox(profile) # list of errors self.errors = [] def tearDown(self): """Cleanup actions after tests.""" self.browser.quit() self.assertEqual([], self.errors) def find_element_by_name_with_timeout(self, element_name, timeout=30): """ Find an element by name. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. @param element_name: name of the element to find @type element_name: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_name(element_name)) except: raise InvenioWebTestCaseException(element=element_name) def find_element_by_link_text_with_timeout(self, element_link_text, timeout=30): """ Find an element by link text. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. @param element_link_text: link text of the element to find @type element_link_text: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_link_text(element_link_text)) except: raise InvenioWebTestCaseException(element=element_link_text) def find_element_by_partial_link_text_with_timeout(self, element_partial_link_text, timeout=30): """ Find an element by partial link text. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. @param element_partial_link_text: partial link text of the element to find @type element_partial_link_text: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_partial_link_text(element_partial_link_text)) except: raise InvenioWebTestCaseException( element=element_partial_link_text) def find_element_by_id_with_timeout(self, element_id, timeout=30, text=""): """ Find an element by id. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. If the parameter text is provided, the function waits until the element is found and its content is equal to the given text. If the element's text is not equal to the given text an exception will be raised and the result of this comparison will be stored in the errors list #NOTE: Currently this is used to wait for an element's text to be refreshed using JavaScript @param element_id: id of the element to find @type element_id: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int @param text: expected text inside the given element. @type text: string """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_id(element_id)) except: raise InvenioWebTestCaseException(element=element_id) if text: q = self.browser.find_element_by_id(element_id) try: # if the element's text is not equal to the given text, an # exception will be raised WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_id(element_id) and q.text == text) except: # let's store the result of the comparison in the errors list try: self.assertEqual(q.text, text) except AssertionError, e: self.errors.append(str(e)) def find_element_by_xpath_with_timeout(self, element_xpath, timeout=30): """ Find an element by xpath. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. @param element_xpath: xpath of the element to find @type element_xpath: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_xpath(element_xpath)) except: raise InvenioWebTestCaseException(element=element_xpath) def find_elements_by_class_name_with_timeout(self, element_class_name, timeout=30): """ Find an element by class name. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if it finds the element will return it in 0 - timeout seconds. @param element_class_name: class name of the element to find @type element_class_name: string @param timeout: time in seconds before throwing an exception if the element is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.find_element_by_class_name(element_class_name)) except: raise InvenioWebTestCaseException(element=element_class_name) def find_page_source_with_timeout(self, timeout=30): """ Find the page source. This waits up to 'timeout' seconds before throwing an InvenioWebTestCaseException or if the page source is loaded will return it in 0 - timeout seconds. @param timeout: time in seconds before throwing an exception if the page source is not found @type timeout: int """ try: WebDriverWait(self.browser, timeout).until( lambda driver: driver.page_source) except: raise InvenioWebTestCaseException(element="page source") def login(self, username="guest", password="", force_ln='en', go_to_login_page=True): """ Login function @param username: the username (nickname or email) @type username: string @param password: the corresponding password @type password: string @param force_ln: if the arrival page doesn't use the corresponding language, then the browser will redirect to it. @type force_ln: string @param go_to_login_page: if True, look for login link on the page. Otherwise expect to be already on a page with the login form @type go_to_login_page: bool """ if go_to_login_page: if not "You can use your nickname or your email address to login." in self.browser.page_source: if "You are no longer recognized by our system" in self.browser.page_source: self.find_element_by_link_text_with_timeout("login here") self.browser.find_element_by_link_text( "login here").click() else: self.find_element_by_link_text_with_timeout("login") self.browser.find_element_by_link_text("login").click() self.find_element_by_name_with_timeout("nickname") self.browser.find_element_by_name("nickname").clear() self.fill_textbox(textbox_name="nickname", text=username) self.find_element_by_name_with_timeout("password") self.browser.find_element_by_name("password").clear() self.fill_textbox(textbox_name="password", text=password) self.find_element_by_name_with_timeout("action") self.browser.find_element_by_name("action").click() from invenio.config import CFG_SITE_NAME_INTL if force_ln and CFG_SITE_NAME_INTL[force_ln] not in self.browser.page_source: splitted_url = list(urlsplit(self.browser.current_url)) query = cgi.parse_qs(splitted_url[3]) query.update({u'ln': unicode(force_ln)}) splitted_url[3] = urlencode(query) new_url = urlunsplit(splitted_url) self.browser.get(new_url) def logout(self): """ Logout function """ self.find_element_by_link_text_with_timeout("logout") self.browser.find_element_by_link_text("logout").click() @nottest def element_value_test(self, element_name="", element_id="", expected_element_value="", unexpected_element_value="", in_form=True): """ Function to check if the value in the given element is the expected (unexpected) value or not @param element_name: name of the corresponding element in the form @type element_name: string @param element_id: id of the corresponding element in the form @type element_id: string @param expected_element_value: the expected element value @type expected_element_value: string @param unexpected_element_value: the unexpected element value @type unexpected_element_value: string @param in_form: depends on this parameter, the value of the given element is got in a different way. If it is True, the given element is a textbox or a textarea in a form. @type in_form: boolean """ if element_name: self.find_element_by_name_with_timeout(element_name) q = self.browser.find_element_by_name(element_name) elif element_id: self.find_element_by_id_with_timeout(element_id) q = self.browser.find_element_by_id(element_id) if unexpected_element_value: try: if in_form: self.assertNotEqual( q.get_attribute('value'), unexpected_element_value) else: self.assertNotEqual(q.text, unexpected_element_value) except AssertionError, e: self.errors.append(str(e)) if expected_element_value: try: if in_form: self.assertEqual( q.get_attribute('value'), expected_element_value) else: self.assertEqual(q.text, expected_element_value) except AssertionError, e: self.errors.append(str(e)) @nottest def page_source_test(self, expected_text="", unexpected_text=""): """ Function to check if the current page contains the expected text (unexpected text) or not. The expected text (unexpected text) can also be a link. The expected text (unexpected text) can be a list of strings in order to check multiple values inside same page @param expected_text: the expected text @type expected_text: string or list of strings @param unexpected_text: the unexpected text @type unexpected_text: string or list of strings """ self.find_page_source_with_timeout() if unexpected_text: if isinstance(unexpected_text, str): unexpected_texts = [unexpected_text] else: unexpected_texts = unexpected_text for unexpected_text in unexpected_texts: try: self.assertEqual( -1, self.browser.page_source.find(unexpected_text)) except AssertionError, e: self.errors.append(str(e)) if expected_text: if isinstance(expected_text, str): expected_texts = [expected_text] else: expected_texts = expected_text for expected_text in expected_texts: try: self.assertNotEqual( -1, self.browser.page_source.find(expected_text)) except AssertionError, e: self.errors.append(str(e)) def choose_selectbox_option_by_label(self, selectbox_name="", selectbox_id="", label=""): """ Select the option at the given label in the corresponding select box @param selectbox_name: the name of the corresponding select box in the form @type selectbox_name: string @param selectbox_id: the id of the corresponding select box in the form @type selectbox_id: string @param label: the option at this label will be selected @type label: string """ if selectbox_name: self.find_element_by_name_with_timeout(selectbox_name) selectbox = self.browser.find_element_by_name(selectbox_name) elif selectbox_id: self.find_element_by_id_with_timeout(selectbox_id) selectbox = self.browser.find_element_by_id(selectbox_id) options = selectbox.find_elements_by_tag_name("option") for option in options: if option.text == label: option.click() break def choose_selectbox_option_by_index(self, selectbox_name="", selectbox_id="", index=""): """ Select the option at the given index in the corresponding select box @param selectbox_name: the name of the corresponding select box in the form @type selectbox_name: string @param selectbox_id: the id of the corresponding select box in the form @type selectbox_id: string @param index: the option at this index will be selected @type index: int """ if selectbox_name: self.find_element_by_name_with_timeout(selectbox_name) selectbox = self.browser.find_element_by_name(selectbox_name) elif selectbox_id: self.find_element_by_id_with_timeout(selectbox_id) selectbox = self.browser.find_element_by_id(selectbox_id) options = selectbox.find_elements_by_tag_name("option") options[int(index)].click() def choose_selectbox_option_by_value(self, selectbox_name="", selectbox_id="", value=""): """ Select the option at the given value in the corresponding select box @param selectbox_name: the name of the corresponding select box in the form @type selectbox_id: string @param selectbox_id: the id of the corresponding select box in the form @type selectbox_id: string @param value: the option at this value will be selected @type value: string """ if selectbox_name: self.find_element_by_name_with_timeout(selectbox_name) selectbox = self.browser.find_element_by_name(selectbox_name) elif selectbox_id: self.find_element_by_id_with_timeout(selectbox_id) selectbox = self.browser.find_element_by_id(selectbox_id) options = selectbox.find_elements_by_tag_name("option") for option in options: if option.get_attribute('value') == value: option.click() break def fill_textbox(self, textbox_name="", textbox_id="", text=""): """ Fill in the input textbox or textarea with the given text @param textbox_name: the name of the corresponding textbox or text area in the form @type textbox_name: string @param textbox_id: the id of the corresponding textbox or text area in the form @type textbox_id: string @param text: the information that the user wants to send @type text: string """ if textbox_name: self.find_element_by_name_with_timeout(textbox_name) textbox = self.browser.find_element_by_name(textbox_name) elif textbox_id: self.find_element_by_id_with_timeout(textbox_id) textbox = self.browser.find_element_by_id(textbox_id) textbox.send_keys(text) def handle_popup_dialog(self): """ Access the alert after triggering an action that opens a popup. """ try: alert = self.browser.switch_to_alert() alert.accept() except: pass class InvenioWebTestCaseException(Exception): """This exception is thrown if the element we are looking for is not found after a set time period. The element is not found because the page needs more time to be fully loaded. To avoid this exception, we should increment the time period for that element in the corresponding function. See also: find_element_by_name_with_timeout() find_element_by_link_text_with_timeout() find_element_by_partial_link_text_with_timeout() find_element_by_id_with_timeout() find_element_by_xpath_with_timeout() find_elements_by_class_name_with_timeout() find_page_source_with_timeout() """ def __init__(self, element): """Initialisation.""" self.element = element self.message = "Time for finding the element '%s' has expired" % self.element def __str__(self): """String representation.""" return repr(self.message) #@nottest def build_and_run_flask_test_suite(): """ Detect all Invenio modules with names ending by '*_flask_tests.py', build a complete test suite of them, and run it. Called by 'inveniocfg --run-flask-tests'. """ test_modules = [] import invenio for candidate in os.listdir(os.path.dirname(invenio.__file__)): base, ext = os.path.splitext(candidate) if ext != '.py' or not base.endswith('_flask_tests'): continue module = __import__( 'invenio.' + base, globals(), locals(), ['TEST_SUITE']) test_modules.append(module.TEST_SUITE) # FIXME create warning about tested databases warn_user_about_tests() complete_suite = unittest.TestSuite(test_modules) run_test_suite(complete_suite) from invenio.base.utils import import_submodules_from_packages def iter_suites(): """Yields all testsuites.""" app = create_app() packages = ['invenio'] + app.config.get('PACKAGES', []) for module in import_submodules_from_packages('testsuite', packages=packages): if not module.__name__.split('.')[-1].startswith('test_'): continue if hasattr(module, 'TEST_SUITE'): yield module.TEST_SUITE def suite(): """A testsuite that has all the tests.""" #setup_path() suite = unittest.TestSuite() for other_suite in iter_suites(): suite.addTest(other_suite) return suite def main(): """Runs the testsuite as command line application.""" try: unittest.main(defaultTest='suite') except Exception as e: print('Error: %s' % e) diff --git a/invenio/testsuite/test_datastructures.py b/invenio/testsuite/test_utils_datastructures.py similarity index 72% rename from invenio/testsuite/test_datastructures.py rename to invenio/testsuite/test_utils_datastructures.py index bb637d13d..0d93b53b7 100644 --- a/invenio/testsuite/test_datastructures.py +++ b/invenio/testsuite/test_utils_datastructures.py @@ -1,97 +1,132 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 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. """ Test unit for the miscutil/datastructures module. """ -from invenio.utils.datastructures import LazyDict, LaziestDict +from invenio.utils.datastructures import LazyDict, LaziestDict, SmartDict from invenio.testsuite import make_test_suite, run_test_suite, InvenioTestCase class CallCounter(object): """Counts number of calls.""" def __init__(self, populate): self.counter = 0 self.populate = populate def __call__(self, *args, **kwargs): self.counter = self.counter + 1 return self.populate(*args, **kwargs) class TestLazyDictionaries(InvenioTestCase): """ Lazy dictionaries TestSuite. """ def test_lazy_dictionary(self): """Checks content of lazy dictionary and number of evaluations.""" populate = CallCounter(lambda: {'foo': 'bar', 1: 11, 'empty': None}) lazy_dict = LazyDict(populate) self.assertEqual(populate.counter, 0) self.assertEqual(lazy_dict['foo'], 'bar') self.assertEqual(populate.counter, 1) self.assertEqual(lazy_dict[1], 11) self.assertEqual(populate.counter, 1) self.assertEqual(lazy_dict['empty'], None) self.assertEqual(populate.counter, 1) # clear the cache lazy_dict.expunge() self.assertEqual(lazy_dict['foo'], 'bar') self.assertEqual(populate.counter, 2) def test_lazies_dictionary(self): populate = CallCounter(lambda k: {'foo': 'bar', 1: 11, 'empty': None}[k]) laziest_dict = LaziestDict(populate) self.assertEqual(populate.counter, 0) self.assertEqual(laziest_dict['foo'], 'bar') self.assertEqual(laziest_dict.keys(), ['foo']) self.assertEqual(populate.counter, 1) self.assertEqual(laziest_dict[1], 11) self.assertEqual(laziest_dict.keys(), [1, 'foo']) self.assertEqual(populate.counter, 2) self.assertEqual(laziest_dict['empty'], None) self.assertEqual(laziest_dict.keys(), [1, 'foo', 'empty']) self.assertEqual(populate.counter, 3) # cached result will not cause new call self.assertEqual(laziest_dict['foo'], 'bar') self.assertEqual(populate.counter, 3) # not existing key cause new call (even multiple times) self.assertEqual(laziest_dict.get('does not exists', -1), -1) self.assertEqual(populate.counter, 4) self.assertEqual(laziest_dict.get('does not exists'), None) self.assertEqual(populate.counter, 5) +class TestSmartDict(InvenioTestCase): + """ + Smart Dictionary TestSuite + """ + + def test_smart_dict(self): + d = SmartDict() + + d['foo'] = {'a': 'world', 'b':'hello'} + d['a'] = [ {'b':1}, {'b':2}, {'b':3} ] + self.assertEqual(d.keys(), ['a', 'foo']) + self.assertTrue('foo.a' in d) + del d['foo'] + self.assertEqual(d.keys(), ['a']) + self.assertEqual(d['a'], [{'b':1}, {'b':2}, {'b':3}]) + self.assertEqual(d['a[0]'], {'b':1}) + self.assertEqual(d['a.b'], [1,2,3]) + self.assertEqual(d['a[1:]'], [{'b':2}, {'b':3}]) + + d.set('a', {'b':4}, extend=True) + self.assertEqual(d['a'], [{'b':1}, {'b':2}, {'b':3}, {'b':4}]) + d.set('a', [ {'b':1}, {'b':2}, {'b':3} ], extend=False) + self.assertEqual(d['a'], [{'b':1}, {'b':2}, {'b':3}]) + + self.assertEqual(d.get('does not exists'), None) + + d = SmartDict() + d.set('a.b.c[n]', 'foo', True) + self.assertEqual(d['a.b.c'], ['foo']) + d.set('a.b.c[n]', 'bar', True) + self.assertEqual(d['a.b.c'], ['foo', 'bar']) + d.set('a.b.c', ['foo'], False) + self.assertEqual(d['a.b.c'], ['foo']) + + TEST_SUITE = make_test_suite(TestLazyDictionaries, ) if __name__ == "__main__": run_test_suite(TEST_SUITE) diff --git a/invenio/utils/datastructures.py b/invenio/utils/datastructures.py index 33b6818a2..40b0ac7d9 100644 --- a/invenio/utils/datastructures.py +++ b/invenio/utils/datastructures.py @@ -1,352 +1,335 @@ # -*- coding: utf-8 -*- ## This file is part of Invenio. ## Copyright (C) 2012, 2013 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. """ Invenio special data structures """ class LazyDict(object): """ Lazy dictionary that evaluates its content when it is first accessed. Example of use: def my_dict(): from werkzeug.utils import import_string return {'foo': import_string('foo')} lazy_dict = LazyDict(my_dict) # at this point the internal dictionary is empty lazy_dict['foo'] """ def __init__(self, function=dict): """ :param function: it must return a dictionary like structure """ super(LazyDict, self).__init__() self._cached_dict = None self._function = function def _evaluate_function(self): self._cached_dict = self._function() def __getitem__(self, key): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.__getitem__(key) def __setitem__(self, key, value): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.__setitem__(key, value) def __getattr__(self, key): if self._cached_dict is None: self._evaluate_function() return getattr(self._cached_dict, key) def __iter__(self): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.__iter__() def iteritems(self): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.iteritems() def iterkeys(self): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.iterkeys() def itervalues(self): if self._cached_dict is None: self._evaluate_function() return self._cached_dict.itervalues() def expunge(self): self._cached_dict = None def get(self, key, default=None): try: return self.__getitem__(key) except KeyError: return default class LaziestDict(LazyDict): """ Even lazier dictionary (maybe the laziest), it doesn't have content and when a key is accessed it tries to evaluate only this key. Example of use: def reader_discover(key): from werkzeug.utils import import_string - return import_string('invenio.legacy.bibfield.%sreader:reader' % (key)) + return import_string('invenio.jsonalchemy.jsonext.readers%sreader:reader' % (key)) laziest_dict = LaziestDict(reader_discover) laziest_dict['json'] # It will give you the JsonReader class """ def __init__(self, function): """ @param function: it must accept one parameter (the key of the dictionary) and returns the element which will be store that key. """ super(LaziestDict, self).__init__(function) def _evaluate_function(self): """ It doesn't know how to create the full dictionary, in case is really needed an empty dictionary is created. """ if self._cached_dict is None: self._cached_dict = {} def __getitem__(self, key): if self._cached_dict is None: self._evaluate_function() if not key in self._cached_dict: try: self._cached_dict.__setitem__(key, self._function(key)) except: raise KeyError(key) return self._cached_dict.__getitem__(key) def __contains__(self, key): if self._cached_dict is None: self._evaluate_function() if not key in self._cached_dict: try: self.__getitem__(key) except: return False return True import re class SmartDict(object): """ This dictionary allows to do some 'smart queries' to its content:: >>> d = SmartDict() >>> d['foo'] = {'a': 'world', 'b':'hello'} >>> d['a'] = [ {'b':1}, {'b':2}, {'b':3} ] >>> d['a'] [ {'b':1}, {'b':2}, {'b':3} ] >>> d['a[0]'] {'b':1} >>> d['a.b'] [1,2,3] >>> d['a[1:]'] [{'b':2}, {'b':3}] """ split_key_pattern = re.compile('\.|\[') main_key_pattern = re.compile('\..*|\[.*') def __init__(self, d=None): self._dict = d if not d is None else dict() def __getitem__(self, key): """ As in C{dict.__getitem__} but using 'smart queries' NOTE: accessing one value in a normal way, meaning d['a'], is almost as fast as accessing a regular dictionary. But using the special name convention is a bit slower than using the regular access:: %timeit x = dd['a[0].b'] 100000 loops, best of 3: 3.94 µs per loop %timeit x = dd['a'][0]['b'] 1000000 loops, best of 3: 598 ns per loop """ def getitem(k, v): if isinstance(v, dict): return v[k] elif ']' in k: k = k[:-1].replace('n', '-1') #Work around for list indexes and slices try: return v[int(k)] except ValueError: return v[slice(*map(lambda x: int(x.strip()) if x.strip() else None, k.split(':')))] else: tmp = [] for inner_v in v: tmp.append(getitem(k, inner_v)) return tmp #Check if we are using python regular keys try: return self._dict[key] except KeyError: pass keys = SmartDict.split_key_pattern.split(key) value = self._dict for k in keys: value = getitem(k, value) return value def __setitem__(self, key, value, extend=False): - """As in C{dict.__getitem__} but using 'smart queries'""" #TODO: Check repeatable fields - if '.' not in key and ']' not in key: + if '.' not in key and ']' not in key and not extend: self._dict[key] = value else: keys = SmartDict.split_key_pattern.split(key) self.__setitem(self._dict, keys[0], keys[1:], value, extend) def __delitem__(self, key): - """ - As in C{dict.__delitem__}. - - None: It only works with first keys - """ + """Note: It only works with first keys""" del self._dict[key] def __contains__(self, key): - """ - As in C{dict.__contains__} but using 'smart queries'. - """ + if '.' not in key and '[' not in key: return key in self._dict try: self[key] except: return False return True def __eq__(self, other): - """@see C{dict.__eq__}""" return (isinstance(other, self.__class__) and self._dict == other._dict) def __iter__(self): - """@see C{dict.__iter__}""" return iter(self._dict) def __len__(self): - """@see C{dict.__len__}""" return len(self._dict) def keys(self): - """@see C{dict.keys}""" return self._dict.keys() + def items(self): + return self._dict.items() + def iteritems(self): - """@see C{dict.iteritems}""" return self._dict.iteritems() def iterkeys(self): - """@see C{dict.iterkeys}""" return self._dict.iterkeys() def itervalues(self): - """@see C{dict.itervalues}""" return self._dict.itervalues() def has_key(self, key): - """ - As in C{dict.has_key} but using BibField name convention. - @see __contains__(self, key) - """ return key in self def __repr__(self): return repr(self._dict) def __setitem(self, chunk, key, keys, value, extend=False): """ Helper function to fill up the dictionary""" def setitem(chunk): if keys: return self.__setitem(chunk, keys[0], keys[1:], value, extend) else: return value if ']' in key: # list key = int(key[:-1].replace('n', '-1')) if extend: if chunk is None: chunk = [None, ] else: if not isinstance(chunk, list): chunk = [chunk, ] if key != -1: chunk.insert(key, None) else: chunk.append(None) else: if chunk is None: chunk = [None, ] chunk[key] = setitem(chunk[key]) else: # dict if extend: if chunk is None: chunk = {} chunk[key] = None chunk[key] = setitem(chunk[key]) elif not key in chunk: chunk[key] = None chunk[key] = setitem(chunk[key]) else: - if keys and ']' in keys[0]: + if keys: chunk[key] = setitem(chunk[key]) else: if not isinstance(chunk[key], list): chunk[key] = [chunk[key],] chunk[key].append(None) chunk[key][-1] = setitem(chunk[key][-1]) else: if chunk is None: chunk = {} if key not in chunk: chunk[key] = None chunk[key] = setitem(chunk[key]) return chunk def get(self, key, default=None): - """docstring for get""" try: return self[key] except: return default def set(self, key, value, extend=False): - """docstring for set""" self.__setitem__(key, value, extend) - def update(self, new): - self._dict.update(new) + def update(self, E, **F): + self._dict.update(E, **F) def flatten_multidict(multidict): return dict([(key, value if len(value) > 1 else value[0]) for (key, value) in multidict.iterlists()]) diff --git a/invenio/utils/jsonalchemy/engines/__init__.py b/invenio/utils/jsonalchemy/engines/__init__.py deleted file mode 100644 index eb537e874..000000000 --- a/invenio/utils/jsonalchemy/engines/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 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. diff --git a/invenio/utils/jsonalchemy/wrappers.py b/invenio/utils/jsonalchemy/wrappers.py deleted file mode 100644 index 5b711c95b..000000000 --- a/invenio/utils/jsonalchemy/wrappers.py +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- - -## This file is part of Invenio. -## Copyright (C) 2013 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. - -import re - -from invenio.base.utils import try_to_eval -from invenio.utils.datastructures import SmartDict - - -class SmartJson(SmartDict): - """ - - """ - - def __init__(self, json=None, bson=None): - super(SmartJson, self).__init__(json) - self._dict_bson = bson if not bson is None else SmartDict() - if '__meta_metadata__' in bson: - try: - self._dict['__meta_metadata__'].update(bson.get('__meta_metadata__', {})) - except KeyError: - self._dict['__meta_metadata__'] = bson.get('__meta_metadata__', {}) - - def __getitem__(self, key): - """ - - """ - try: - return self._dict_bson[key] - except KeyError: - #We will try to fin the key inside the json dict and load it - pass - - main_key = SmartDict.main_key_pattern.sub('', key) - if main_key in self._dict['__meta_metadata__']['__aliases__']: - try: - rest_of_key = SmartDict.main_key_pattern.findall(key)[0] - except IndexError: - rest_of_key = '' - return self[self._dict['__meta_metadata__']['__aliases__'][main_key] + rest_of_key] - - - if main_key in self._dict['__meta_metadata__']['__do_not_dump__']: - self._dict_bson[main_key] = try_to_eval(self._dict['__meta_metadata__'][main_key]['function'], - self.field_function_context, self=self) - else: - try: - load_function = self._dict['__meta_metadata__'][main_key]['loads'] or 'lambda x:x' # Never going to be used, jus for security - load_function = try_to_eval(load_function) - self._dict_bson[main_key] = load_function(self._dict[main_key]) - except KeyError: - self._dict_bson[main_key] = self._dict[main_key] - - return self._dict_bson[key] - - - def __setitem__(self, key, value, extend=False): - """ - - """ - self._dict_bson.set(key, value, extend) - main_key = SmartDict.main_key_patter.sub('', key) - try: - dump_function = self._dict['__meta_metadata__'][main_key]['dumps'] - if dump_function: - self._dict[main_key] = dump_function(self._dict_bson[main_key]) - else: - self._dict[main_key] = self._dict_bson[main_key] - except KeyError: - self._dict[main_key] = self._dict_bson[main_key] - - def invalidate_cache(self, key): - """ - - """ - main_key = SmartDict.main_key_patter.sub('', key) - del self._dict_bson[key] - - def dumps(self): - """docstring for dumps""" - for key in self._dict_bson: - try: - if key in self._dict['__meta_metadata__']['__do_not_dump__']: - self._dict[key] = None - continue - dump_function = self._dict['__meta_metadata__'][key]['dumps'] - if dump_function: - dump_function = try_to_eval(dump_function) - self._dict[key] = dump_function(self._dict_bson[key]) - else: - self._dict[key] = self._dict_bson[key] - except KeyError, e: - self._dict[key] = self._dict_bson[key] - return self._dict - - def save(self): - """docstring for save""" - raise NotImplementedError() - - @property - def field_function_context(self): - """docstring for field_function_context""" - raise NotImplementedError()