0001-start-atal-connector-33348.patch
passerelle/apps/atal/migrations/0001_initial.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2019-05-24 10:25 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
initial = True |
|
11 | ||
12 |
dependencies = [ |
|
13 |
('base', '0012_job'), |
|
14 |
] |
|
15 | ||
16 |
operations = [ |
|
17 |
migrations.CreateModel( |
|
18 |
name='ATALConnector', |
|
19 |
fields=[ |
|
20 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
21 |
('title', models.CharField(max_length=50, verbose_name='Title')), |
|
22 |
('description', models.TextField(verbose_name='Description')), |
|
23 |
('slug', models.SlugField(unique=True, verbose_name='Identifier')), |
|
24 |
('base_soap_url', models.URLField(help_text='URL of the base SOAP endpoint', max_length=400, verbose_name='Base SOAP endpoint')), |
|
25 |
('users', models.ManyToManyField(blank=True, related_name='_atalconnector_users_+', related_query_name='+', to='base.ApiUser')), |
|
26 |
], |
|
27 |
options={ |
|
28 |
'verbose_name': 'ATAL connector', |
|
29 |
}, |
|
30 |
), |
|
31 |
] |
passerelle/apps/atal/models.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
# Copyright (C) 2019 Entr'ouvert |
|
4 |
# |
|
5 |
# This program is free software: you can redistribute it and/or modify it |
|
6 |
# under the terms of the GNU Affero General Public License as published |
|
7 |
# by the Free Software Foundation, either version 3 of the License, or |
|
8 |
# (at your option) any later version. |
|
9 |
# |
|
10 |
# This program is distributed in the hope that it will be useful, |
|
11 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
# GNU Affero General Public License for more details. |
|
14 |
# |
|
15 |
# You should have received a copy of the GNU Affero General Public License |
|
16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 | ||
18 | ||
19 |
from django.db import models |
|
20 |
from django.utils.six.moves import urllib |
|
21 |
from django.utils.translation import ugettext_lazy as _ |
|
22 |
import lxml.etree |
|
23 |
from zeep import helpers |
|
24 | ||
25 |
from passerelle.base.models import BaseResource |
|
26 |
from passerelle.utils.api import endpoint |
|
27 |
from passerelle.utils.jsonresponse import APIError |
|
28 | ||
29 | ||
30 |
INSERT_DEMANDE_BY_TYPE_SCHEMA = { |
|
31 |
"$schema": "http://json-schema.org/draft-03/schema#", |
|
32 |
"title": "", |
|
33 |
"description": "", |
|
34 |
"type": "object", |
|
35 |
"properties": { |
|
36 |
"contact_nom": { |
|
37 |
"description": "Nom du contact", |
|
38 |
"required": True |
|
39 |
}, |
|
40 |
"contact_tel": { |
|
41 |
"description": "Téléphone du contact", |
|
42 |
"type": "string", |
|
43 |
}, |
|
44 |
"contact_email": { |
|
45 |
"description": "Email du contact", |
|
46 |
"type": "string", |
|
47 |
}, |
|
48 |
"contact_adresse": { |
|
49 |
"description": "Adresse du contact", |
|
50 |
"type": "string", |
|
51 |
}, |
|
52 |
"demande_objet": { |
|
53 |
"description": "Object de la demande", |
|
54 |
"type": "string", |
|
55 |
}, |
|
56 |
"demande_lieu": { |
|
57 |
"description": "Lieu de la demande", |
|
58 |
"type": "string", |
|
59 |
}, |
|
60 |
"demande_description": { |
|
61 |
"description": "Description de la demande", |
|
62 |
"type": "string", |
|
63 |
}, |
|
64 |
"remote_adresse": { |
|
65 |
"description": "", |
|
66 |
"type": "string" |
|
67 |
}, |
|
68 |
"code_equipement": { |
|
69 |
"description": "Code de l'équipement", |
|
70 |
"type": "string" |
|
71 |
}, |
|
72 |
"code_service_demandeur": { |
|
73 |
"description": "Code du service demandeur", |
|
74 |
"type": "string" |
|
75 |
}, |
|
76 |
"date_souhaite": { |
|
77 |
"description": "Date souhaitée", |
|
78 |
"type": "string" |
|
79 |
}, |
|
80 |
"type_demande": { |
|
81 |
"description": "Type demande", |
|
82 |
"type": "string" |
|
83 |
} |
|
84 |
} |
|
85 |
} |
|
86 | ||
87 | ||
88 |
class ATALConnector(BaseResource): |
|
89 |
base_soap_url = models.URLField( |
|
90 |
max_length=400, verbose_name=_('Base SOAP endpoint'), |
|
91 |
help_text=_('URL of the base SOAP endpoint')) |
|
92 |
category = _('Business Process Connectors') |
|
93 | ||
94 |
class Meta: |
|
95 |
verbose_name = _('ATAL connector') |
|
96 | ||
97 |
def _soap_call(self, wsdl, method, **kwargs): |
|
98 |
wsdl_url = urllib.parse.urljoin(self.base_soap_url, '%s?wsdl' % wsdl) |
|
99 |
client = self.soap_client(wsdl_url=wsdl_url) |
|
100 |
return getattr(client.service, method)(**kwargs) |
|
101 | ||
102 |
def _basic_ref(self, wsdl, method): |
|
103 |
soap_res = self._soap_call(wsdl=wsdl, method=method) |
|
104 |
res = [] |
|
105 |
for elem in soap_res: |
|
106 |
res.append({'id': elem.code, 'text': elem.libelle}) |
|
107 |
return {'data': res} |
|
108 | ||
109 |
@endpoint(methods=['get'], perm='can_access') |
|
110 |
def get_type_activite(self, request): |
|
111 |
return self._basic_ref('VilleAgileService', 'getTypeActivite') |
|
112 | ||
113 |
@endpoint(methods=['get'], perm='can_access') |
|
114 |
def get_type_de_voie(self, request): |
|
115 |
return self._basic_ref('VilleAgileService', 'getTypeDeVoie') |
|
116 | ||
117 |
@endpoint(methods=['get'], perm='can_access') |
|
118 |
def get_types_equipement(self, request): |
|
119 |
soap_res = self._soap_call(wsdl='VilleAgileService', method='getTypesEquipement') |
|
120 |
tree = lxml.etree.fromstring(soap_res.encode('utf-8')).getroottree() |
|
121 |
types = tree.xpath('//types')[0] |
|
122 |
res = [] |
|
123 |
for type_elem in types.getchildren(): |
|
124 |
res.append({'id': type_elem.get('id'), 'text': type_elem.get('label')}) |
|
125 |
return {'data': res} |
|
126 | ||
127 |
@endpoint( |
|
128 |
perm='can_access', |
|
129 |
post={ |
|
130 |
'description': _('Insert Demande By Type'), |
|
131 |
'request_body': { |
|
132 |
'schema': { |
|
133 |
'application/json': INSERT_DEMANDE_BY_TYPE_SCHEMA |
|
134 |
} |
|
135 |
} |
|
136 |
} |
|
137 |
) |
|
138 |
def insert_demande_by_type(self, request, post_data): |
|
139 |
demande_number = self._soap_call( |
|
140 |
wsdl='DemandeService', method='insertDemandeByType', |
|
141 |
contactNom=post_data['contact_nom'], |
|
142 |
contactTelephone=post_data['contact_telephone'], |
|
143 |
contactCourriel=post_data['contact_email'], |
|
144 |
contactAdresse=post_data['contact_adresse'], demandeObjet=post_data['demande_objet'], |
|
145 |
demandeLieu=post_data['demande_lieu'], |
|
146 |
demandeDescription=post_data['demande_description'], |
|
147 |
remoteAddress=post_data['remote_adresse'], codeEquipement=post_data['code_equipement'], |
|
148 |
codeServiceDemandeur=post_data['code_service_demandeur'], |
|
149 |
dateSouhaitee=post_data['date_souhaite'], typeDemande=post_data['type_demande'] |
|
150 |
) |
|
151 |
return {'data': {'demande_number': demande_number}} |
|
152 | ||
153 |
@endpoint( |
|
154 |
methods=['get'], perm='can_access', example_pattern='{demande_number}/', |
|
155 |
pattern='^(?P<demande_number>\w+)/$', |
|
156 |
parameters={ |
|
157 |
'demande_number': { |
|
158 |
'description': _('Demande number'), 'example_value': 'DIT18050001' |
|
159 |
} |
|
160 |
} |
|
161 |
) |
|
162 |
def retrieve_details_demande(self, request, demande_number, **kwargs): |
|
163 |
if not demande_number: |
|
164 |
raise APIError('A demande_number parameter must be specified') |
|
165 |
soap_res = self._soap_call( |
|
166 |
wsdl='DemandeService', method='retrieveDetailsDemande', |
|
167 |
demandeNumberParam=demande_number) |
|
168 |
return {'data': helpers.serialize_object(soap_res)} |
passerelle/settings.py | ||
---|---|---|
123 | 123 |
'passerelle.apps.api_particulier', |
124 | 124 |
'passerelle.apps.arcgis', |
125 | 125 |
'passerelle.apps.arpege_ecp', |
126 |
'passerelle.apps.atal', |
|
126 | 127 |
'passerelle.apps.atos_genesys', |
127 | 128 |
'passerelle.apps.base_adresse', |
128 | 129 |
'passerelle.apps.bdp', |
tests/test_atal.py | ||
---|---|---|
1 |
from django.contrib.contenttypes.models import ContentType |
|
2 |
import mock |
|
3 |
import pytest |
|
4 | ||
5 |
from passerelle.apps.atal.models import ATALConnector |
|
6 |
from passerelle.base.models import ApiUser, AccessRight |
|
7 | ||
8 | ||
9 |
@pytest.fixture() |
|
10 |
def connector(db): |
|
11 |
api = ApiUser.objects.create(username='all', keytype='', key='') |
|
12 |
connector = ATALConnector.objects.create( |
|
13 |
base_soap_url='http://example.atal.com/', slug='slug-atal') |
|
14 |
obj_type = ContentType.objects.get_for_model(connector) |
|
15 |
AccessRight.objects.create( |
|
16 |
codename='can_access', apiuser=api, resource_type=obj_type, resource_pk=connector.pk) |
|
17 |
return connector |
|
18 | ||
19 | ||
20 |
def mock_atal_soap_call(monkeypatch, return_value=None, side_effect=None): |
|
21 |
kwargs = {} |
|
22 |
if return_value is not None: |
|
23 |
kwargs['return_value'] = return_value |
|
24 |
if side_effect is not None: |
|
25 |
kwargs['side_effect'] = side_effect |
|
26 |
mock_soap_call = mock.Mock(**kwargs) |
|
27 |
monkeypatch.setattr(ATALConnector, '_soap_call', mock_soap_call) |
|
28 |
return mock_soap_call |
|
29 | ||
30 | ||
31 |
class SoapElem(object): |
|
32 | ||
33 |
def __init__(self, **kwargs): |
|
34 |
for attr, value in kwargs.items(): |
|
35 |
setattr(self, attr, value) |
|
36 | ||
37 | ||
38 |
REFS = [ |
|
39 |
SoapElem(code='code1', libelle='elem1'), SoapElem(code='code2', libelle='elem2') |
|
40 |
] |
|
41 | ||
42 | ||
43 |
def test_get_type_activite(app, connector, monkeypatch): |
|
44 |
mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS) |
|
45 |
response = app.get('/atal/slug-atal/get_type_activite') |
|
46 |
assert response.json == { |
|
47 |
'err': 0, |
|
48 |
'data': [ |
|
49 |
{'text': 'elem1', 'id': 'code1'}, |
|
50 |
{'text': 'elem2', 'id': 'code2'}] |
|
51 |
} |
|
52 |
call_params = mock_soap_call.call_args.kwargs |
|
53 |
assert call_params['wsdl'] == 'VilleAgileService' |
|
54 |
assert call_params['method'] == 'getTypeActivite' |
|
55 | ||
56 | ||
57 |
def test_get_type_de_voie(app, connector, monkeypatch): |
|
58 |
mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS) |
|
59 |
response = app.get('/atal/slug-atal/get_type_de_voie') |
|
60 |
assert response.json == { |
|
61 |
'err': 0, |
|
62 |
'data': [ |
|
63 |
{'text': 'elem1', 'id': 'code1'}, |
|
64 |
{'text': 'elem2', 'id': 'code2'}] |
|
65 |
} |
|
66 |
call_params = mock_soap_call.call_args.kwargs |
|
67 |
assert call_params['wsdl'] == 'VilleAgileService' |
|
68 |
assert call_params['method'] == 'getTypeDeVoie' |
|
69 | ||
70 | ||
71 |
def test_get_types_equipement(app, connector, monkeypatch): |
|
72 |
return_value = u"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
73 |
<types> |
|
74 |
<type id="2" label="Espaces Verts"></type> |
|
75 |
<type id="4" label="Voirie"> |
|
76 |
</type> |
|
77 |
</types> |
|
78 |
""" |
|
79 |
mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=return_value) |
|
80 |
response = app.get('/atal/slug-atal/get_types_equipement') |
|
81 |
assert response.json == { |
|
82 |
'err': 0, |
|
83 |
'data': [ |
|
84 |
{'text': 'Espaces Verts', 'id': '2'}, |
|
85 |
{'text': 'Voirie', 'id': '4'}] |
|
86 |
} |
|
87 |
call_params = mock_soap_call.call_args.kwargs |
|
88 |
assert call_params['wsdl'] == 'VilleAgileService' |
|
89 |
assert call_params['method'] == 'getTypesEquipement' |
|
90 | ||
91 | ||
92 |
def test_insert_demande_by_type(app, connector, monkeypatch): |
|
93 |
mock_soap_call = mock_atal_soap_call(monkeypatch, return_value='DIT19050001') |
|
94 |
params = { |
|
95 |
'contact_nom': 'John Doe', |
|
96 |
'contact_telephone': '0101010101', |
|
97 |
'contact_email': 'john@doe.com', |
|
98 |
'contact_adresse': '1 doe street', |
|
99 |
'demande_objet': 'sarah connor', |
|
100 |
'demande_lieu': 'LA', |
|
101 |
'demande_description': 'poker face', |
|
102 |
'remote_adresse': 'hollywood bd', |
|
103 |
'code_equipement': 'MAC10', |
|
104 |
'code_service_demandeur': 'skynet', |
|
105 |
'date_souhaite': 'now', |
|
106 |
'type_demande': 'scary' |
|
107 |
} |
|
108 |
response = app.post_json('/atal/slug-atal/insert_demande_by_type', params=params) |
|
109 |
assert response.json == { |
|
110 |
'err': 0, |
|
111 |
'data': {'demande_number': 'DIT19050001'} |
|
112 |
} |
|
113 |
call_params = mock_soap_call.call_args.kwargs |
|
114 |
assert call_params['wsdl'] == 'DemandeService' |
|
115 |
assert call_params['method'] == 'insertDemandeByType' |
|
116 |
assert call_params['contactNom'] == 'John Doe' |
|
117 |
assert call_params['contactTelephone'] == '0101010101' |
|
118 |
assert call_params['contactCourriel'] == 'john@doe.com' |
|
119 |
assert call_params['contactAdresse'] == '1 doe street' |
|
120 |
assert call_params['demandeObjet'] == 'sarah connor' |
|
121 |
assert call_params['demandeLieu'] == 'LA' |
|
122 |
assert call_params['demandeDescription'] == 'poker face' |
|
123 |
assert call_params['remoteAddress'] == 'hollywood bd' |
|
124 |
assert call_params['codeEquipement'] == 'MAC10' |
|
125 |
assert call_params['codeServiceDemandeur'] == 'skynet' |
|
126 |
assert call_params['dateSouhaitee'] == 'now' |
|
127 |
assert call_params['typeDemande'] == 'scary' |
|
128 | ||
129 | ||
130 |
def test_retrieve_details_demande(app, connector, monkeypatch): |
|
131 |
mock_soap_call = mock_atal_soap_call( |
|
132 |
monkeypatch, return_value=dict(code='code1', libelle='elem1')) |
|
133 |
response = app.get('/atal/slug-atal/retrieve_details_demande/DIT19050001/') |
|
134 |
assert response.json == { |
|
135 |
'err': 0, |
|
136 |
'data': {'code': 'code1', 'libelle': 'elem1'} |
|
137 |
} |
|
138 |
call_params = mock_soap_call.call_args.kwargs |
|
139 |
assert call_params['wsdl'] == 'DemandeService' |
|
140 |
assert call_params['method'] == 'retrieveDetailsDemande' |
|
141 |
assert call_params['demandeNumberParam'] == 'DIT19050001' |
|
0 |
- |