toulouse_maelis: parsifal: add referentials webservices (#67325)

This commit is contained in:
Nicolas Roche 2022-07-12 18:54:14 +02:00
parent 1a551ab9e5
commit b6b40e501b
7 changed files with 679 additions and 1 deletions

View File

@ -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'),

View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readCategoryListResponse xmlns:ns2="family.ws.maelis.sigec.com">
<itemList>
<code>BI</code>
<libelle>BIPARENTALE</libelle>
</itemList>
<itemList>
<code>ACCEUI</code>
<libelle>FAMILLE D'ACCUEIL</libelle>
</itemList>
<itemList>
<code>MONO</code>
<libelle>MONOPARENTALE</libelle>
</itemList>
</ns2:readCategoryListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readCivilityListResponse xmlns:ns2="family.ws.maelis.sigec.com">
<itemList>
<code>MME</code>
<libelle>Madame</libelle>
</itemList>
<itemList>
<code>M.</code>
<libelle>Monsieur</libelle>
</itemList>
</ns2:readCivilityListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readCSPListResponse xmlns:ns2="family.ws.maelis.sigec.com">
<itemList>
<code>14</code>
<libelle>AGENT DE MAITRISE</libelle>
</itemList>
<itemList>
<code>1</code>
<libelle>AGRICULTEUR</libelle>
</itemList>
<itemList>
<code>AGR</code>
<libelle>AGRICULTEUR</libelle>
</itemList>
<itemList>
<code>ARTI</code>
<libelle>ARTISAN</libelle>
</itemList>
<itemList>
<code>ART</code>
<libelle>ARTISAN</libelle>
</itemList>
<itemList>
<code>2</code>
<libelle>ARTISAN-COMMERCANT</libelle>
</itemList>
<itemList>
<code>15</code>
<libelle>AUTRES</libelle>
</itemList>
<itemList>
<code>CADR</code>
<libelle>CADRE</libelle>
</itemList>
<itemList>
<code>4</code>
<libelle>CADRE</libelle>
</itemList>
<itemList>
<code>13</code>
<libelle>CADRE SUPERIEUR</libelle>
</itemList>
<itemList>
<code>3</code>
<libelle>CHEF D'ENTREPRISE</libelle>
</itemList>
<itemList>
<code>CHOM</code>
<libelle>CHOMEUR</libelle>
</itemList>
<itemList>
<code>COM</code>
<libelle>COMMERCANT</libelle>
</itemList>
<itemList>
<code>COMM</code>
<libelle>COMMERCANT</libelle>
</itemList>
<itemList>
<code>7</code>
<libelle>COMMERCANT</libelle>
</itemList>
<itemList>
<code>10</code>
<libelle>DEMANDEUR D'EMPLOI</libelle>
</itemList>
<itemList>
<code>DIV</code>
<libelle>DIVERS</libelle>
</itemList>
<itemList>
<code>EMP</code>
<libelle>EMPLOYE</libelle>
</itemList>
<itemList>
<code>5</code>
<libelle>EMPLOYE</libelle>
</itemList>
<itemList>
<code>ENS</code>
<libelle>ENSEIGNANT</libelle>
</itemList>
<itemList>
<code>EN</code>
<libelle>ENSEIGNANT</libelle>
</itemList>
<itemList>
<code>17</code>
<libelle>ENSEIGNANT</libelle>
</itemList>
<itemList>
<code>ETU</code>
<libelle>ETUDIANT</libelle>
</itemList>
<itemList>
<code>8</code>
<libelle>ETUDIANT</libelle>
</itemList>
<itemList>
<code>11</code>
<libelle>FONCTIONNAIRE</libelle>
</itemList>
<itemList>
<code>FONC</code>
<libelle>FONCTIONNAIRE</libelle>
</itemList>
<itemList>
<code>FONCT</code>
<libelle>FONCTIONNAIRE </libelle>
</itemList>
<itemList>
<code>MAIR</code>
<libelle>MAIRIE DE NICE</libelle>
</itemList>
<itemList>
<code>OUV</code>
<libelle>OUVRIER</libelle>
</itemList>
<itemList>
<code>6</code>
<libelle>OUVRIER</libelle>
</itemList>
<itemList>
<code>PERENS</code>
<libelle>PERISCO ENSEIGNANT</libelle>
</itemList>
<itemList>
<code>PEREXT</code>
<libelle>PERISCO EXTERNE</libelle>
</itemList>
<itemList>
<code>PERMAI</code>
<libelle>PERISCO MAIRIE DE NICE</libelle>
</itemList>
<itemList>
<code>PERANI</code>
<libelle>PERISCO S.ANIMATION</libelle>
</itemList>
<itemList>
<code>9</code>
<libelle>PROFESSION LIBERALE</libelle>
</itemList>
<itemList>
<code>LIB</code>
<libelle>PROFESSION LIBERALE</libelle>
</itemList>
<itemList>
<code>12</code>
<libelle>RETRAITE</libelle>
</itemList>
<itemList>
<code>RET</code>
<libelle>RETRAITE</libelle>
</itemList>
<itemList>
<code>RMI</code>
<libelle>REVENU MINIMUM D'INSERTION</libelle>
</itemList>
<itemList>
<code>16</code>
<libelle>SANS PROFESSION</libelle>
</itemList>
<itemList>
<code>SANPRO</code>
<libelle>SANS PROFESSION</libelle>
</itemList>
<itemList>
<code>SANS</code>
<libelle>SANS PROFESSION</libelle>
</itemList>
<itemList>
<code>SEC</code>
<libelle>SECRETAIRE</libelle>
</itemList>
</ns2:readCSPListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readQualityListResponse xmlns:ns2="family.ws.maelis.sigec.com">
<itemList>
<code>AU</code>
<libelle>AUTRE</libelle>
</itemList>
<itemList>
<code>BP</code>
<libelle>BEAU PERE</libelle>
</itemList>
<itemList>
<code>BM</code>
<libelle>BELLE MERE</libelle>
</itemList>
<itemList>
<code>CONSO</code>
<libelle>CONSOMMATEUR</libelle>
</itemList>
<itemList>
<code>EN</code>
<libelle>ENFANT</libelle>
</itemList>
<itemList>
<code>FS</code>
<libelle>FRERES ET SOEURS</libelle>
</itemList>
<itemList>
<code>GM</code>
<libelle>GRAND MERE MATERNELLE</libelle>
</itemList>
<itemList>
<code>GP</code>
<libelle>GRAND PERE MATERNEL</libelle>
</itemList>
<itemList>
<code>GMP</code>
<libelle>GRAND-MERE PATERNELLE</libelle>
</itemList>
<itemList>
<code>GPP</code>
<libelle>GRAND-PERE PATERNEL</libelle>
</itemList>
<itemList>
<code>MAIRIE</code>
<libelle>MAIRIE</libelle>
</itemList>
<itemList>
<code>MERE</code>
<libelle>MERE</libelle>
</itemList>
<itemList>
<code>O</code>
<libelle>ONCLE</libelle>
</itemList>
<itemList>
<code>OS</code>
<libelle>ORGANISME SOCIAL</libelle>
</itemList>
<itemList>
<code>PERE</code>
<libelle>PERE</libelle>
</itemList>
<itemList>
<code>T</code>
<libelle>TANTE</libelle>
</itemList>
<itemList>
<code>TUTEUR</code>
<libelle>TUTEUR</libelle>
</itemList>
</ns2:readQualityListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readSituationListResponse xmlns:ns2="family.ws.maelis.sigec.com">
<itemList>
<code>C</code>
<libelle>Célibataire</libelle>
</itemList>
<itemList>
<code>D</code>
<libelle>Divorcé (e)</libelle>
</itemList>
<itemList>
<code>CS</code>
<libelle>EN COURS DE SEPARATION</libelle>
</itemList>
<itemList>
<code>M</code>
<libelle>Marié (e)</libelle>
</itemList>
<itemList>
<code>P</code>
<libelle>Pacsé (e)</libelle>
</itemList>
<itemList>
<code>S</code>
<libelle>Séparé (e)</libelle>
</itemList>
<itemList>
<code>UL</code>
<libelle>UNION LIBRE</libelle>
</itemList>
<itemList>
<code>V</code>
<libelle>Veuf (ve)</libelle>
</itemList>
<itemList>
<code>VM</code>
<libelle>Vivant maritalement</libelle>
</itemList>
</ns2:readSituationListResponse>
</soap:Body>
</soap:Envelope>

View File

@ -14,6 +14,7 @@
# 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/>.
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):