Commit 717905ca authored by LE GAC Renaud's avatar LE GAC Renaud
Browse files

Merge branch '67-affiliation-rules' into 'master'

67 affiliation rules

* New affiliation mechanism.
* Add the affiliation table `affiliation_keys`.
* Remove obsolete preferences `inspirehep_institute_id` and `add_rules_reg_institute`.
* Introduce iterator to scan authors list and their affiliations.
* Update the documentation.
* Add the script `fix_affiliation_keys_0960.py` to migrate existing database.
* Close #67.

See merge request !76
parents a49cb1c5 67991f2d
......@@ -26,7 +26,7 @@ from plugin_dbui import (get_id,
UNDEF_ID)
MODE_DRY_RUN = T(DRY_RUN)
MSG_NO_REG_INSTITUTE = "Preference REG_INSTITUTE is not defined."
MSG_NO_AFFILIATION = "Affiliation keys are not defined !!!"
MSG_NO_HARVESTER = "No harvesters for your selection !!!"
MSG_NO_RECORD = "Sorry, the record does not exist."
......@@ -35,8 +35,8 @@ def free_run():
All harvester parameters are defined via the selector.
"""
if not current.app.inspirehep_institute_id:
return inline_alert(T("Error"), T(MSG_NO_REG_INSTITUTE))
if db(db.affiliation_keys.id > 0).count() == 0:
return inline_alert(T("Error"), T(MSG_NO_AFFILIATION))
table = virtdb.free_harvester_selector
fields = ('collections',
......@@ -95,8 +95,8 @@ def edit_insert():
no checks are run. The user is editing the record to fix problems.
"""
if not current.app.inspirehep_institute_id:
return inline_alert(T("Error"), T(MSG_NO_REG_INSTITUTE))
if db(db.affiliation_keys.id > 0).count() == 0:
return inline_alert(T("Error"), T(MSG_NO_AFFILIATION))
fields = ('controller',
'host',
......@@ -285,8 +285,8 @@ def insert_marcxml():
"""Insert a MarcXML record in the database.
"""
if not current.app.inspirehep_institute_id:
return inline_alert(T("Error"), T(MSG_NO_REG_INSTITUTE))
if db(db.affiliation_keys.id > 0).count() == 0:
return inline_alert(T("Error"), T(MSG_NO_AFFILIATION))
try:
selector = Selector(virtdb.marc12_selector, exclude_fields=('mode'))
......@@ -333,8 +333,8 @@ def run():
Search arguments are defined via the harvester selector.
"""
if not current.app.inspirehep_institute_id:
return inline_alert(T("Error"), T(MSG_NO_REG_INSTITUTE))
if db(db.affiliation_keys.id > 0).count() == 0:
return inline_alert(T("Error"), T(MSG_NO_AFFILIATION))
try:
selector = Selector(virtdb.harvester_selector,
......@@ -387,8 +387,8 @@ def run_all():
"""Run all harvesters in one go.
"""
if not current.app.inspirehep_institute_id:
return inline_alert(T("Error"), T(MSG_NO_REG_INSTITUTE))
if db(db.affiliation_keys.id > 0).count() == 0:
return inline_alert(T("Error"), T(MSG_NO_AFFILIATION))
collection_logs = []
logs = []
......
......@@ -8,6 +8,7 @@ import re
from check_tools import check_publication
from gluon.storage import Storage
from harvest_tools import DRY_RUN
from invenio_tools import CdsException, load_record, Marc12Exception
from plugin_dbui import (CALLBACK_ERRORS,
inline_alert,
is_foreign_field,
......@@ -21,6 +22,105 @@ MODE_DRY_RUN = T(DRY_RUN)
MSG_NO_AUTHORS = "<br><br>Removing affiliation failed.<br>"\
"Use INSPIRES instead with the tool 'insert MARCXML'"
MSG_EXISTING_KEY = "Keys already exist!"
MSG_NO_AUTHOR = "Author not found!"
MSG_NO_INSTITUTE = "Institute not found in the inspirehep database!"
MSG_NO_PUBLICATION = "Publication not found!"
MSG_NO_SERVER = "Server is not reachable or respond badly!"
def affiliation_institute():
"""Determine affiliation keys using the institute database.
"""
# shortcuts
institute_id = request.vars.institute_id
# find the record for the institute
try:
record = load_record("inspirehep.net", institute_id)
except (CdsException, Marc12Exception):
raise HTTP(500, T(MSG_NO_SERVER))
if record is None:
raise HTTP(500, T(MSG_NO_INSTITUTE))
# extract keys defining the affiliation
# u and v are the main keys use in inspirehep and cds
# b is uses by some note in Atlas
key_1 = dict(key_u=record[u"110"]["u"], key_v="")
key_2 = dict(key_u=record[u"110"]["t"], key_v="")
key_3 = dict(key_u=record[u"110"]["b"], key_v="")
# check that the rules does not exist
# load new rules
is_key_add = False
for key in (key_1, key_2, key_3):
if get_id(db.affiliation_keys, **key) is None:
db.affiliation_keys[0] = key
is_key_add = True
if not is_key_add:
raise HTTP(500, T(MSG_EXISTING_KEY))
return
def affiliation_publication():
"""Determine affiliation keys using a given publication.
"""
# shortcuts
vars = request.vars
family_name = vars.family_name.strip()
first_name = vars.first_name.strip()
publication_id = vars.publication_id
publication_store = vars.publication_store
# find the publication
try:
record = load_record(publication_store, publication_id)
except (CdsException, Marc12Exception):
raise HTTP(500, T(MSG_NO_SERVER))
if record is None:
raise HTTP(500, T(MSG_NO_PUBLICATION))
# find the author
# test all possible patterns (John, Doe; Doe, John; J. Doe; Doe, J. etc)
# for simplicity ignore composed name, e.g. Jean-Pierre
pattern = "^%s, *%s$" % (family_name, first_name)
pattern = "%s|^%s, *%s$" % (pattern, first_name, family_name)
pattern = "%s|^%s, *%s\.?$" % (pattern, family_name, first_name[0])
pattern = "%s|^%s\.?, *%s$" % (pattern, first_name[0], family_name)
reg = re.compile(pattern, re.IGNORECASE)
key = None
if "700" in record and isinstance(record[u"700"], list):
for di in record[u"700"]:
author = di["a"]
if reg.match(author):
if "v" in di:
key = dict(key_u=di["u"], key_v=di["v"])
else:
key = dict(key_u=di["u"], key_v="")
break
if key is None:
raise HTTP(500, T(MSG_NO_AUTHOR))
# check that the rules does not exist
# load new rules
if get_id(db.affiliation_keys, **key) is None:
db.affiliation_keys[0] = key
else:
raise HTTP(500, T(MSG_EXISTING_KEY))
return
def check_validate():
"""Check and validate publication records.
......
docs/db_schema/database.png

167 KB | W: | H:

docs/db_schema/database.png

172 KB | W: | H:

docs/db_schema/database.png
docs/db_schema/database.png
docs/db_schema/database.png
docs/db_schema/database.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -549,4 +549,18 @@
<part>id</part>
</key>
</table>
<table x="643" y="492" name="affiliation_keys">
<row name="id" null="1" autoincrement="1">
<datatype>integer</datatype>
<default>NULL</default></row>
<row name="key_u" null="0" autoincrement="0">
<datatype>string</datatype>
</row>
<row name="key_v" null="1" autoincrement="0">
<datatype>string</datatype>
<default>NULL</default></row>
<key type="PRIMARY" name="">
<part>id</part>
</key>
</table>
</sql>
......@@ -257,7 +257,7 @@ latex_elements = {
# 'preamble': '',
# Latex figure (float) alignment
'figure_align': 'H',
'figure_align': 'htb',
# fancy chapter title
'fncychap': '\\usepackage[Glenn]{fncychap}',
......
APC,`910133 <http://inspirehep.net/record/910133>`_
CENBG,`904493 <http://inspirehep.net/record/904493>`_
CPPM,`902989 <http://inspirehep.net/record/902989>`_
CSNSM,`907588 <http://inspirehep.net/record/907588>`_
GANIL,`903453 <http://inspirehep.net/record/903453>`_
IMNC,
IPHC,`911366 <http://inspirehep.net/record/911366>`_
IPNL,`902974 <http://inspirehep.net/record/902974>`_
IPNO,`903099 <http://inspirehep.net/record/903099>`_
LAL,`903100 <http://inspirehep.net/record/903100>`_
LAPP,`903421 <http://inspirehep.net/record/903421>`_
LLR,`902786 <http://inspirehep.net/record/902786>`_
LMA,`911249 <http://inspirehep.net/record/911249>`_
LPC Caen,`902703 <http://inspirehep.net/record/902703>`_
LPSC, `902828 <http://inspirehep.net/record/902828>`_
\ No newline at end of file
LPC Clermont,`902740 <http://inspirehep.net/record/902740>`_
LPNHE,`902786 <http://inspirehep.net/record/902786>`_
LPSC, `902828 <http://inspirehep.net/record/902828>`_
LUPM,`1201986 <http://inspirehep.net/record/1201986>`_
LSM,`907247 <http://inspirehep.net/record/907247>`_
Subatech,`906885 <http://inspirehep.net/record/906885>`_
.. include:: hyperlinks.txt
.. _harvester_affiliation:
Les clés définissant l'affiliation de vos auteurs
--------------------------------------------------
Dans le format `MARC`_ les auteurs sont définis dans les champ ``100`` et
``700``. Pour chacun d'eux, la clé ``a`` pointe sur le nom de l'auteur et
la clé ``u`` sur sont affiliation. La clé ``v`` n'est pas défini dans le
standard, mais elle est utilisé par certaine collaboration pour compléter
l'affiliation.
Les valeur de la clé ``u`` (``v``) dépendent de l'entrepôt et de la
collaboration. Elles peuvent aussi évoluées avec le temps.
Dans ``Limbra`` un méchanisme est mis en place pour traiter tous les cas de
figures sans limitations.
La(es) clé(s) de base
^^^^^^^^^^^^^^^^^^^^^
Dans l'entrepôt `inspirehep.net`_ il existe une fiche par laboratoire. Elle
contient la clé pour l'affiliation. Cette clé est utilisé pour la majorité
des publications dans les entrepôts `cds.cern.ch`_ et `inspirehep.net`_.
La méthode pour récupérer cette clé est la suivante :
* lancer l'action :
.. line-block::
``Les données du laboratoire > clés definissant l'affiliation``
* Ouvrir le menu contextuel (click droit) et lancer l'assistant:
.. index::
pair: clés definissant l'affiliation; ajouter des clés à partir des fiches laboratoire
.. line-block::
``ajouter des clés à partir des fiches laboratoire``.
* Après quelques explications, l'assistant vous demande l'identifiant
de votre laboratoire dans la base de données `inspirehep.net`_.
Les :ref:`institutes` sont donnés en annexe.
Mon laboratoire est affilié à deux institutions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C'est par exemple le cas du ``LPC Caen`` qui est affilié d'une part au
laboratoire de l'IN2P3 et d'autre part à l'école d'ingénieur ENSICAEN.
Pour chaque institution, il suffit de lancer l'action décrite dans
la section précédente.
.. note::
Cette méthode est valable si le nom de mon laboratoire change au court
du temps
.. note::
Il n'y a pas de limitation sur le nombre d'institutions.
La collaboration BaBar utilisent des clés différentes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C'est par exemple le cas du ``LAL`` qui a un équipe qui a travaillé dans
l'expérience BaBar.
La clé de base du laboratoire est ``Orsay, LAL``. Par contre la collaboration
BaBar utilise les identifiant suivant :
.. line-block::
``u`` = *Orsay*
``v`` = *Laboratoire de l'Accelerateur Lineaire, IN2P3/CNRS et Universite Paris-Sud 11,Centre Scientifique d'Orsay, F-91898 Orsay Cedex, France*
Pour intégrer cette clé spécifique :
* lancer l'action :
.. line-block::
``Les données du laboratoire > clés definissant l'affiliation``
* Ouvrir le menu contextuel (click droit) et lancer l'assistant
.. index::
pair: clés definissant l'affiliation; ajouter des clés à partir d'une publication
.. line-block::
``ajouter des clés à partir d'une publication``.
* Après quelques explications, l'assistant vous demande de sélectioner
une publication puis un auteur.
.. note::
Cette méthode permet de récupérer toutes les clés spécifiques :
changement de la clé dans `cds.cern.ch`_ mais pas dans
`inspirehep.net`_, collaboration qui utilise un clé différent que la
clé de base, *etc*.
\ No newline at end of file
......@@ -52,30 +52,57 @@ Trois actions sont alors possible:
.. important::
En annexe sont détailées les :ref:`harvester_processing`.
L'identifiant du laboratoire
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Afin de trouver dans les entrepôts, les notices signées par les membres
du laboratoire, il est impératif de définir *l'identifiant* du laboratoire :
Les clés définissant l'affiliation de vos auteurs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dans les entrepôts `cds.cern.ch`_ et `inspirehep.net`_, l'affiliation de chaque
auteur est définie par une clé. Elle est souvent construite à partir de la ville
et du sigle du laboratoire, par exemple ``CPPM, Marseille``.
* A partir du panneau de navigation rapide, accéder à la table qui continent
les paramètres de configuration de l'application :
Afin de trouver les notices signées par les membres de votre laboratoire,
il faut identifier la clé de base associée à votre laboratoire :
* A partir du panneau de navigation rapide, lancer l'action :
.. index::
pair: préférences; inspirehep_institute_id
pair: Les données du laboratoire; clés definissant l'affiliation
.. line-block::
``Les données du laboratoire > clés definissant l'affiliation``
* Ouvrir le menu contextuel (click droit) et lancer l'assistant
.. line-block::
``Application > préférences``
``ajouter des clés à partir des fiches laboratoire``.
.. _fig-wizard-affiliation:
* Editer la propriété ``inspirehep_institute_id`` et la remplir avec le
numéro de la notice décrivant le laboratoire dans l'entrepôt
`inspirehep.net`_.
* Redémarrer l'applciation.
.. figure:: images/wizard-affiliation.png
:align: center
:width: 60%
Le menu contextuel pour lancer les assistants qui trouvent les clés
définissant l'affiliation de votre laboratoire.
* Après quelques explications, l'assistant vous demande l'identifiant
de votre laboratoire dans la base de données `inspirehep.net`_.
* Les :ref:`institutes` sont donnés en annexe.
.. attention::
Il est impératif de définir l'*identifiant du laboratoire* avant
de pouvoir moissonner un entrepôt.
Il est impératif de définir les *clés définissant vore affiliation*
avant de pouvoir moissonner un entrepôt.
.. note::
Il est possible d'ajouter ou de détruite une clé à tout moment.
Par contre, il n'est pas possible de modifier une clé.
.. note::
Il n'y a pas de limitations sur le nombre de clés.
Les clés définissant l'affiliation des auteurs dépendent de l'entrepôt
mais aussi des collaborations. Elles peuvent aussi changer avec le temps.
Différents cas de figures sont décris dans l'Annexe :ref:`harvester_affiliation`.
Définir un moissonneur
^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -33,6 +33,7 @@ Annexe
application_identifier
institute_identifier
category_aeres
harvester_affiliation
harvester_expert
harvester_processing
harvester_collection_cds
......
......@@ -16,5 +16,5 @@ Pour les laboratoires de l'IN2P3, les identifiants sont les suivants:
.. csv-table::
:file: csv/institutes.csv
:widths: 50 20
:widths: 50, 50
......@@ -10,6 +10,7 @@
'&bull; title, conference title, date and town': '&ndash; titre ainsi que le titre, la ville et la date de la conférence',
'&bull; title, publisher, volume and pages': '&ndash; titre, revue, volume et pages',
"'et al.' in authors": "'et al.' dans les authors",
"'RecordConf' object has no attribute 'country'": "'RecordConf' object has no attribute 'country'",
'A report already exists with the same title': 'Un rapport existe deja avec le même titre',
'A talk/proceeding already exists with the same:': 'Un acte ou une présentation existe déja avec le même:',
'Abbreviation': 'Abréviation',
......@@ -25,6 +26,10 @@
'Address of the invenio store where the search is performed.': 'Addresse du site invenio où les recherches sont effectuées.',
'administrators, librairians,...': 'administrateurs, documentalistes,...',
'Affiliation': 'Affiliation',
'affiliation keys': "clés définissant l'affiliation",
'Affiliation keys are not defined !!!': "Les clés définissant votre affiliation n'existe pas !!!",
'affiliation_keys': "clés définissant l'affiliation",
'affiliation_rules': 'affiliation_rules',
'Agencies': 'Agences',
'agencies': 'agences',
'agency': 'agence',
......@@ -49,6 +54,7 @@
'auth_permission': 'auth_permission',
'auth_user': 'auth_user',
'Author': 'Auteur',
'Author not found!': "L'auteur n'a pas été trouvé !",
"author's rescue list": 'liste de secours pour les auteurs du laboratoire',
'Authorize automatic scan.': 'Authorise le mossonage automatique.',
'Authors': 'Auteurs',
......@@ -230,6 +236,8 @@
'Fill': 'Remplir',
'Fill all fields': 'Remplir tous les champs',
'Filter': 'Filtrer',
'Filter affiliation_keys': 'Filter affiliation_keys',
'Filter affiliation_rules': "Filtrer les régles d'affiliation",
'Filter categories': 'Filtrer les catégories',
'Filter collaborations': 'Filtrer les collaborations',
'Filter countries': 'Filtrer les pays',
......@@ -302,6 +310,7 @@
'install': 'installé',
'Institute': 'Institut',
'Institute identifier in inspirehep.net.': 'Identifiant du laboratoire dans inspirehep.net.',
'Institute not found in the inspirehep database!': "Le laboratoire n'a pas été trouvé !",
'Institute number associated to CPPM authors': "Numéro de l'Institut associé aux auteurs du CPPM",
'Invalid': 'Non conforme',
"Invalid database table '%s'": "Invalid database table '%s'",
......@@ -318,6 +327,9 @@
'ISBN': 'ISBN',
'ISSN': 'ISSN',
'Javascript API': 'Javascript API',
'Key U': 'Clé U',
'Key V': 'Clé V',
'Keys already exist!': 'La clé existe!',
'Last name': 'Last name',
'Level': 'Niveau',
'Level 1': 'Niveau 1',
......@@ -453,8 +465,8 @@
'Process': 'Analyser',
'Processing time %s seconds': 'Temps de calcul %s seconds',
'Profile': 'Profile',
'project': 'projet',
'Project': 'Projet',
'project': 'projet',
'projects': 'projets',
'Projects': 'Projets',
'Projets': 'Projets',
......@@ -462,6 +474,7 @@
'Property': 'Propriété',
'Publication': 'Publication',
'Publication category associated to the found records.': 'Catégorie associée aux enregistrements.',
'Publication not found!': "La publication n'a pas été trouvé !",
'publications': 'publications',
'Publications': 'Publications',
'Publisher': 'Revue',
......@@ -582,6 +595,7 @@
'Serie': 'Serie',
'Serie Axis': 'Serie Axis',
'Serie Granularity': 'Serie Granularity',
'Server is not reachable or respond badly!': 'Le serveur est injoignable ou répond mal !',
'Service': 'Service',
'Several publications refer to it.': 'Several publications refer to it.',
'Sign Up': 'Sign Up',
......@@ -679,6 +693,7 @@
'Transform the preprint into an article': 'Transforme le preprint en article',
'Transform the talk into a proceeding': 'Transforme la présentation orale en actes de conférence',
'Type': 'Type',
'U': 'U',
'Unable to send email': 'Unable to send email',
'undefined': 'indéfini',
'universities': 'universités',
......@@ -704,6 +719,7 @@
'Users can login when the value is equal to true.': 'Les utilisateurs peuvent se logger quand cette valuer est égale à vraie.',
'usual': 'usuel',
'Usual': 'Usuel',
'V': 'V',
'Validate': 'Valider',
'Validated': 'Validé',
'Value': 'Valeur',
......@@ -713,8 +729,8 @@
'Verify Password': 'Verify Password',
'versions': 'versions',
'Vertical': 'Vertical',
'Volume': 'Volume',
'volume': 'volume',
'Volume': 'Volume',
'Volume / pages': 'Volume / pages',
'Volume number is not defined': "Le numéro du volume n'est pas défini",
'Welcome': 'Welcome',
......
......@@ -31,7 +31,7 @@ from regex import (REG_COLLABORATION,
plugins = PluginManager()
plugins.dbui.app_css = 'static/my.css'
plugins.dbui.app_debug = None
plugins.dbui.app_lg = 'static/limbra/locale/trp-lang-fr.js'
plugins.dbui.app_lg = 'static/limbra/locale/limbra-lang-fr.js'
plugins.dbui.app_libmin = 'static/limbra-min.js'
plugins.dbui.app_script = 'static/app.js'
# plugins.dbui.app_script_dir = None
......
# -*- coding: utf-8 -*-
""" affiliation_rules
"""
db.define_table("affiliation_keys",
Field("key_u", "string", length=255, notnull=True),
Field("key_v", "string", length=255, notnull=False),
migrate="affiliation_keys.table")
\ No newline at end of file
# -*- coding: utf-8 -*-
""" affiliation_rules
"""
#-------------------------------------------------------------------------------
#
# FIELDS CONFIGURATiON
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#
# FORM CONFIGURATiON
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#
# GRID CONFIGURATiON
#
#-------------------------------------------------------------------------------
gridModifier = dbui.GridModifier('affiliation_keys')
gridModifier.append_filter(('key_u', 'contains', T('contains')))
gridModifier.append_filter(('key_v', 'contains', T('contains')))
gridModifier.configure_filters(plugins=['pFormToolTip'], width=300)
gridModifier.configure_gridWithFilter(selectorTitle=T('Filter'))
gridModifier.configure(plugins=['pGridRowEditorConfirmDelete',
'pGridAffiliationKeysContextMenu'])
#-------------------------------------------------------------------------------
#
# STORE CONFIGURATiON
#
#-------------------------------------------------------------------------------
storeModifier = dbui.StoreModifier('affiliation_keys')
storeModifier.orderby(db.affiliation_keys.key_u, db.affiliation_keys.key_v)
......@@ -31,13 +31,11 @@ cfgPreferences = dict(dbtable='preferences',
source={
'authorize_harvester_scan': False,
'authorize_user_login': False,
'harvester_start_year': year,
'inspirehep_institute_id': 0},
'harvester_start_year': year},
sourceConfig={
'authorize_harvester_scan': {'type': 'boolean'},
'authorize_user_login': {'type': 'boolean'},
'harvester_start_year': {'type': 'number'},
'inspirehep_institute_id': {'type': 'number'}},
'harvester_start_year': {'type': 'number'}},
width=250,
xtype='xpreferences')
......
......@@ -4,12 +4,6 @@
"""
if db(db.preferences).count() != 3:
if not db(db.preferences.property == "add_rules_reg_institute").select():
db.preferences.insert(property='add_rules_reg_institute',
definition=T("For expert. "
"Add rules to the regular expression"
" defining my institute."))
if not db(db.preferences.property == "authorize_harvester_scan").select():
db.preferences.insert(property='authorize_harvester_scan',
definition=T("Harvesters are ran automatically "
......@@ -26,7 +20,3 @@ if db(db.preferences).count() != 3:
"the harvesters can be ran. "
"It starts with the given value and "
"ends with the current year."))
if not db(db.preferences.property == "inspirehep_institute_id").select():
db.preferences.insert(property="inspirehep_institute_id",