Skip to content

Commit

Permalink
users: add new resource
Browse files Browse the repository at this point in the history
* Adds a new 'user' resource to manage the user profiles. It contains
  the user personal data as opposed to the patron data that are link to
  a specific organisation.
* Writes a REST API:
  * A GET, POST, PUT methods to create, update and retrieves the patron
    personal information.
  * A search endpoint to retrieve the patron personal information given
    an email or a username.
* Creates units testing.
* Centralizes several views and methods in a specific `User` python
  class.
* Disables the validation email for freshly created user.
* Uses the standard ngx-formly `widget` field for the user JSON schema.

Co-Authored-by: Aly Badr <aly.badr@rero.ch>
Co-Authored-by: Johnny Mariéthoz <Johnny.Mariethoz@rero.ch>
  • Loading branch information
Aly Badr and jma committed Mar 1, 2021
1 parent ea9b133 commit 4e15fed
Show file tree
Hide file tree
Showing 21 changed files with 859 additions and 275 deletions.
8 changes: 2 additions & 6 deletions rero_ils/accounts_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

from .modules.patrons.api import Patron, current_patron
from .modules.patrons.permissions import PatronPermission
from .modules.users.api import User

current_datastore = LocalProxy(
lambda: current_app.extensions['security'].datastore)
Expand Down Expand Up @@ -60,12 +61,7 @@ class LoginView(CoreLoginView):
@classmethod
def get_user(cls, email=None, **kwargs):
"""Retrieve a user by the provided arguments."""
try:
profile = UserProfile.get_by_username(email)
return profile.user
except NoResultFound:
pass
return current_datastore.get_user(email)
return User.get_by_username_or_email(email).user

@use_kwargs(post_args)
def post(self, **kwargs):
Expand Down
5 changes: 3 additions & 2 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2288,7 +2288,8 @@ def _(x):
SECURITY_CHANGEABLE = True

#: Allow user to confirm their email address.
SECURITY_CONFIRMABLE = True
# TODO: decide what should be the workflow of the login user
SECURITY_CONFIRMABLE = False

#: Allow password recovery by users.
SECURITY_RECOVERABLE = True
Expand All @@ -2300,7 +2301,7 @@ def _(x):
SECURITY_SEND_REGISTER_EMAIL = True

#: Allow users to login without first confirming their email address.
SECURITY_LOGIN_WITHOUT_CONFIRMATION = False
SECURITY_LOGIN_WITHOUT_CONFIRMATION = True

#: TODO: remove this when the email is sent only if the user has an email
# address
Expand Down
35 changes: 32 additions & 3 deletions rero_ils/modules/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
from invenio_indexer.signals import before_record_index
from invenio_oaiharvester.signals import oaiharvest_finished
from invenio_records.signals import after_record_insert, after_record_update
from invenio_records_rest.errors import JSONSchemaValidationError
from invenio_userprofiles.signals import after_profile_update
from jsonschema.exceptions import ValidationError

from .apiharvester.signals import apiharvest_part
from .collections.listener import enrich_collection_data
Expand All @@ -50,6 +52,7 @@
from .patron_transactions.listener import enrich_patron_transaction_data
from .patrons.listener import create_subscription_patron_transaction, \
enrich_patron_data, update_from_profile
from .users.views import UsersCreateResource, UsersResource
from ..filter import empty_data, format_date_filter, jsondumps, lib_url, \
node_assets, text_to_id, to_pretty_json

Expand Down Expand Up @@ -87,9 +90,10 @@ def init_app(self, app):
Wiki(app)
self.init_config(app)
app.extensions['rero-ils'] = self
self.register_api_blueprint(app)
self.register_import_api_blueprint(app)
self.register_users_api_blueprint(app)

