Skip to content

Commit

Permalink
sip2: implements selfcheck user accounts
Browse files Browse the repository at this point in the history
To easiest manage selfcheck user accounts, we separate them from rero-ils users.

* Adds separate table to store selfcheck users.
* Adatps loan json schema to store terminal id on creation.
* Adds command into Dockerfile to install sip2 module.

Co-Authored-by: Laurent Dubois <laurent.dubois@itld-solutions.be>
  • Loading branch information
lauren-d committed Mar 31, 2021
1 parent 47b04bf commit 04ab4dc
Show file tree
Hide file tree
Showing 16 changed files with 683 additions and 158 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Expand Up @@ -37,3 +37,4 @@ ARG UI_TGZ=""
ENV INVENIO_COLLECT_STORAGE='flask_collect.storage.file'

RUN poetry run bootstrap --deploy ${UI_TGZ}
RUN poetry install --no-root --extras sip2
3 changes: 3 additions & 0 deletions rero_ils/config.py
Expand Up @@ -97,6 +97,7 @@
from .modules.patrons.api import Patron
from .modules.patrons.permissions import PatronPermission
from .modules.permissions import record_permission_factory
from .modules.selfcheck.permissions import seflcheck_permission_factory
from .modules.templates.api import Template
from .modules.templates.permissions import TemplatePermission
from .modules.users.api import get_profile_countries, \
Expand Down Expand Up @@ -2670,6 +2671,8 @@ def _(x):
SIP2_SUPPORT_STATUS_UPDATE = True
SIP2_DATE_FORMAT = '%Y%m%d %H%M%S'

SIP2_PERMISSIONS_FACTORY = seflcheck_permission_factory

SIP2_REMOTE_ACTION_HANDLERS = dict(
rero_ils=dict(
login_handler='rero_ils.modules.selfcheck.api:selfcheck_login',
Expand Down
4 changes: 4 additions & 0 deletions rero_ils/modules/loans/jsonschemas/loans/loan-ils-v0.0.1.json
Expand Up @@ -94,6 +94,10 @@
"type": "string",
"title": "Request pickup location PID"
},
"selfcheck_terminal_id": {
"type": "string",
"title": "Selfcheck terminal id"
},
"start_date": {
"type": "string",
"format": "date-time",
Expand Down
3 changes: 3 additions & 0 deletions rero_ils/modules/loans/mappings/v7/loans/loan-ils-v0.0.1.json
Expand Up @@ -80,6 +80,9 @@
"transaction_user_pid": {
"type": "keyword"
},
"selfcheck_terminal_id": {
"type": "keyword"
},
"to_anonymize": {
"type": "boolean"
},
Expand Down
48 changes: 48 additions & 0 deletions rero_ils/modules/locations/api.py
Expand Up @@ -23,6 +23,7 @@

from .models import LocationIdentifier, LocationMetadata
from ..api import IlsRecord, IlsRecordsIndexer, IlsRecordsSearch
from ..errors import MissingRequiredParameterError
from ..fetchers import id_fetcher
from ..minters import id_minter
from ..providers import Provider
Expand Down Expand Up @@ -189,3 +190,50 @@ def bulk_index(self, record_id_iterator):
:param record_id_iterator: Iterator yielding record UUIDs.
"""
super().bulk_index(record_id_iterator, doc_type='loc')


def search_locations_by_pid(organisation_pid=None, library_pid=None,
is_online=False, is_pickup=False,
sort_by_field='location_name', sort_order='asc',
preserve_order=False):
"""Retrieve locations attached to the given organisation or library.
:param organisation_pid: Organisation pid.
:param library_pid: Library pid.
:param is_online: Filter only on online location.
:param is_pickup: Filter only on pickup location.
:param sort_by_field: Location field used for sort.
:param sort_order: Sort order `asc` or `desc`.
:param preserve_order: Preserve order.
:returns: - A Search object.
"""
search = LocationsSearch()

if organisation_pid:
search = search.filter('term', organisation__pid=organisation_pid)
elif library_pid:
search = search.filter('term', library__pid=library_pid)
else:
raise MissingRequiredParameterError(
"One of the parameters 'organisation_pid' "
"or 'library_pid' is required."
)

if is_online:
search = search.filter('term', is_online=is_online)

if is_pickup:
search = search.filter('term', is_pickup=is_pickup)

if sort_by_field:
search = search.sort({sort_by_field: {'order': sort_order}})
if preserve_order:
search = search.params(preserve_order=True)
return search


def search_location_by_pid(loc_pid):
"""Search location for the given location pid."""
loc_search = LocationsSearch().filter('term', pid=loc_pid)
for location in loc_search.scan():
return location
121 changes: 121 additions & 0 deletions rero_ils/modules/selfcheck/admin.py
@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2021 RERO
# Copyright (C) 2021 UCLouvain
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Admin views for selfcheck user."""

from flask_admin.contrib.sqla import ModelView
from flask_admin.form.fields import DateTimeField
from flask_babelex import gettext as _
from werkzeug.local import LocalProxy
from wtforms.fields import SelectField
from wtforms.validators import DataRequired

from .models import SelfcheckTerminal
from ..locations.api import search_location_by_pid, search_locations_by_pid
from ..organisations.api import Organisation


class SelfcheckTerminalView(ModelView):
"""Flask-Admin view to manage selfcheck terminals."""

can_view_details = True
can_create = True
can_delete = True

list_all = (
'id', 'name', 'access_token', 'organisation_pid', 'library_pid',
'location_pid', 'active', 'last_login_at', 'last_login_ip',
)

column_list = \
column_searchable_list = \
column_sortable_list = \
column_details_list = \
list_all

form_columns = ('name', 'access_token', 'location_pid', 'active')

form_args = dict(
name=dict(label='Name', validators=[DataRequired()]),
access_token=dict(label='Access token', validators=[DataRequired()]),
location_pid=dict(
label='Location',
validators=[DataRequired()],
choices=LocalProxy(lambda: [
(opts.get('location_pid'), opts.get('location_name')) for opts
in locations_form_options()
]),
),
)

column_filters = ('id', 'name', 'active', 'organisation_pid',
'library_pid', 'location_pid', 'last_login_at')

column_default_sort = ('last_login_at', True)

form_overrides = {
'location_pid': SelectField,
'last_login_at': DateTimeField
}

def on_model_change(self, form, model, is_created):
"""Fill organisation_pid when saving.
:param form:
Form used to create/update model
:param model:
Model that was created/updated
:param is_created:
True if model was created, False if model was updated
"""
location_pid = form.location_pid.data
location = search_location_by_pid(location_pid)
model.organisation_pid = location.organisation['pid']
model.library_pid = location.library['pid']


def locations_form_options():
"""Get locations form options."""
location_opts = []
for org in Organisation.get_all():
search = search_locations_by_pid(organisation_pid=org.pid,
sort_by_field='code',
preserve_order=True)
search = search.exclude('term', is_online=True)
for location in search.scan():
location_opts.append({
'location_pid': location.pid,
'location_name': '{org} - {loc_code} ({loc_name})'.format(
org=org.get('name'),
loc_code=location.code,
loc_name=location.name
)
})
return location_opts


selfcheck_terminal_adminview = {
'model': SelfcheckTerminal,
'modelview': SelfcheckTerminalView,
'category': _('Selfcheck Terminal Management'),
}

__all__ = (
'selfcheck_terminal_adminview',
'SelfcheckTerminalView'
)

0 comments on commit 04ab4dc

Please sign in to comment.