diff --git a/data/templates.json b/data/templates.json index 242580d636..76296ae157 100644 --- a/data/templates.json +++ b/data/templates.json @@ -282,7 +282,7 @@ "patron_type": { "$ref": "https://ils.rero.ch/api/patron_types/1" }, - "phone": "", + "home_phone": "", "postal_code": "", "roles": [ "patron" @@ -861,7 +861,7 @@ "patron_type": { "$ref": "https://ils.rero.ch/api/patron_types/4" }, - "phone": "", + "home_phone": "", "postal_code": "", "roles": [ "patron" @@ -1440,7 +1440,7 @@ "patron_type": { "$ref": "https://ils.rero.ch/api/patron_types/5" }, - "phone": "", + "home_phone": "", "postal_code": "", "roles": [ "patron" diff --git a/data/users.json b/data/users.json index 523b97c49d..8367dd24a6 100644 --- a/data/users.json +++ b/data/users.json @@ -16,7 +16,7 @@ "$ref": "https://ils.rero.ch/api/libraries/4" } ], - "phone": "+39324993597", + "home_phone": "+39324993597", "postal_code": "11100", "street": "Viale Rue Gran Paradiso, 44", "notes": [ @@ -45,7 +45,7 @@ "$ref": "https://ils.rero.ch/api/libraries/4" } ], - "phone": "+41261234567", + "home_phone": "+41261234567", "postal_code": "1700", "street": "Avenue de la Gare, 80" }, @@ -65,7 +65,7 @@ "$ref": "https://ils.rero.ch/api/libraries/3" } ], - "phone": "+41911234567", + "home_phone": "+41911234567", "postal_code": "6900", "street": "Viale Carlo Cattaneo, 52" }, @@ -80,14 +80,16 @@ "patron" ], "last_name": "Casalini", - "phone": "+39324993585", + "home_phone": "+39324993585", "postal_code": "11100", "street": "Via Croix Noire 3", "patron": { "expiration_date": "2023-10-07", "blocked": false, "keep_history": false, - "barcode": "2050124311", + "barcode": [ + "2050124311" + ], "type": { "$ref": "https://ils.rero.ch/api/patron_types/1" }, @@ -120,7 +122,9 @@ "street": "Panoramica Collinare, 47", "patron": { "expiration_date": "2023-10-07", - "barcode": "2030124287", + "barcode": [ + "2030124287" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/2" @@ -145,7 +149,7 @@ "$ref": "https://ils.rero.ch/api/libraries/4" } ], - "phone": "+39244857275", + "home_phone": "+39244857275", "postal_code": "11010", "street": "Frazione Plan" }, @@ -164,7 +168,9 @@ "street": "Rue du Nord 7", "patron": { "expiration_date": "2023-10-07", - "barcode": "reroils1", + "barcode": [ + "reroils1" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/3" @@ -184,12 +190,14 @@ "patron" ], "last_name": "John", - "phone": "+1408492280015", + "home_phone": "+1408492280015", "postal_code": "95054", "street": "520 Scott Blvd", "patron": { "expiration_date": "2023-10-07", - "barcode": "2000000001", + "barcode": [ + "2000000001" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/3" @@ -209,12 +217,14 @@ "patron" ], "last_name": "Premand", - "phone": "+41795762233", + "home_phone": "+41795762233", "postal_code": "1892", "street": "Route du Village 6", "patron": { "expiration_date": "2023-10-07", - "barcode": "10000001", + "barcode": [ + "10000001" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/1" @@ -234,12 +244,14 @@ "patron" ], "last_name": "Broglio", - "phone": "+41918264239", + "home_phone": "+41918264239", "postal_code": "6500", "street": "Piazza Collegiata 12", "patron": { "expiration_date": "2023-10-07", - "barcode": "2050124312", + "barcode": [ + "2050124312" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/1" @@ -259,12 +271,14 @@ "patron" ], "last_name": "Carron", - "phone": "+41792001020", + "home_phone": "+41792001020", "postal_code": "1920", "street": "Gare 45", "patron": { "expiration_date": "2023-10-07", - "barcode": "kad001", + "barcode": [ + "kad001" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/1" @@ -284,12 +298,14 @@ "patron" ], "last_name": "Rard", - "phone": "+41792500512", + "home_phone": "+41792500512", "postal_code": "1926", "street": "Vignettes 25", "patron": { "expiration_date": "2023-10-07", - "barcode": "kad002", + "barcode": [ + "kad002" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/1" @@ -315,7 +331,7 @@ "$ref": "https://ils.rero.ch/api/libraries/5" } ], - "phone": "+01411234567", + "home_phone": "+01411234567", "postal_code": "0000", "street": "High Street" }, @@ -334,7 +350,9 @@ "street": "Magic Street 3", "patron": { "expiration_date": "2023-10-07", - "barcode": "3050124311", + "barcode": [ + "3050124311" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/4" @@ -358,7 +376,9 @@ "street": "Diagon Alley 72", "patron": { "expiration_date": "2023-10-07", - "barcode": "3050124312", + "barcode": [ + "3050124312" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/4" @@ -384,7 +404,7 @@ "$ref": "https://ils.rero.ch/api/libraries/6" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "7878", "street": "Imagination Street 48" }, @@ -404,7 +424,7 @@ "$ref": "https://ils.rero.ch/api/libraries/6" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -424,7 +444,7 @@ "$ref": "https://ils.rero.ch/api/libraries/7" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -444,7 +464,7 @@ "$ref": "https://ils.rero.ch/api/libraries/8" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -464,7 +484,7 @@ "$ref": "https://ils.rero.ch/api/libraries/9" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -484,7 +504,7 @@ "$ref": "https://ils.rero.ch/api/libraries/10" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -504,7 +524,7 @@ "$ref": "https://ils.rero.ch/api/libraries/11" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -524,7 +544,7 @@ "$ref": "https://ils.rero.ch/api/libraries/12" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -544,7 +564,7 @@ "$ref": "https://ils.rero.ch/api/libraries/13" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -564,7 +584,7 @@ "$ref": "https://ils.rero.ch/api/libraries/14" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -584,7 +604,7 @@ "$ref": "https://ils.rero.ch/api/libraries/15" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -604,7 +624,7 @@ "$ref": "https://ils.rero.ch/api/libraries/16" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -624,7 +644,7 @@ "$ref": "https://ils.rero.ch/api/libraries/17" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -644,7 +664,7 @@ "$ref": "https://ils.rero.ch/api/libraries/18" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -664,7 +684,7 @@ "$ref": "https://ils.rero.ch/api/libraries/19" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -684,7 +704,7 @@ "$ref": "https://ils.rero.ch/api/libraries/20" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -704,7 +724,7 @@ "$ref": "https://ils.rero.ch/api/libraries/21" } ], - "phone": "+765554433", + "home_phone": "+765554433", "postal_code": "no indication", "street": "no indication" }, @@ -719,12 +739,14 @@ "patron" ], "last_name": "Rh\u00e9aume", - "phone": "+41316126321", + "home_phone": "+41316126321", "postal_code": "1292", "street": "Faubourg du Lac 6", "patron": { "expiration_date": "2023-10-07", - "barcode": "920001", + "barcode": [ + "920001" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -744,12 +766,14 @@ "patron" ], "last_name": "Rocher", - "phone": "+41913843044", + "home_phone": "+41913843044", "postal_code": "1489", "street": "Rue du Tr\u00e9sor 9", "patron": { "expiration_date": "2023-10-07", - "barcode": "920002", + "barcode": [ + "920002" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -769,12 +793,14 @@ "patron" ], "last_name": "Chalifour", - "phone": "+41628609930", + "home_phone": "+41628609930", "postal_code": "2953", "street": "Rue de l'Industrie 46", "patron": { "expiration_date": "2023-10-07", - "barcode": "920003", + "barcode": [ + "920003" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -794,12 +820,14 @@ "patron" ], "last_name": "Lamarre", - "phone": "+41613956062", + "home_phone": "+41613956062", "postal_code": "2000", "street": "Avenue Max-Huber 2", "patron": { "expiration_date": "2023-10-07", - "barcode": "920004", + "barcode": [ + "920004" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -819,12 +847,14 @@ "patron" ], "last_name": "Boileau", - "phone": "+41815333591", + "home_phone": "+41815333591", "postal_code": "3960", "street": "Rue de Montsalvens 3", "patron": { "expiration_date": "2023-10-07", - "barcode": "920005", + "barcode": [ + "920005" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -844,12 +874,14 @@ "patron" ], "last_name": "Doiron", - "phone": "+41316126321", + "home_phone": "+41316126321", "postal_code": "1630", "street": "Rue du Vieux-Pont 3", "patron": { "expiration_date": "2023-10-07", - "barcode": "920006", + "barcode": [ + "920006" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -869,12 +901,14 @@ "patron" ], "last_name": "Beich", - "phone": "+41562747247", + "home_phone": "+41562747247", "postal_code": "9604", "street": "Seefeldstrasse 37", "patron": { "expiration_date": "2023-10-07", - "barcode": "920007", + "barcode": [ + "920007" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -894,12 +928,14 @@ "patron" ], "last_name": "Fischer", - "phone": "+41449166744", + "home_phone": "+41449166744", "postal_code": "8620", "street": "In Stierwisen 98", "patron": { "expiration_date": "2023-10-07", - "barcode": "920008", + "barcode": [ + "920008" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -919,12 +955,14 @@ "patron" ], "last_name": "Freeh", - "phone": "+41523734083", + "home_phone": "+41523734083", "postal_code": "8155", "street": "Im Wingert 117", "patron": { "expiration_date": "2023-10-07", - "barcode": "920009", + "barcode": [ + "920009" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -944,12 +982,14 @@ "patron" ], "last_name": "Schuhmacher", - "phone": "+41318033434", + "home_phone": "+41318033434", "postal_code": "9525", "street": "Werkstrasse 136", "patron": { "expiration_date": "2023-10-07", - "barcode": "920010", + "barcode": [ + "920010" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -969,12 +1009,14 @@ "patron" ], "last_name": "Trommler", - "phone": "+41278023434", + "home_phone": "+41278023434", "postal_code": "3605", "street": "L\u00fctzelfl\u00fchstrasse 120", "patron": { "expiration_date": "2023-10-07", - "barcode": "920011", + "barcode": [ + "920011" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -994,12 +1036,14 @@ "patron" ], "last_name": "Ziegler", - "phone": "+41222747247", + "home_phone": "+41222747247", "postal_code": "5637", "street": "\u00dcerklisweg 55", "patron": { "expiration_date": "2023-10-07", - "barcode": "920012", + "barcode": [ + "920012" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1019,12 +1063,14 @@ "patron" ], "last_name": "Marcelo", - "phone": "+41322747447", + "home_phone": "+41322747447", "postal_code": "6675", "street": "Via Verbano 109", "patron": { "expiration_date": "2023-10-07", - "barcode": "920013", + "barcode": [ + "920013" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1043,12 +1089,14 @@ "patron" ], "last_name": "Folliero", - "phone": "+41328619930", + "home_phone": "+41328619930", "postal_code": "7106", "street": "Quadra 93", "patron": { "expiration_date": "2023-10-07", - "barcode": "920014", + "barcode": [ + "920014" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1068,12 +1116,14 @@ "patron" ], "last_name": "Bellucci", - "phone": "+41313966062", + "home_phone": "+41313966062", "postal_code": "6987", "street": "Via Camischolas sura 63", "patron": { "expiration_date": "2023-10-07", - "barcode": "920015", + "barcode": [ + "920015" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1093,12 +1143,14 @@ "patron" ], "last_name": "Colombo", - "phone": "+41274865229", + "home_phone": "+41274865229", "postal_code": "6500", "street": "Lungolago 118", "patron": { "expiration_date": "2023-10-07", - "barcode": "920016", + "barcode": [ + "920016" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1118,12 +1170,14 @@ "patron" ], "last_name": "de Baskerville", - "phone": "+41324993585", + "home_phone": "+41324993585", "postal_code": "6073", "street": "Ranftweg 1", "patron": { "expiration_date": "2023-10-07", - "barcode": "999999", + "barcode": [ + "999999" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/5" @@ -1150,7 +1204,7 @@ } ], "password": "123456", - "phone": "+39324993599", + "home_phone": "+39324993599", "postal_code": "11101", "street": "Central Park" }, @@ -1171,7 +1225,7 @@ } ], "password": "123456", - "phone": "+39324993596", + "home_phone": "+39324993596", "postal_code": "11102", "street": "Illogical street, 44" }, @@ -1187,12 +1241,14 @@ ], "last_name": "Kirk", "password": "123456", - "phone": "+41324883598", + "home_phone": "+41324883598", "postal_code": "4242", "street": "Galaxy street", "patron": { "expiration_date": "2023-10-07", - "barcode": "cypress-1", + "barcode": [ + "cypress-1" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/7" @@ -1213,12 +1269,14 @@ ], "last_name": "Uhura", "password": "123456", - "phone": "+41324883123", + "home_phone": "+41324883123", "postal_code": "4242", "street": "Milky Way street", "patron": { "expiration_date": "2023-10-07", - "barcode": "cypress-2", + "barcode": [ + "cypress-2" + ], "keep_history": false, "type": { "$ref": "https://ils.rero.ch/api/patron_types/7" diff --git a/poetry.lock b/poetry.lock index 20c40a550f..3dff2fe92a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1038,7 +1038,7 @@ optional = true version = ">=1.2.5,<1.3.0" [package.dependencies.invenio-db] -extras = ["postgresql", "versioning"] +extras = ["versioning", "postgresql"] optional = true version = ">=1.0.8,<1.1.0" @@ -1845,10 +1845,9 @@ sqlite = ["invenio-db (>=1.0.5)"] tests = ["pytest-invenio (>=1.4.0)"] [package.source] -reference = "d7fafc33068df24a7a9ef34836587f15c45ca96d" +reference = "49d77006fc4485f704dab6270fcd7115f6291eb7" type = "git" url = "https://github.com/rero/invenio-userprofiles.git" - [[package]] category = "main" description = "IPython: Productive Interactive Computing" @@ -3334,7 +3333,7 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt sip2 = ["invenio-sip2"] [metadata] -content-hash = "6dcb39a59a6b2fc23bacdab85f297565cfff29a3e275da7acdd081fe94034e19" +content-hash = "c5acff9e5250e3265fad4a23093b8ae36014509dc48486c56549af08e12ac508" lock-version = "1.0" python-versions = ">= 3.6, <3.8" diff --git a/pyproject.toml b/pyproject.toml index e2285308a8..b925e824a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ redisbeat = "*" jsonpickle = ">=1.4.1" ciso8601 = "*" # TODO: to be removed when the thumbnail will be refactored -invenio-userprofiles = {git = "https://github.com/rero/invenio-userprofiles.git", tag = "invenio-3.4"} +invenio-userprofiles = {git = "https://github.com/rero/invenio-userprofiles.git", rev = "v1.2.1-rero1.0"} ## Additionnal constraints on python modules flask-wiki = {git = "https://github.com/rero/flask-wiki.git"} diff --git a/rero_ils/accounts_views.py b/rero_ils/accounts_views.py index b418fab5f6..f982ccd162 100644 --- a/rero_ils/accounts_views.py +++ b/rero_ils/accounts_views.py @@ -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) @@ -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): diff --git a/rero_ils/config.py b/rero_ils/config.py index b0b7ad64db..cfb6a135a5 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -99,6 +99,8 @@ from .modules.permissions import record_permission_factory from .modules.templates.api import Template from .modules.templates.permissions import TemplatePermission +from .modules.users.api import get_profile_countries, \ + get_readonly_profile_fields from .modules.vendors.api import Vendor from .modules.vendors.permissions import VendorPermission from .permissions import librarian_delete_permission_factory, \ @@ -228,6 +230,9 @@ def _(x): ACCOUNTS_USERINFO_HEADERS = False # Disable User Profiles USERPROFILES = True +USERPROFILES_COUNTRIES = get_profile_countries +USERPROFILES_DEFAULT_COUNTRY = 'sz' +USERPROFILES_READONLY_FIELDS = get_readonly_profile_fields # Custom login view ACCOUNTS_REST_AUTH_VIEWS = { @@ -802,15 +807,19 @@ def _(x): }, record_serializers_aliases={ 'json': 'application/json', + 'rero+json': 'application/rero+json', }, search_serializers={ 'application/json': ( 'rero_ils.modules.serializers:json_v1_search' + ), + 'application/rero+json': ( + 'rero_ils.modules.patrons.serializers:json_patron_search' ) }, list_route='/patrons/', record_loaders={ - 'application/json': lambda: Patron(request.get_json()), + 'application/json': lambda: Patron.load(request.get_json()), }, record_class='rero_ils.modules.patrons.api:Patron', item_route=('/patrons/', + 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 diff --git a/rero_ils/modules/items/api/circulation.py b/rero_ils/modules/items/api/circulation.py index a5161541bb..76dd2b4fea 100644 --- a/rero_ils/modules/items/api/circulation.py +++ b/rero_ils/modules/items/api/circulation.py @@ -1018,7 +1018,7 @@ def can_request(cls, item, **kwargs): "organisation.") if patron.patron.get('barcode') and \ item.patron_has_an_active_loan_on_item( - patron.patron.get('barcode')): + patron.patron.get('barcode')[0]): reasons.append("Item is already checked-out or requested by " "patron.") return len(reasons) == 0, reasons diff --git a/rero_ils/modules/items/api/record.py b/rero_ils/modules/items/api/record.py index aefbe6b8ce..4c85df5290 100644 --- a/rero_ils/modules/items/api/record.py +++ b/rero_ils/modules/items/api/record.py @@ -28,7 +28,7 @@ from ...organisations.api import Organisation from ...utils import date_string_to_utc, extracted_data_from_ref, \ generate_item_barcode, get_base_url, get_ref_for_pid, \ - trim_barcode_for_record + trim_item_barcode_for_record class ItemRecord(IlsRecord): @@ -262,7 +262,7 @@ def _prepare_item_record(cls, data, mode): :param mode: update or create mode. :return: the modified record. """ - data = trim_barcode_for_record(data=data) + data = trim_item_barcode_for_record(data=data) # Since the barcode is a mandatory field, we set it to current # timestamp if not given data = generate_item_barcode(data=data) diff --git a/rero_ils/modules/items/cli.py b/rero_ils/modules/items/cli.py index a678b78b75..e0744b1d4a 100644 --- a/rero_ils/modules/items/cli.py +++ b/rero_ils/modules/items/cli.py @@ -331,7 +331,5 @@ def get_patrons_barcodes(): barcodes = [] for uuid in patrons_ids: patron = Patron.get_record_by_id(uuid) - barcode = patron.patron.get('barcode') - if barcode: - barcodes.append(barcode) + barcodes = barcodes + patron.patron.get('barcode', []) return barcodes diff --git a/rero_ils/modules/loans/api.py b/rero_ils/modules/loans/api.py index 8243fad12b..e1dd068cd7 100644 --- a/rero_ils/modules/loans/api.py +++ b/rero_ils/modules/loans/api.py @@ -678,7 +678,7 @@ def patron_profile(patron): if loan['state'] in [ LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP]: loan['rank'] = item.patron_request_rank( - patron.patron['barcode']) + patron.patron['barcode'][0]) requests.append(loan) elif loan['state'] in [ LoanState.ITEM_RETURNED, diff --git a/rero_ils/modules/loans/cli.py b/rero_ils/modules/loans/cli.py index 20ec324b9b..5c4e29235f 100644 --- a/rero_ils/modules/loans/cli.py +++ b/rero_ils/modules/loans/cli.py @@ -394,9 +394,8 @@ def get_random_patron(exclude_this_barcode): .filter('term', organisation__pid=org_pid)\ .source(['patron']).scan() for patron in patrons: - if patron.patron.barcode != exclude_this_barcode: - return Patron.get_patron_by_barcode(barcode=patron.patron.barcode) - return None + if exclude_this_barcode not in patron.patron.barcode: + return Patron.get_patron_by_barcode(barcode=patron.patron.barcode[0]) def get_random_librarian(patron): diff --git a/rero_ils/modules/patrons/api.py b/rero_ils/modules/patrons/api.py index 7509fcf2d3..c3722c18ad 100644 --- a/rero_ils/modules/patrons/api.py +++ b/rero_ils/modules/patrons/api.py @@ -17,6 +17,7 @@ # along with this program. If not, see . """API for manipulating patrons.""" +from copy import deepcopy from datetime import datetime from functools import partial @@ -24,7 +25,6 @@ from flask_babelex import gettext as _ from flask_login import current_user from invenio_circulation.proxies import current_circulation -from invenio_db import db from werkzeug.local import LocalProxy from .models import PatronIdentifier, PatronMetadata @@ -36,8 +36,9 @@ from ..organisations.api import Organisation from ..patron_transactions.api import PatronTransaction from ..providers import Provider +from ..users.api import User from ..utils import extracted_data_from_ref, get_patron_from_arguments, \ - get_ref_for_pid, trim_barcode_for_record + get_ref_for_pid, trim_patron_barcode_for_record from ...utils import create_user_from_data _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) @@ -106,12 +107,6 @@ class Patron(IlsRecord): provider = PatronProvider model_cls = PatronMetadata - # field list to be in sync - profile_fields = [ - 'first_name', 'last_name', 'street', 'postal_code', - 'city', 'birth_date', 'username', 'phone', 'keep_history' - ] - available_roles = [ROLE_SYSTEM_LIBRARIAN, ROLE_LIBRARIAN, ROLE_PATRON] def _validate(self, **kwargs): @@ -200,30 +195,16 @@ def create(cls, data, id_=None, delete_pid=False, :param email_notification - send a reset password link to the user """ # remove spaces - data = trim_barcode_for_record(data=data) - # synchronize the rero id user profile data - user = cls._get_user_by_user_id(data.get('user_id')) - data = cls.merge_data_from_profile(data, user.profile) - try: - record = super().create( + data = trim_patron_barcode_for_record(data=data) + record = super().create( data, id_, delete_pid, dbcommit, reindex, **kwargs) - record._update_roles() - except Exception as err: - db.session.rollback() - raise err - # TODO: send reset password instruction when a librarian create a user + record._update_roles() return record def update(self, data, dbcommit=False, reindex=False): """Update data for record.""" # remove spaces - data = trim_barcode_for_record(data=data) - data = dict(self, **data) - - # synchronize the rero id user profile data - user = self._get_user_by_user_id(data.get('user_id')) - data = self.merge_data_from_profile(data, user.profile) - + data = trim_patron_barcode_for_record(data=data) super().update(data, dbcommit, reindex) self._update_roles() return self @@ -235,54 +216,29 @@ def delete(self, force=False, delindex=False): return self @classmethod - def merge_data_from_profile(cls, data, profile): - """Get the profile informations and inject it. - - TODO: move this to the indexing time. - """ - # retrieve the user - for field in cls.profile_fields: - # date field requires conversion - if field == 'birth_date': - data[field] = getattr( - profile, field).strftime('%Y-%m-%d') - elif field == 'keep_history': - if 'patron' in data.get('roles', []): - new_keep_history = getattr(profile, field) - data.setdefault('patron', {})['keep_history'] = new_keep_history - else: - value = getattr(profile, field) - if value not in [None, '']: - data[field] = value - # update the email - if profile.user.email != data.get('email'): - # the email is not defined or removed in the user profile - if not profile.user.email: - try: - del data['email'] - except KeyError: - pass - else: - # the email has been updated in the user profile - data['email'] = profile.user.email - return data + def load(cls, data): + """Load the data and remove the user data.""" + return cls(cls.removeUserData(data)) @classmethod - def update_from_profile(cls, data, profile): - """Update the current record with the user profile data. + def removeUserData(cls, data): + """Remove the user data.""" + data = deepcopy(data) + profile_fields = User.profile_fields + ['username', 'email'] + for field in profile_fields: + try: + del data[field] + except KeyError: + pass + return data - :param profile - the rero user profile - """ - # retrieve the user - patron = Patron.get_patron_by_user(profile.user) - if patron: - cls.merge_data_from_profile(dict(patron), profile) - super().update(dict(patron), True, True) - # TODO: do it at the profile changes - # anonymize user loans if keep_history is changed - # if old_keep_history and not new_keep_history: - # from ..loans.api import anonymize_loans - # anonymize_loans(patron_pid=patron.pid, dbcommit=True, reindex=True) + def dumps(self, **kwargs): + """Return pure Python dictionary with record metadata.""" + dump = super().dumps(**kwargs) + user = User.get_by_id(self['user_id']) + user_info = user.dumpsMetadata() + dump.update(user_info) + return dump @classmethod def _get_user_by_user_id(cls, user_id): @@ -476,20 +432,6 @@ def remove_role(self, role_name): _datastore.remove_role_from_user(self.user, role) _datastore.commit() - @property - def initial(self): - """Return the initials of the patron first name.""" - initial = '' - firsts = self['first_name'].split(' ') - for first in firsts: - initial += first[0] - lasts = self['last_name'].split(' ') - for last in lasts: - if last[0].isupper(): - initial += last[0] - - return initial - @property def patron(self): """Patron property shorcut.""" @@ -498,9 +440,10 @@ def patron(self): @property def formatted_name(self): """Return the best possible human readable patron name.""" + profile = self.user.profile name_parts = [ - self.get('last_name', '').strip(), - self.get('first_name', '').strip() + profile.last_name.strip(), + profile.first_name.strip() ] name_parts = [part for part in name_parts if part] # remove empty part return ', '.join(name_parts) diff --git a/rero_ils/modules/patrons/cli.py b/rero_ils/modules/patrons/cli.py index 7166b7196c..11aaa8694d 100644 --- a/rero_ils/modules/patrons/cli.py +++ b/rero_ils/modules/patrons/cli.py @@ -84,7 +84,7 @@ def import_users(infile, append, verbose, password, lazy, dont_stop_on_error, ), fg='yellow') if password: patron_data.pop('password', None) - # do nothing if the patron alredy exists + # do nothing if the patron already exists patron = Patron.get_patron_by_username(username) if patron: click.secho('{count: <8} Patron already exist: {username}'.format( diff --git a/rero_ils/modules/patrons/jsonschemas/patrons/patron-v0.0.1.json b/rero_ils/modules/patrons/jsonschemas/patrons/patron-v0.0.1.json index 10d8cc1a58..71759c4533 100644 --- a/rero_ils/modules/patrons/jsonschemas/patrons/patron-v0.0.1.json +++ b/rero_ils/modules/patrons/jsonschemas/patrons/patron-v0.0.1.json @@ -6,10 +6,13 @@ "additionalProperties": false, "propertiesOrder": [ "user_id", + "second_address", "roles", "patron", "libraries", - "notes" + "notes", + "source", + "local_code" ], "required": [ "$schema", @@ -29,110 +32,93 @@ "title": "Patron ID", "type": "string" }, - "user_id": { - "title": "User ID", - "type": "number" - }, - "first_name": { - "title": "First name", + "source": { + "title": "Source", + "description": "Source if the record has been loaded in a batch.", "type": "string", - "minLength": 2, - "form": { - "focus": true - } + "minLength": 2 }, - "last_name": { - "title": "Last name", + "local_code": { + "title": "Local code", + "description": "Code used to classify users, for instance for statistics.", "type": "string", "minLength": 2 }, - "birth_date": { - "title": "Date of birth", - "type": "string", - "format": "date", - "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "user_id": { + "title": "Personal informations", + "description": "", + "type": "number", "form": { - "validation": { - "messages": { - "patternMessage": "Should be in the following format: 2022-12-31 (YYYY-MM-DD)." - } - }, - "placeholder": "Example: 1985-12-29" + "templateOptions": { + "wrappers": [ + "form-field", + "user-id" + ] + } } }, - "email": { - "title": "Email", - "type": "string", - "format": "email", - "pattern": "^.*@.*\\..+$", - "minLength": 6, - "form": { - "expressionProperties": { - "templateOptions.required": "field.parent.model.roles.some(v => (v === 'librarian' || v === 'system_librarian')) || (field.parent.model.patron.communication_channel === 'email' && !field.parent.model.patron.additional_communication_email)" + "second_address": { + "title": "Secound address", + "type": "object", + "additionalProperties": false, + "propertiesOrder": [ + "street", + "postal_code", + "city", + "country" + ], + "properties": { + "street": { + "title": "Street", + "description": "Street and number of the address.", + "type": "string", + "minLength": 1, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-12" + } + } }, - "validation": { - "validators": { - "valueAlreadyExists": { - "term": "email", - "remoteRecordType": "patrons/count" + "postal_code": { + "title": "Postal code", + "type": "string", + "minLength": 1, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" } - }, - "messages": { - "patternMessage": "The email is not valid.", - "alreadyTakenMessage": "This email is already taken." } - } - } - }, - "username": { - "title": "Username", - "description": "Login username for the web interface.", - "type": "string", - "pattern": "^[a-zA-Z][a-zA-Z0-9-_]{2}[a-zA-Z0-9-_]*$", - "minLength": 3, - "form": { - "validation": { - "validators": { - "valueAlreadyExists": { - "term": "username", - "remoteRecordType": "patrons/count" + }, + "city": { + "title": "City", + "type": "string", + "minLength": 1, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" } - }, - "messages": { - "patternMessage": "Username must start with a letter, be at least three characters long and only contain alphanumeric characters, dashes and underscores.", - "alreadyTakenMessage": "This username is already taken." } + }, + "country": { + "allOf": [ + { + "$ref": "https://ils.rero.ch/schemas/common/countries-v0.0.1.json#/country" + }, + { + "form": { + "hideExpression": "!(field.parent.model && field.parent.model.city)" + } + } + ] } - } - }, - "street": { - "title": "Street", - "description": "Street and number of the address.", - "type": "string", - "minLength": 1 - }, - "postal_code": { - "title": "Postal code", - "type": "string", - "minLength": 1 - }, - "city": { - "title": "City", - "type": "string", - "minLength": 1 - }, - "phone": { - "title": "Phone number", - "description": "Phone number with the international prefix, without spaces.", - "type": "string", - "pattern": "^\\+[0-9]*$", + }, "form": { - "validation": { - "messages": { - "patternMessage": "Phone number with the international prefix, without spaces, ie +41221234567." - } - }, - "placeholder": "Example: +41791231212" + "templateOptions": { + "wrappers": [ + "card" + ], + "containerCssClass": "row" + } } }, "patron": { @@ -154,7 +140,6 @@ "communication_language", "expiration_date", "libraries", - "keep_history", "blocked", "blocked_note" ], @@ -179,16 +164,25 @@ } }, "barcode": { - "title": "Patron's barcode or card number", - "type": "string", - "minLength": 6, - "form": { - "validation": { - "validators": { - "valueAlreadyExists": {} - }, - "messages": { - "alreadyTakenMessage": "The barcode is already taken." + "title": "Patron's barcodes or cards number", + "type": "array", + "minItems": 1, + "maxItems": 2, + "uniqueItems": true, + "items": { + "title": "Patron's barcode or card number", + "type": "string", + "minLength": 6, + "form": { + "validation": { + "validators": { + "valueAlreadyExists": { + "term": "barcode" + } + }, + "messages": { + "alreadyTakenMessage": "The barcode is already taken." + } } } } @@ -460,13 +454,19 @@ } }, "form": { - "fieldMap": "roles" + "fieldMap": "roles", + "templateOptions": { + "wrappers": [ + "card" + ] + } } }, "notes": { "title": "Notes", "description": "The public note is visible for the patron in his/her account.", "type": "array", + "minItems": 0, "items": { "type": "object", "additionalProperties": false, diff --git a/rero_ils/modules/patrons/listener.py b/rero_ils/modules/patrons/listener.py index 58f02dcb03..b24d4a999d 100644 --- a/rero_ils/modules/patrons/listener.py +++ b/rero_ils/modules/patrons/listener.py @@ -43,7 +43,6 @@ def enrich_patron_data(sender, json=None, record=None, index=None, 'pid': org_pid } - def create_subscription_patron_transaction(sender, record=None, **kwargs): """This method check the patron to know if a subscription is requested. @@ -76,4 +75,15 @@ def update_from_profile(sender, profile=None, **kwargs): :param profile - the rero user profile """ - Patron.update_from_profile(profile) + patron = Patron.get_patron_by_user(profile.user) + if patron: + old_keep_history = patron.get('patron', {}).get('keep_history') + patron.reindex() + from ..loans.api import anonymize_loans + new_keep_history = profile.keep_history + if old_keep_history and not new_keep_history: + anonymize_loans( + patron_data=patron, + patron_pid=patron.get('pid'), + dbcommit=True, + reindex=True) diff --git a/rero_ils/modules/patrons/mappings/v7/patrons/patron-v0.0.1.json b/rero_ils/modules/patrons/mappings/v7/patrons/patron-v0.0.1.json index 2faacd1860..8e03ade1a4 100644 --- a/rero_ils/modules/patrons/mappings/v7/patrons/patron-v0.0.1.json +++ b/rero_ils/modules/patrons/mappings/v7/patrons/patron-v0.0.1.json @@ -61,7 +61,40 @@ "city": { "type": "text" }, - "phone": { + "facet_city": { + "type": "keyword", + "copy_to": "facet_city" + }, + "country": { + "type": "text" + }, + "second_address": { + "properties": { + "street": { + "type": "text" + }, + "postal_code": { + "type": "keyword" + }, + "city": { + "type": "text", + "copy_to": "facet_city" + }, + "country": { + "type": "text" + } + } + }, + "home_phone": { + "type": "keyword" + }, + "business_phone": { + "type": "keyword" + }, + "mobile_phone": { + "type": "keyword" + }, + "other_phone": { "type": "keyword" }, "barcode": { diff --git a/rero_ils/modules/patrons/serializers.py b/rero_ils/modules/patrons/serializers.py new file mode 100644 index 0000000000..2d21530555 --- /dev/null +++ b/rero_ils/modules/patrons/serializers.py @@ -0,0 +1,46 @@ +# -*- 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 . + +"""Patrons serialization.""" + +from invenio_records_rest.serializers.response import search_responsify + +from ..patron_types.api import PatronType +from ..serializers import JSONSerializer, RecordSchemaJSONV1 + + +class PatronJSONSerializer(JSONSerializer): + """Mixin serializing records as JSON.""" + + def post_process_serialize_search(self, results, pid_fetcher): + """Post process the search results.""" + # Add patron type name + for type_term in results.get('aggregations', {}).get( + 'patron_type', {}).get('buckets', []): + pid = type_term.get('key') + name = PatronType.get_record_by_pid(pid).get('name') + type_term['key'] = pid + type_term['name'] = name + + return super().post_process_serialize_search(results, pid_fetcher) + + +json_patron = PatronJSONSerializer(RecordSchemaJSONV1) +"""JSON v1 serializer.""" + +json_patron_search = search_responsify( + json_patron, 'application/rero+json') diff --git a/rero_ils/modules/patrons/tasks.py b/rero_ils/modules/patrons/tasks.py index 58fac6fd24..ed2db2ab46 100644 --- a/rero_ils/modules/patrons/tasks.py +++ b/rero_ils/modules/patrons/tasks.py @@ -55,7 +55,7 @@ def is_obsolete(subscription, end_date=None): # NOTE : this update will trigger the listener # `create_subscription_patron_transaction`. This listener will # create a new subscription if needed - patron.update(patron.dumps(), dbcommit=True, reindex=True) + patron.update(Patron.removeUserData(patron.dumps()), dbcommit=True, reindex=True) def check_patron_types_and_add_subscriptions(): diff --git a/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_head.html b/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_head.html index 3669f0336f..7bc2b2dba3 100644 --- a/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_head.html +++ b/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_head.html @@ -21,6 +21,6 @@
-

{{ record.first_name }} {{ record.last_name }}

+

{{ record_dumps.first_name }} {{ record_dumps.last_name }}

diff --git a/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_personal.html b/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_personal.html index d12228bde2..180c54cde0 100644 --- a/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_personal.html +++ b/rero_ils/modules/patrons/templates/rero_ils/_patron_profile_personal.html @@ -96,7 +96,7 @@
- {{ record.patron.barcode }} + {{ record.patron.barcode[0] }}
{% endif %} @@ -117,7 +117,7 @@ {% endif %} - {% if record.patron.barcode %} + {% if record.patron %}
{{_('Keep history')}}: @@ -125,7 +125,7 @@
- {% if record.patron.keep_history %} + {% if record_dumps.keep_history %} {{_('The loan history is saved for a maximum of six months. It is visible to you and the library staff.')}} {%- else %} {{_('The loan history is not saved.')}} diff --git a/rero_ils/modules/patrons/templates/rero_ils/patron_profile.html b/rero_ils/modules/patrons/templates/rero_ils/patron_profile.html index 16629508a2..2aa73d37a2 100644 --- a/rero_ils/modules/patrons/templates/rero_ils/patron_profile.html +++ b/rero_ils/modules/patrons/templates/rero_ils/patron_profile.html @@ -19,6 +19,7 @@ {%- extends 'rero_ils/page_settings.html' %} {% from "rero_ils/_macro_profile.html" import build_fees %} +{% set record_dumps = record.dumps() %} {%- block settings_body %} {% include('rero_ils/_patron_profile_head.html') %} @@ -59,7 +60,7 @@ {{ fees.open.total_amount | format_currency(fees.open.currency) }} - {% if record.patron.keep_history %} + {% if record_dumps.keep_history %}