def register_api_blueprint(self, app):
def register_import_api_blueprint(self, app):
"""Imports bluprint initialization."""
api_blueprint = Blueprint(
'api_imports',
Expand All @@ -106,7 +110,6 @@ def register_api_blueprint(self, app):
'/import_{endpoint}/'.format(endpoint=endpoint),
view_func=imports_search
)
app.register_blueprint(api_blueprint)

imports_record = ImportsResource.as_view(
'imports_record',
Expand All @@ -124,6 +127,32 @@ def handle_bad_request(e):
ResultNotFoundOnTheRemoteServer, handle_bad_request)
app.register_blueprint(api_blueprint)

def register_users_api_blueprint(self, app):
"""User bluprint initialization."""
api_blueprint = Blueprint(
'api_users',
__name__
)
api_blueprint.add_url_rule(
'/users/<id>',
view_func=UsersResource.as_view(
'users_item'
)
)
api_blueprint.add_url_rule(
'/users/',
view_func=UsersCreateResource.as_view(
'users_list'
)
)

@api_blueprint.errorhandler(ValidationError)
def validation_error(error):
"""Catch validation errors."""
return JSONSchemaValidationError(error=error).get_response()

app.register_blueprint(api_blueprint)

def init_config(self, app):
"""Initialize configuration."""
# Use theme's base template if theme is installed
Expand Down
13 changes: 11 additions & 2 deletions rero_ils/modules/patrons/jsonschemas/patrons/patron-v0.0.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,17 @@
"type": "string"
},
"user_id": {
"title": "User ID",
"type": "number"
"title": "Personal Informations",
"description": "",
"type": "number",
"form": {
"templateOptions": {
"wrappers": [
"form-field",
"user-id"
]
}
}
},
"first_name": {
"title": "First name",
Expand Down
38 changes: 1 addition & 37 deletions rero_ils/modules/patrons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import re
from functools import wraps

from elasticsearch_dsl import Q
from flask import Blueprint, abort, current_app, flash, jsonify, \
render_template, request, url_for
from flask_babelex import format_currency
Expand All @@ -35,7 +34,7 @@
from werkzeug.exceptions import NotFound
from werkzeug.utils import redirect

from .api import Patron, PatronsSearch
from .api import Patron
from .permissions import get_allowed_roles_management
from .utils import user_has_patron
from ..items.api import Item
Expand Down Expand Up @@ -71,41 +70,6 @@ def is_logged_librarian(*args, **kwargs):
return fn(*args, **kwargs)
return is_logged_librarian


@api_blueprint.route('/count/', methods=['GET'])
@check_permission
def number_of_patrons():
"""Returns the number of patrons matching the query.
The query should be one of the following forms:
- `/api/patrons/count/?q=email:"test@test.ch"
- `/api/patrons/count/?q=email:"test@test.ch" NOT pid:1
- `/api/patrons/count/?q=username:"test"
- `/api/patrons/count/?q=username:"test" NOT pid:1
:return: The number of existing user account corresponding to the given
email or username.
:rtype: A JSON of the form:{"hits": {"total": 1}}
"""
query = request.args.get('q')
email = _EMAIL_REGEX.search(query)
username = _USERNAME_REGEX.search(query)
if not email and not username:
abort(400)
if email:
email = email.group(1)
s = PatronsSearch().query('match', email__analyzed=email)
else:
username = username.group(1)
s = PatronsSearch().query('match', username__analyzed=username)
exclude_pid = _PID_REGEX.search(query)
if exclude_pid:
exclude_pid = exclude_pid.group(1)
s = s.filter('bool', must_not=[Q('term', pid=exclude_pid)])
response = dict(hits=dict(total=s.count()))
return jsonify(response)


@api_blueprint.route('/<patron_pid>/circulation_informations', methods=['GET'])
@check_permission
def patron_circulation_informations(patron_pid):
Expand Down
18 changes: 18 additions & 0 deletions rero_ils/modules/users/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2021 RERO
#
# 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/>.

"""User Record."""
Loading

0 comments on commit 4e15fed

Please sign in to comment.