diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index 9a30ea73..dc7a591f 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -17,6 +17,7 @@
from urllib.parse import urljoin
import zeep
+from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from zeep.helpers import serialize_object
@@ -68,6 +69,44 @@ class ToulouseMaelis(BaseResource, HTTPResource):
assert self.call('Family', 'isWSRunning')
assert self.call('Activity', 'isWSRunning')
+ def get_referential(self, referential_name):
+
+ # local referentials
+ if referential_name == 'Complement':
+ response = [
+ {'id': 'B', 'text': 'bis'},
+ {'id': 'T', 'text': 'ter'},
+ {'id': 'Q', 'text': 'quater'},
+ ]
+ return {'list': response, 'dict': {x['id']: x['text'] for x in response}}
+ elif referential_name == 'Sex':
+ response = [
+ {'id': 'M', 'text': 'Masculin'},
+ {'id': 'F', 'text': 'Féminin'},
+ ]
+ return {'list': response, 'dict': {x['id']: x['text'] for x in response}}
+
+ # remote referentials
+ cache_key = 'maelis-%s-%s' % (self.pk, referential_name)
+ data = cache.get(cache_key)
+ if data is None:
+ response = self.call('Family', 'read' + referential_name + 'List')
+ data = {
+ 'list': [{'id': x.code, 'text': x.libelle} for x in response],
+ 'dict': {x.code: x.libelle for x in response},
+ }
+ # put in cache for two hours
+ cache.set(cache_key, data, 3600 * 2)
+ return data
+
+ def get_referential_value(self, referential_name, key):
+ try:
+ return self.get_referential(referential_name)['dict'][key]
+ except KeyError:
+ # Maelis DB not properly configurated
+ self.logger.warning("No '%s' key into Maelis '%s' referential", key, referential_name)
+ return key
+
def get_link(self, NameID):
try:
return self.link_set.get(name_id=NameID)
@@ -77,8 +116,101 @@ class ToulouseMaelis(BaseResource, HTTPResource):
def get_family(self, family_id):
response = self.call('Family', 'readFamily', dossierNumber=family_id)
data = serialize_object(response)
+
+ def add_text_value(referential_name, data, keys):
+ last_key = keys.pop()
+ for key in keys:
+ if not isinstance(data, dict) or not key in data:
+ return
+ data = data[key]
+ if isinstance(data, dict) and last_key in data and data[last_key] is not None:
+ data[last_key + '_text'] = self.get_referential_value(referential_name, data[last_key])
+
+ # add text from referentials
+ add_text_value('Category', data, ['category'])
+ add_text_value('Situation', data, ['situation'])
+ for rlg in 'RL1', 'RL2':
+ add_text_value('Civility', data, [rlg, 'civility'])
+ add_text_value('Quality', data, [rlg, 'quality'])
+ add_text_value('Complement', data, [rlg, 'adresse', 'numComp'])
+ add_text_value('CSP', data, [rlg, 'profession', 'codeCSP'])
+ for child in data['childList']:
+ add_text_value('Sex', child, ['sexe'])
return data
+ @endpoint(
+ display_category=_('Family'),
+ description='Liste des catégories',
+ name='read-category-list',
+ perm='can_access',
+ )
+ def read_category_list(self, request):
+ return {'data': self.get_referential('Category')['list']}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='Liste des civilités',
+ name='read-civility-list',
+ perm='can_access',
+ )
+ def read_civility_list(self, request):
+ return {'data': self.get_referential('Civility')['list']}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='Liste des compléments du numéro de voie',
+ name='read-complement-list',
+ perm='can_access',
+ )
+ def read_complement_list(self, request):
+ return {'data': self.get_referential('Complement')['list']}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='liste des catégories socio-professionnelles',
+ name='read-csp-list',
+ perm='can_access',
+ )
+ def read_csp_list(self, request):
+ data = self.get_referential('CSP')['list']
+
+ # remove redundant codes
+ uniq_text = set()
+ uniq_data = []
+ for item in data:
+ item['text'] = item['text'].strip()
+ if item['text'] not in uniq_text:
+ uniq_data.append(item)
+ uniq_text.add(item['text'])
+ return {'data': uniq_data}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='liste des qualités du référenciel',
+ name='read-quality-list',
+ perm='can_access',
+ )
+ def read_quality_list(self, request):
+ return {'data': self.get_referential('Quality')['list']}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='Liste des sexes',
+ name='read-sex-list',
+ perm='can_access',
+ )
+ def read_sex_list(self, request):
+ return {'data': self.get_referential('Sex')['list']}
+
+ @endpoint(
+ display_category=_('Family'),
+ description='liste des situations',
+ name='read-situation-list',
+ perm='can_access',
+ )
+ def read_situation_list(self, request):
+ return {'data': self.get_referential('Situation')['list']}
+
@endpoint(
display_category=_('Family'),
description=_('Create link between user and family'),
diff --git a/tests/data/toulouse_maelis/R_read_category_list.xml b/tests/data/toulouse_maelis/R_read_category_list.xml
new file mode 100644
index 00000000..19d2d933
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_category_list.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ BI
+ BIPARENTALE
+
+
+ ACCEUI
+ FAMILLE D'ACCUEIL
+
+
+ MONO
+ MONOPARENTALE
+
+
+
+
diff --git a/tests/data/toulouse_maelis/R_read_civility_list.xml b/tests/data/toulouse_maelis/R_read_civility_list.xml
new file mode 100644
index 00000000..012c222d
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_civility_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ MME
+ Madame
+
+
+ M.
+ Monsieur
+
+
+
+
diff --git a/tests/data/toulouse_maelis/R_read_csp_list.xml b/tests/data/toulouse_maelis/R_read_csp_list.xml
new file mode 100644
index 00000000..e389e734
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_csp_list.xml
@@ -0,0 +1,179 @@
+
+
+
+
+
+ 14
+ AGENT DE MAITRISE
+
+
+ 1
+ AGRICULTEUR
+
+
+ AGR
+ AGRICULTEUR
+
+
+ ARTI
+ ARTISAN
+
+
+ ART
+ ARTISAN
+
+
+ 2
+ ARTISAN-COMMERCANT
+
+
+ 15
+ AUTRES
+
+
+ CADR
+ CADRE
+
+
+ 4
+ CADRE
+
+
+ 13
+ CADRE SUPERIEUR
+
+
+ 3
+ CHEF D'ENTREPRISE
+
+
+ CHOM
+ CHOMEUR
+
+
+ COM
+ COMMERCANT
+
+
+ COMM
+ COMMERCANT
+
+
+ 7
+ COMMERCANT
+
+
+ 10
+ DEMANDEUR D'EMPLOI
+
+
+ DIV
+ DIVERS
+
+
+ EMP
+ EMPLOYE
+
+
+ 5
+ EMPLOYE
+
+
+ ENS
+ ENSEIGNANT
+
+
+ EN
+ ENSEIGNANT
+
+
+ 17
+ ENSEIGNANT
+
+
+ ETU
+ ETUDIANT
+
+
+ 8
+ ETUDIANT
+
+
+ 11
+ FONCTIONNAIRE
+
+
+ FONC
+ FONCTIONNAIRE
+
+
+ FONCT
+ FONCTIONNAIRE
+
+
+ MAIR
+ MAIRIE DE NICE
+
+
+ OUV
+ OUVRIER
+
+
+ 6
+ OUVRIER
+
+
+ PERENS
+ PERISCO ENSEIGNANT
+
+
+ PEREXT
+ PERISCO EXTERNE
+
+
+ PERMAI
+ PERISCO MAIRIE DE NICE
+
+
+ PERANI
+ PERISCO S.ANIMATION
+
+
+ 9
+ PROFESSION LIBERALE
+
+
+ LIB
+ PROFESSION LIBERALE
+
+
+ 12
+ RETRAITE
+
+
+ RET
+ RETRAITE
+
+
+ RMI
+ REVENU MINIMUM D'INSERTION
+
+
+ 16
+ SANS PROFESSION
+
+
+ SANPRO
+ SANS PROFESSION
+
+
+ SANS
+ SANS PROFESSION
+
+
+ SEC
+ SECRETAIRE
+
+
+
+
diff --git a/tests/data/toulouse_maelis/R_read_quality_list.xml b/tests/data/toulouse_maelis/R_read_quality_list.xml
new file mode 100644
index 00000000..f86e3147
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_quality_list.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+ AU
+ AUTRE
+
+
+ BP
+ BEAU PERE
+
+
+ BM
+ BELLE MERE
+
+
+ CONSO
+ CONSOMMATEUR
+
+
+ EN
+ ENFANT
+
+
+ FS
+ FRERES ET SOEURS
+
+
+ GM
+ GRAND MERE MATERNELLE
+
+
+ GP
+ GRAND PERE MATERNEL
+
+
+ GMP
+ GRAND-MERE PATERNELLE
+
+
+ GPP
+ GRAND-PERE PATERNEL
+
+
+ MAIRIE
+ MAIRIE
+
+
+ MERE
+ MERE
+
+
+ O
+ ONCLE
+
+
+ OS
+ ORGANISME SOCIAL
+
+
+ PERE
+ PERE
+
+
+ T
+ TANTE
+
+
+ TUTEUR
+ TUTEUR
+
+
+
+
diff --git a/tests/data/toulouse_maelis/R_read_situation_list.xml b/tests/data/toulouse_maelis/R_read_situation_list.xml
new file mode 100644
index 00000000..cb3f7d58
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_situation_list.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+ C
+ Célibataire
+
+
+ D
+ Divorcé (e)
+
+
+ CS
+ EN COURS DE SEPARATION
+
+
+ M
+ Marié (e)
+
+
+ P
+ Pacsé (e)
+
+
+ S
+ Séparé (e)
+
+
+ UL
+ UNION LIBRE
+
+
+ V
+ Veuf (ve)
+
+
+ VM
+ Vivant maritalement
+
+
+
+
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index ed16a1b6..feb73ff9 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import logging
import os
import mock
@@ -40,6 +41,11 @@ FAILED_AUTH = FakedResponse(content=get_xml_file('R_failed_authentication.xml'),
ISWSRUNNING_TRUE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'true', status_code=200)
ISWSRUNNING_FALSE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'false', status_code=200)
READ_FAMILY = FakedResponse(content=get_xml_file('R_read_family.xml'), status_code=200)
+READ_CATEGORIES = FakedResponse(content=get_xml_file('R_read_category_list.xml'), status_code=200)
+READ_CIVILITIES = FakedResponse(content=get_xml_file('R_read_civility_list.xml'), status_code=200)
+READ_CSP = FakedResponse(content=get_xml_file('R_read_csp_list.xml'), status_code=200)
+READ_QUALITIES = FakedResponse(content=get_xml_file('R_read_quality_list.xml'), status_code=200)
+READ_SITUATIONS = FakedResponse(content=get_xml_file('R_read_situation_list.xml'), status_code=200)
def get_endpoint(name):
@@ -160,11 +166,197 @@ def test_unlink(con, app):
assert resp.json['err_desc'] == 'User not linked to family'
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_get_referential_using_cache(mocked_post, mocked_get, con, freezer):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CATEGORIES
+
+ freezer.move_to('2021-11-10 00:00')
+ con.get_referential('Category')
+ assert mocked_post.call_count == 1
+
+ con.get_referential('Category')
+ assert mocked_post.call_count == 1
+
+ freezer.move_to('2021-11-10 02:00')
+ con.get_referential('Category')
+ assert mocked_post.call_count == 2
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_get_referential_value(mocked_post, mocked_get, con):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CSP
+
+ assert con.get_referential_value('CSP', '1') == 'AGRICULTEUR'
+ assert con.get_referential_value('CSP', 'AGR') == 'AGRICULTEUR'
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_get_referential_value_not_found(mocked_post, mocked_get, con, caplog):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CIVILITIES
+
+ assert con.get_referential_value('Civility', 'MR') == 'MR'
+ assert len(caplog.records) == 1
+ assert caplog.records[0].levelno == logging.WARNING
+ assert caplog.records[0].message == "No 'MR' key into Maelis 'Civility' referential"
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_category_list(mocked_post, mocked_get, con, app):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CATEGORIES
+ url = get_endpoint('read-category-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': 'BI', 'text': 'BIPARENTALE'},
+ {'id': 'ACCEUI', 'text': "FAMILLE D'ACCUEIL"},
+ {'id': 'MONO', 'text': 'MONOPARENTALE'},
+ ]
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_civility_list(mocked_post, mocked_get, con, app):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CIVILITIES
+ url = get_endpoint('read-civility-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {"id": "MME", "text": "Madame"},
+ {"id": "M.", "text": "Monsieur"},
+ ]
+
+
+def test_read_complement_list(con, app):
+ url = get_endpoint('read-complement-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': 'B', 'text': 'bis'},
+ {'id': 'T', 'text': 'ter'},
+ {'id': 'Q', 'text': 'quater'},
+ ]
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_csp_list(mocked_post, mocked_get, con, app):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_CSP
+ url = get_endpoint('read-csp-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': '14', 'text': 'AGENT DE MAITRISE'},
+ {'id': '1', 'text': 'AGRICULTEUR'},
+ {'id': 'ARTI', 'text': 'ARTISAN'},
+ {'id': '2', 'text': 'ARTISAN-COMMERCANT'},
+ {'id': '15', 'text': 'AUTRES'},
+ {'id': 'CADR', 'text': 'CADRE'},
+ {'id': '13', 'text': 'CADRE SUPERIEUR'},
+ {'id': '3', 'text': "CHEF D'ENTREPRISE"},
+ {'id': 'CHOM', 'text': 'CHOMEUR'},
+ {'id': 'COM', 'text': 'COMMERCANT'},
+ {'id': '10', 'text': "DEMANDEUR D'EMPLOI"},
+ {'id': 'DIV', 'text': 'DIVERS'},
+ {'id': 'EMP', 'text': 'EMPLOYE'},
+ {'id': 'ENS', 'text': 'ENSEIGNANT'},
+ {'id': 'ETU', 'text': 'ETUDIANT'},
+ {'id': '11', 'text': 'FONCTIONNAIRE'},
+ {'id': 'MAIR', 'text': 'MAIRIE DE NICE'},
+ {'id': 'OUV', 'text': 'OUVRIER'},
+ {'id': 'PERENS', 'text': 'PERISCO ENSEIGNANT'},
+ {'id': 'PEREXT', 'text': 'PERISCO EXTERNE'},
+ {'id': 'PERMAI', 'text': 'PERISCO MAIRIE DE NICE'},
+ {'id': 'PERANI', 'text': 'PERISCO S.ANIMATION'},
+ {'id': '9', 'text': 'PROFESSION LIBERALE'},
+ {'id': '12', 'text': 'RETRAITE'},
+ {'id': 'RMI', 'text': "REVENU MINIMUM D'INSERTION"},
+ {'id': '16', 'text': 'SANS PROFESSION'},
+ {'id': 'SEC', 'text': 'SECRETAIRE'},
+ ]
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_quality_list(mocked_post, mocked_get, con, app):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_QUALITIES
+ url = get_endpoint('read-quality-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': 'AU', 'text': 'AUTRE'},
+ {'id': 'BP', 'text': 'BEAU PERE'},
+ {'id': 'BM', 'text': 'BELLE MERE'},
+ {'id': 'CONSO', 'text': 'CONSOMMATEUR'},
+ {'id': 'EN', 'text': 'ENFANT'},
+ {'id': 'FS', 'text': 'FRERES ET SOEURS'},
+ {'id': 'GM', 'text': 'GRAND MERE MATERNELLE'},
+ {'id': 'GP', 'text': 'GRAND PERE MATERNEL'},
+ {'id': 'GMP', 'text': 'GRAND-MERE PATERNELLE'},
+ {'id': 'GPP', 'text': 'GRAND-PERE PATERNEL'},
+ {'id': 'MAIRIE', 'text': 'MAIRIE'},
+ {'id': 'MERE', 'text': 'MERE'},
+ {'id': 'O', 'text': 'ONCLE'},
+ {'id': 'OS', 'text': 'ORGANISME SOCIAL'},
+ {'id': 'PERE', 'text': 'PERE'},
+ {'id': 'T', 'text': 'TANTE'},
+ {'id': 'TUTEUR', 'text': 'TUTEUR'},
+ ]
+
+
+def test_read_sex_list(con, app):
+ url = get_endpoint('read-sex-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': 'M', 'text': 'Masculin'},
+ {'id': 'F', 'text': 'Féminin'},
+ ]
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_situation_list(mocked_post, mocked_get, con, app):
+ mocked_get.return_value = FAMILY_SERVICE_WSDL
+ mocked_post.return_value = READ_SITUATIONS
+ url = get_endpoint('read-situation-list')
+
+ resp = app.get(url)
+ assert resp.json['err'] == 0
+ assert resp.json['data'] == [
+ {'id': 'C', 'text': 'Célibataire'},
+ {'id': 'D', 'text': 'Divorcé (e)'},
+ {'id': 'CS', 'text': 'EN COURS DE SEPARATION'},
+ {'id': 'M', 'text': 'Marié (e)'},
+ {'id': 'P', 'text': 'Pacsé (e)'},
+ {'id': 'S', 'text': 'Séparé (e)'},
+ {'id': 'UL', 'text': 'UNION LIBRE'},
+ {'id': 'V', 'text': 'Veuf (ve)'},
+ {'id': 'VM', 'text': 'Vivant maritalement'},
+ ]
+
+
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_read_family(mocked_post, mocked_get, con, app):
mocked_get.return_value = FAMILY_SERVICE_WSDL
- mocked_post.side_effect = [READ_FAMILY]
+ mocked_post.side_effect = [READ_FAMILY, READ_CATEGORIES, READ_SITUATIONS, READ_CIVILITIES, READ_QUALITIES]
url = get_endpoint('read-family')
Link.objects.create(resource=con, family_id='1312', name_id='local')
@@ -175,7 +367,9 @@ def test_read_family(mocked_post, mocked_get, con, app):
'lastname': 'COSTANZE',
'firstname': 'DAMIEN',
'quality': 'PERE',
+ 'quality_text': 'PERE',
'civility': 'M.',
+ 'civility_text': 'Monsieur',
'dateBirth': '1980-10-07T00:00:00+01:00',
'adresse': {
'idStreet': 'AV0044',
@@ -197,6 +391,27 @@ def test_read_family(mocked_post, mocked_get, con, app):
'profession': None,
'CAFInfo': {'number': '51', 'organ': None},
}
+ assert resp.json['data']['childList'][0] == {
+ 'num': '613880',
+ 'lastname': 'COSTANZE',
+ 'firstname': 'CASSANDRA',
+ 'sexe': 'F',
+ 'sexe_text': 'Féminin',
+ 'birth': {'dateBirth': '2021-06-22T00:00:00+02:00', 'place': None},
+ 'dietcode': 'STD',
+ 'fsl': None,
+ 'bPhoto': False,
+ 'bLeaveAlone': False,
+ 'authorizedPersonList': [],
+ 'indicatorList': [],
+ 'medicalRecord': None,
+ 'subscribeSchoolList': [],
+ 'mother': None,
+ 'father': None,
+ 'rl': None,
+ 'subscribeActivityList': [],
+ 'paiInfoBean': None,
+ }
def test_read_family_not_linked_error(con, app):