Projet

Général

Profil

0001-cityweb-add-cityweb-connector-15883.patch

Josué Kouka, 05 septembre 2017 19:05

Télécharger (40 ko)

Voir les différences:

Subject: [PATCH] cityweb: add cityweb connector (#15883)

 debian/control                                     |   1 +
 passerelle/apps/cityweb/__init__.py                |   0
 passerelle/apps/cityweb/cityweb.py                 | 385 +++++++++++++++++++++
 passerelle/apps/cityweb/migrations/0001_initial.py |  28 ++
 passerelle/apps/cityweb/migrations/__init__.py     |   0
 passerelle/apps/cityweb/models.py                  |  84 +++++
 passerelle/settings.py                             |   1 +
 setup.py                                           |   1 +
 tests/data/cityweb/cityweb.xsd                     | 181 ++++++++++
 tests/data/cityweb/payload_birth.json              |  50 +++
 tests/data/cityweb/payload_death.json              |  37 ++
 tests/data/cityweb/payload_mariage.json            |  68 ++++
 tests/test_cityweb.py                              | 189 ++++++++++
 tox.ini                                            |   1 +
 14 files changed, 1026 insertions(+)
 create mode 100644 passerelle/apps/cityweb/__init__.py
 create mode 100644 passerelle/apps/cityweb/cityweb.py
 create mode 100644 passerelle/apps/cityweb/migrations/0001_initial.py
 create mode 100644 passerelle/apps/cityweb/migrations/__init__.py
 create mode 100644 passerelle/apps/cityweb/models.py
 create mode 100644 tests/data/cityweb/cityweb.xsd
 create mode 100644 tests/data/cityweb/payload_birth.json
 create mode 100644 tests/data/cityweb/payload_death.json
 create mode 100644 tests/data/cityweb/payload_mariage.json
 create mode 100644 tests/test_cityweb.py
debian/control
24 24
    python-magic,
25 25
    python-suds,
26 26
    python-cmislib (>= 0.5), python-cmislib (< 0.6),
27
    python-lxml
27 28
Recommends: python-soappy, python-phpserialize
28 29
Suggests: python-sqlalchemy, python-mako
29 30
Description: Uniform access to multiple data sources and services (Python module)
passerelle/apps/cityweb/cityweb.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import os
18
import zipfile
19
from dateutil.parser import parse as dateutil_parse
20
from lxml import etree, objectify as xobject
21

  
22
from django.core.files.storage import default_storage
23
from django.core.files.base import ContentFile
24

  
25
from passerelle.utils.jsonresponse import APIError
26

  
27

  
28
CERTIFICATE_TYPES = [
29
    {"id": "NAI", "text": "Naissance"},
30
    {"id": "MAR", "text": "Mariage"},
31
    {"id": "REC", "text": "Reconnaissance"},
32
    {"id": "DEC", "text": "Décès"}
33
]
34

  
35
SEXES = [
36
    {"id": "M", "text": "Homme"},
37
    {"id": "F", "text": "Femme"},
38
    {"id": "NA", "text": "Autre"}
39
]
40

  
41
TITLES = [
42
    {"id": "M", "text": "Monsieur"},
43
    {"id": "Mme", "text": "Madame"},
44
    {"id": "Mlle", "text": "Mademoiselle"}
45
]
46

  
47
DOCUMENT_TYPES = [
48
    {"id": "CPI", "text": "Copie intégrale"},
49
    {"id": "EXTAF", "text": "Extrait avec filiation"},
50
    {"id": "EXTSF", "text": "Extrait sans filiation"},
51
    {"id": "EXTPL", "text": "Extrait plurilingue"}
52
]
53

  
54
CONCERNED = [
55
    {"id": "reconnu", "text": "Reconnu"},
56
    {"id": "auteur", "text": "Auteur"}
57
]
58

  
59
ORIGINS = [
60
    {"id": "internet", "text": "Internet"},
61
    {"id": "guichet", "text": "Guichet"},
62
    {"id": "courrier", "text": "Courrier"}
63
]
64

  
65

  
66
def is_clean(element):
67
    if not element.getchildren() and element.text is None:
68
        return False
69
    return all(is_clean(child) for child in element.iterchildren())
70

  
71

  
72
class BaseType(object):
73
    """Base data binding object
74
    """
75
    tagname = None
76

  
77
    def __repr__(self):
78
        return '<%s>' % self.tagname
79

  
80
    @classmethod
81
    def make_element(cls, tagname, value=None, namespace=None, nsmap=None):
82
        M = xobject.ElementMaker(annotate=False, namespace=namespace,
83
                                 nsmap=nsmap)
84
        return M(tagname, value)
85

  
86
    @property
87
    def xml(self):
88
        if isinstance(self, (SimpleType, DateType)):
89
            return self.make_element(self.tagname, self.value, namespace=self.namespace)
90
        tag = self.make_element(self.tagname, namespace=self.namespace, nsmap=self.nsmap)
91
        for subelt in self.sequence:
92
            attr = getattr(self, subelt, None)
93
            if not attr:
94
                continue
95
            if isinstance(attr, (str, unicode)):
96
                tag.append(self.make_element(subelt, attr, namespace=self.namespace))
97
            else:
98
                xml = attr.xml
99
                if not is_clean(xml):
100
                    continue
101
                tag.append(xml)
102
        xobject.deannotate(tag, xsi_nil=True)
103
        return tag
104

  
105
    def __str__(self):
106
        return etree.tostring(self.xml, pretty_print=True)
107

  
108

  
109
class CityWebType(BaseType):
110
    namespace = 'http://tempuri.org/XMLSchema.xsd'
111
    nsmap = {'xs': 'http://tempuri.org/XMLSchema.xsd'}
112

  
113

  
114
class SimpleType(CityWebType):
115
    """Data binding class for SimpleType
116
    """
117
    allowed_values = None
118

  
119
    def __init__(self, value):
120
        if value not in self.allowed_values:
121
            raise APIError('<%s> value (%s) not in %s' % (self.tagname, value,
122
                                                          self.allowed_values))
123
        self.value = value
124

  
125

  
126
class DateType(CityWebType):
127

  
128
    def __init__(self, value):
129
        try:
130
            self.value = dateutil_parse(value).date().isoformat()
131
        except (ValueError,) as exc:
132
            raise APIError(exc.message)
133

  
134

  
135
class ComplexType(CityWebType):
136
    """Data binding class for ComplexType
137
    """
138
    sequence = None
139
    pattern = None
140

  
141
    def __init__(self, data):
142
        if self.pattern:
143
            data = self.extract_by_pattern(data)
144
        self.data = data
145

  
146
    def extract_by_pattern(self, data):
147
        data = {key: value for key, value in data.iteritems() if self.pattern in key}
148
        data = {key.replace(self.pattern, ''): value for key, value in data.iteritems()}
149
        return data
150

  
151

  
152
class BirthDate(DateType):
153
    tagname = 'date'
154

  
155

  
156
class StartDate(DateType):
157
    tagname = 'dateDebut'
158

  
159

  
160
class EndDate(DateType):
161
    tagname = 'dateFin'
162

  
163

  
164
# SIMPLE TYPES
165

  
166

  
167
class Document(SimpleType):
168
    tagname = 'natureDocument'
169
    allowed_values = map(lambda x: x['id'], DOCUMENT_TYPES)
170

  
171

  
172
class Origin(SimpleType):
173
    tagname = 'origine'
174
    allowed_values = map(lambda x: x['id'], ORIGINS)
175

  
176

  
177
class Title(SimpleType):
178
    tagname = 'genre'
179
    allowed_values = map(lambda x: x['id'], TITLES)
180

  
181

  
182
class Sex(SimpleType):
183
    tagname = 'sexe'
184
    allowed_values = map(lambda x: x['id'], SEXES)
185

  
186

  
187
class Certificate(SimpleType):
188
    tagname = 'natureEvenement'
189
    allowed_values = map(lambda x: x['id'], CERTIFICATE_TYPES)
190

  
191

  
192
class ConcernedKind(SimpleType):
193
    tagname = 'typeInteresse'
194
    allowed_values = ('reconnu', 'auteur')
195

  
196

  
197
# COMPLEX TYPES
198

  
199

  
200
class Names(ComplexType):
201
    tagname = 'noms'
202
    sequence = ('nomDeFamille', 'nomUsgae', 'typeUsage')
203

  
204
    def __init__(self, data):
205
        super(Names, self).__init__(data)
206
        if self.data.get('lastname'):
207
            self.nomDeFamille = self.data['lastname']
208
            self.nomUsage = self.data.get('usual_name', '')
209
            self.typeUsage = self.data.get('name_usage', '')
210

  
211

  
212
class Place(ComplexType):
213
    tagname = 'lieu'
214
    sequence = ('ville', 'province', 'pays')
215

  
216
    def __init__(self, data):
217
        super(Place, self).__init__(data)
218
        if self.data.get('city'):
219
            self.ville = self.data['city']
220
            self.province = self.data.get('county', '')
221
            self.pays = self.data.get('country', '')
222

  
223

  
224
class Address(ComplexType):
225
    tagname = 'adresse'
226
    sequence = ('ligneAdr1', 'ligneAdr2', 'codePostal',
227
                'lieu', 'mail', 'tel')
228
    pattern = 'address_'
229

  
230
    def __init__(self, data):
231
        super(Address, self).__init__(data)
232
        self.ligneAdr1 = self.data.get('street', '')
233
        self.ligneAdr2 = self.data.get('complement', '')
234
        self.lieu = Place(self.data)
235
        self.mail = self.data.get('email', '')
236
        self.tel = self.data.get('phone', '')
237

  
238

  
239
class Birth(ComplexType):
240
    tagname = 'naissance'
241
    sequence = ('date', 'lieu')
242
    pattern = 'birth_'
243

  
244
    def __init__(self, data):
245
        super(Birth, self).__init__(data)
246
        birthdate = self.data.get('date', None)
247
        if birthdate:
248
            self.date = BirthDate(birthdate)
249
        self.lieu = Place(self.data)
250

  
251

  
252
class EventDate(ComplexType):
253
    tagname = 'dateEvenement'
254
    sequence = ('dateDebut', 'dateFin')
255

  
256
    def __init__(self, data):
257
        super(EventDate, self).__init__(data)
258
        self.dateDebut = StartDate(self.data['event_date_start'])
259
        if data.get('event_date_end', None):
260
            self.dateFin = EndDate(self.data['event_date_end'])
261

  
262

  
263
class EventPlace(Place):
264
    tagname = 'lieuEvenement'
265
    pattern = 'event_'
266

  
267

  
268
class Person(ComplexType):
269
    sequence = ('noms', 'prenoms', 'genre', 'adresse', 'sexe',
270
                'pere', 'mere', 'naissance')
271

  
272
    def __init__(self, data):
273
        super(Person, self).__init__(data)
274
        self.noms = Names(self.data)
275
        self.prenoms = self.data.get('firstnames', '')
276
        if self.data.get('title', None):
277
            self.genre = Title(self.data['title'])
278
        self.adresse = Address(self.data)
279
        if self.data.get('sex', None):
280
            self.sexe = Sex(self.data['sex'])
281
        self.naissance = Birth(self.data)
282

  
283

  
284
class ApplicantPerson(Person):
285
    tagname = 'individu'
286
    pattern = 'applicant_'
287
    sequence = ('noms', 'prenoms', 'genre', 'adresse')
288

  
289

  
290
class Parent(Person):
291
    sequence = ('noms', 'prenoms', 'genre')
292

  
293
    def __init__(self, data, pattern):
294
        self.pattern = pattern
295
        super(Parent, self).__init__(data)
296
        sex = self.data.get('sex')
297
        if sex:
298
            if sex == 'M':
299
                self.tagname = 'pere'
300
            else:
301
                self.tagname = 'mere'
302
        else:
303
            if pattern.startswith('parent1'):
304
                self.tagname = 'pere'
305
            else:
306
                self.tagname = 'mere'
307

  
308

  
309
class ConcernedCommon(Person):
310
    sequence = ('noms', 'prenoms', 'genre', 'sexe',
311
                'parent1', 'parent2', 'naissance')
312

  
313
    def __init__(self, data):
314
        super(ConcernedCommon, self).__init__(data)
315
        self.parent1 = Parent(self.data, 'parent1_')
316
        self.parent2 = Parent(self.data, 'parent2_')
317

  
318

  
319
class Concerned(ConcernedCommon):
320
    tagname = 'interesse'
321
    pattern = 'concerned_'
322

  
323

  
324
class Partner(ConcernedCommon):
325
    tagname = 'conjoint'
326
    pattern = 'partner_'
327

  
328

  
329
class Applicant(ComplexType):
330
    tagname = 'demandeur'
331
    sequence = ('qualiteDemandeur', 'individu')
332

  
333
    def __init__(self, data):
334
        self.qualiteDemandeur = data.get('applicant_status', '')
335
        self.individu = ApplicantPerson(data)
336

  
337

  
338
class Event(ComplexType):
339
    tagname = 'evenement'
340
    sequence = ('interesse', 'conjoint', 'natureEvenement',
341
                'typeInteresse', 'dateEvenement', 'lieuEvenement')
342

  
343
    def __init__(self, data):
344
        certificate_type = data['certificate_type']
345
        self.interesse = Concerned(data)
346
        if certificate_type == 'MAR':
347
            self.conjoint = Partner(data)
348
        self.natureEvenement = Certificate(data['certificate_type'])
349
        if data.get('concerned_kind', None):
350
            self.typeInteresse = ConcernedKind(data['concerned_kind'])
351
        self.dateEvenement = EventDate(data)
352
        self.lieuEvenement = EventPlace(data)
353

  
354

  
355
class CivilStatusApplication(ComplexType):
356
    tagname = 'demandeEtatCivil'
357
    sequence = (
358
        'identifiant', 'demandeur', 'natureDocument', 'nbExemplaire',
359
        'dateDemande', 'evenement', 'motif', 'origine', 'commentaire')
360

  
361
    def __init__(self, data):
362
        self.identifiant = data['application_id']
363
        self.demandeur = Applicant(data)
364
        self.natureDocument = Document(data['document_type'])
365
        self.nbExemplaire = data['document_copies']
366
        self.dateDemande = data['application_time']
367
        self.evenement = Event(data)
368
        self.motif = data.get('application_reason', '')
369
        if data.get('application_origin', None):
370
            self.origine = Origin(data['application_origin'])
371
        self.commentaire = data.get('application_comment', '')
372

  
373
    @property
374
    def demand_id(self):
375
        return '%s-%s' % (self.identifiant, self.evenement.natureEvenement.value)
376

  
377
    def save(self, path):
378
        basename = os.path.join(path, self.demand_id)
379
        archname = basename + '.zip'
380
        filepath = basename + '.xml'
381
        content = etree.tostring(self.xml, pretty_print=True)
382
        default_storage.save(filepath, ContentFile(content))
383
        with zipfile.ZipFile(archname, 'w') as zipf:
384
            zipf.write(filepath, os.path.basename(filepath))
385
        return archname
passerelle/apps/cityweb/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('base', '0005_resourcelog'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='CityWeb',
16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
                ('title', models.CharField(max_length=50)),
19
                ('slug', models.SlugField()),
20
                ('description', models.TextField()),
21
                ('log_level', models.CharField(default=b'INFO', max_length=10, verbose_name='Log Level', choices=[(b'NOTSET', b'NOTSET'), (b'DEBUG', b'DEBUG'), (b'INFO', b'INFO'), (b'WARNING', b'WARNING'), (b'ERROR', b'ERROR'), (b'CRITICAL', b'CRITICAL'), (b'FATAL', b'FATAL')])),
22
                ('users', models.ManyToManyField(to='base.ApiUser', blank=True)),
23
            ],
24
            options={
25
                'verbose_name': "CityWeb - Demande d'acte d'\xe9tat civil",
26
            },
27
        )
28
    ]
passerelle/apps/cityweb/models.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import os
18
import json
19

  
20
from django.utils.translation import ugettext_lazy as _
21
from django.core.files.storage import default_storage
22

  
23
from passerelle.base.models import BaseResource
24
from passerelle.utils.api import endpoint
25
from passerelle.utils.jsonresponse import APIError
26

  
27
from .cityweb import (CivilStatusApplication, TITLES, SEXES, DOCUMENT_TYPES,
28
                      CERTIFICATE_TYPES, CONCERNED, ORIGINS)
29

  
30

  
31
class CityWeb(BaseResource):
32
    category = _('Business Process Connectors')
33

  
34
    class Meta:
35
        verbose_name = "CityWeb - Demande d'acte d'état civil"
36

  
37
    @classmethod
38
    def get_verbose_name(cls):
39
        return cls._meta.verbose_name
40

  
41
    @endpoint(perm='can_access', methods=['post'], description=_('Create a demand'))
42
    def create(self, request, *args, **kwargs):
43
        payload = json.loads(request.body)
44
        # check mandatory keys
45
        for key in ('application_id', 'application_time', 'certificate_type'):
46
            if key not in payload:
47
                raise APIError('<%s> is required' % key)
48

  
49
        application = CivilStatusApplication(payload)
50
        application.save(self.basepath)
51
        return {'data': {'demand_id': application.demand_id}}
52

  
53
    @property
54
    def basepath(self):
55
        return os.path.join(
56
            default_storage.path('cityweb'), self.slug)
57

  
58
    @endpoint(perm='can_access', description=_('Get title list'))
59
    def titles(self, request):
60
        return {'data': TITLES}
61

  
62
    @endpoint(perm='can_access', description=_('Get sex list'))
63
    def sexes(self, request):
64
        return {'data': SEXES}
65

  
66
    @endpoint(perm='can_access', description=_('Get concerned status list'))
67
    def concerned(self, request):
68
        return {'data': CONCERNED}
69

  
70
    @endpoint(perm='can_access', description=_('Get application origin list'))
71
    def origins(self, request):
72
        return {'data': ORIGINS}
73

  
74
    @endpoint(name='certificate-types', perm='can_access',
75
              description=_('Get certificate type list'), parameters={'exclude': {'example_value': 'REC'}})
76
    def certificate_types(self, request, exclude=''):
77
        return {'data': [item for item in CERTIFICATE_TYPES
78
                if item.get('id') not in exclude.split(',')]}
79

  
80
    @endpoint(name='document-types', perm='can_access',
81
              description=_('Get document type list'), parameters={'exclude': {'example_value': 'EXTPL'}})
82
    def document_types(self, request, exclude=''):
83
        return {'data': [item for item in DOCUMENT_TYPES
84
                if item.get('id') not in exclude.split(',')]}
passerelle/settings.py
116 116
    'passerelle.apps.airquality',
117 117
    'passerelle.apps.okina',
118 118
    'passerelle.apps.cmis',
119
    'passerelle.apps.cityweb',
119 120
    # backoffice templates and static
120 121
    'gadjo',
121 122
)
setup.py
98 98
            'pyexcel-xls >= 0.4, <0.5',
99 99
            'cmislib >= 0.5, <0.6',
100 100
            'feedparser',
101
            'lxml'
101 102
        ],
102 103
        cmdclass={
103 104
            'build': build,
tests/data/cityweb/cityweb.xsd
1
<?xml version="1.0" encoding="utf-8"?>
2
<!-- edited with XMLSpy v2005 U (http://www.xmlspy.com) by judlin (digitech) -->
3
<xs:schema xmlns="http://tempuri.org/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://tempuri.org/XMLSchema.xsd" elementFormDefault="qualified">
4
	<xs:element name="demandeEtatCivil">
5
		<xs:annotation>
6
			<xs:documentation>Root element</xs:documentation>
7
		</xs:annotation>
8
		<xs:complexType>
9
			<xs:sequence>
10
				<xs:element name="identifiant" type="xs:string" />
11
				<xs:element name="demandeur" type="demandeurType" minOccurs="0">
12
					<xs:annotation>
13
						<xs:documentation>utile si différent de l'interessé</xs:documentation>
14
					</xs:annotation>
15
				</xs:element>
16
				<xs:element name="natureDocument" type="natureDocumentType">
17
					<xs:annotation>
18
						<xs:documentation>copie intégrale, extrait avec ou sans	filiation, plurilingue</xs:documentation>
19
					</xs:annotation>
20
				</xs:element>
21
				<xs:element name="nbExemplaire" type="xs:int" minOccurs="0" />
22
				<xs:element name="dateDemande" type="xs:dateTime" />
23
				<xs:element name="evenement" type="evenementType" />
24
				<xs:element name="motif" type="xs:string" minOccurs="0" />
25
				<xs:element name="origine" type="origineECType" minOccurs="0">
26
					<xs:annotation>
27
						<xs:documentation>origine de la demande pour l'état civil : courrier, guichet ou internet</xs:documentation>
28
					</xs:annotation>
29
				</xs:element>
30
				<xs:element name="commentaire" type="xs:string" minOccurs="0" >
31
					<xs:annotation>
32
						<xs:documentation>champ commentaire</xs:documentation>
33
					</xs:annotation>
34
				</xs:element>
35
			</xs:sequence>
36
			<xs:attribute name="version" type="xs:string" default="1.0"/>
37
		</xs:complexType>
38
	</xs:element>
39
	<xs:simpleType name="natureEvenementType">
40
		<xs:annotation>
41
			<xs:documentation>Naissance, mariage, décès, reconnaissance</xs:documentation>
42
		</xs:annotation>
43
		<xs:restriction base="xs:string">
44
			<xs:enumeration value="NAI"/>
45
			<xs:enumeration value="DEC"/>
46
			<xs:enumeration value="MAR"/>
47
			<xs:enumeration value="REC"/>
48
		</xs:restriction>
49
	</xs:simpleType>
50
	<xs:complexType name="individuType">
51
		<xs:sequence>
52
			<xs:element name="noms" type="nomsType"/>
53
			<xs:element name="prenoms" type="xs:string" minOccurs="0"/>
54
			<xs:element name="genre" type="genreType" minOccurs="0"/>
55
			<xs:element name="adresse" type="adresseType" minOccurs="0"/>
56
			<xs:element name="sexe" type="sexeType" minOccurs="0"/>
57
			<xs:element name="pere" type="individuType" minOccurs="0"/>
58
			<xs:element name="mere" type="individuType" minOccurs="0"/>
59
			<xs:element name="naissance" type="naissanceType" minOccurs="0"/>
60
		</xs:sequence>
61
	</xs:complexType>
62
	<xs:complexType name="demandeurType">
63
		<xs:annotation>
64
			<xs:documentation>Informations sur le demandeur</xs:documentation>
65
		</xs:annotation>
66
		<xs:sequence>
67
			<xs:element name="qualiteDemandeur" type="xs:string">
68
				<xs:annotation>
69
					<xs:documentation>avocat, notaire, père, mère...</xs:documentation>
70
				</xs:annotation>
71
			</xs:element>
72
			<xs:element name="individu" type="individuType"/>
73
		</xs:sequence>
74
	</xs:complexType>
75
	<xs:simpleType name="sexeType">
76
		<xs:annotation>
77
			<xs:documentation>permet de gérer le sexe indeterminé</xs:documentation>
78
		</xs:annotation>
79
		<xs:restriction base="xs:string">
80
			<xs:enumeration value="F"/>
81
			<xs:enumeration value="M"/>
82
			<xs:enumeration value="NA"/>
83
		</xs:restriction>
84
	</xs:simpleType>
85
	<xs:simpleType name="genreType">
86
		<xs:restriction base="xs:string">
87
			<xs:enumeration value="M"/>
88
			<xs:enumeration value="Mme"/>
89
			<xs:enumeration value="Mlle"/>
90
		</xs:restriction>
91
	</xs:simpleType>
92
	<xs:complexType name="adresseType">
93
		<xs:sequence>
94
			<xs:element name="ligneAdr1" type="xs:string" minOccurs="0"/>
95
			<xs:element name="ligneAdr2" type="xs:string" minOccurs="0"/>
96
			<xs:element name="codePostal" type="xs:string" minOccurs="0"/>
97
			<xs:element name="lieu" type="lieuType"/>
98
			<xs:element name="mail" type="xs:string" minOccurs="0"/>
99
			<xs:element name="tel" type="xs:string" minOccurs="0"/>
100
		</xs:sequence>
101
	</xs:complexType>
102
	<xs:simpleType name="typeInteresseType">
103
		<xs:restriction base="xs:string">
104
			<xs:enumeration value="reconnu"/>
105
			<xs:enumeration value="auteur"/>
106
		</xs:restriction>
107
	</xs:simpleType>
108
	<xs:simpleType name="natureDocumentType">
109
		<xs:restriction base="xs:string">
110
			<xs:enumeration value="CPI"/>
111
			<xs:enumeration value="EXTAF"/>
112
			<xs:enumeration value="EXTSF"/>
113
			<xs:enumeration value="EXTPL"/>
114
		</xs:restriction>
115
	</xs:simpleType>
116
	<xs:simpleType name="origineECType">
117
		<xs:restriction base="xs:string">
118
			<xs:enumeration value="internet"/>
119
			<xs:enumeration value="guichet"/>
120
			<xs:enumeration value="courrier"/>
121
		</xs:restriction>
122
	</xs:simpleType>
123
	<xs:complexType name="evenementType">
124
		<xs:sequence>
125
			<xs:element name="interesse" type="individuType"/>
126
			<xs:element name="conjoint" type="individuType" minOccurs="0">
127
				<xs:annotation>
128
					<xs:documentation>Seulement pour les mariages</xs:documentation>
129
				</xs:annotation>
130
			</xs:element>
131
			<xs:element name="natureEvenement" type="natureEvenementType">
132
				<xs:annotation>
133
					<xs:documentation>naissance, mariage, décès, reconnaissance</xs:documentation>
134
				</xs:annotation>
135
			</xs:element>
136
			<xs:element name="typeInteresse" type="typeInteresseType" minOccurs="0">
137
				<xs:annotation>
138
					<xs:documentation>necessaire pour les reconnaissances seulement, l'interessé pouvant etre le parent ou le reconnu, il faut donc préciser.</xs:documentation>
139
				</xs:annotation>
140
			</xs:element>
141
			<xs:element name="dateEvenement" type="dateEvenementType"/>
142
			<xs:element name="lieuEvenement" type="lieuType"/>
143
		</xs:sequence>
144
	</xs:complexType>
145
	<xs:complexType name="nomsType">
146
		<xs:sequence>
147
			<xs:element name="nomDeFamille" type="xs:string"/>
148
			<xs:element name="nomUsage" type="xs:string" minOccurs="0"/>
149
			<xs:element name="typeUsage" type="xs:string" minOccurs="0">
150
				<xs:annotation>
151
					<xs:documentation>précission sur le nom d'usage: nom d'épouse, veuve, d'usage...</xs:documentation>
152
				</xs:annotation>
153
			</xs:element>
154
		</xs:sequence>
155
	</xs:complexType>
156
	<xs:complexType name="naissanceType">
157
		<xs:sequence>
158
			<xs:element name="date" type="dateCiviqueType"/>
159
			<xs:element name="lieu" type="lieuType" minOccurs="0"/>
160
		</xs:sequence>
161
	</xs:complexType>
162
	<xs:complexType name="lieuType">
163
		<xs:sequence>
164
			<xs:element name="ville" type="xs:string"/>
165
			<xs:element name="province" type="xs:string" minOccurs="0"/>
166
			<xs:element name="pays" type="xs:string" minOccurs="0"/>
167
		</xs:sequence>
168
	</xs:complexType>
169
	<xs:complexType name="dateEvenementType">
170
		<xs:sequence>
171
			<xs:element name="dateDebut" type="xs:date"/>
172
			<xs:element name="dateFin" type="xs:date" minOccurs="0"/>
173
		</xs:sequence>
174
	</xs:complexType>
175
	<xs:simpleType name="dateCiviqueType">
176
		<xs:annotation>
177
			<xs:documentation>Permet de gérer les dates incomplètes</xs:documentation>
178
		</xs:annotation>
179
		<xs:union memberTypes="xs:date xs:gYear xs:gYearMonth"/>
180
	</xs:simpleType>
181
</xs:schema>
tests/data/cityweb/payload_birth.json
1
{
2
    "applicant_address_city": "Nancy",
3
    "applicant_address_complement": "Bat A",
4
    "applicant_address_country": "France",
5
    "applicant_address_county": "Meurthe-et-Moselle",
6
    "applicant_address_email": "chelsea@whatever.com",
7
    "applicant_address_phone": "+33 6 55 44 22 11",
8
    "applicant_address_street": "37 Rue du Cheval Blanc",
9
    "applicant_address_zipcode": "54000",
10
    "applicant_firstnames": "Kim Chelsea",
11
    "applicant_lastname": "Whatever",
12
    "applicant_name_usage": "nom d'epouse",
13
    "applicant_status": "concerne",
14
    "applicant_title": "Mme",
15
    "applicant_title_label": "Madame",
16
    "applicant_usual_name": "Whatever",
17
    "application_id": "15-4",
18
    "application_origin": "internet",
19
    "application_reason": "They are just messy",
20
    "application_time": "2016-10-20T14:41:20Z",
21
    "certificate_type": "NAI",
22
    "certificate_type_label": "Acte de naissance",
23
    "concerned_birth_city": "Harare",
24
    "concerned_birth_country": "Zimbabwe",
25
    "concerned_birth_county": "",
26
    "concerned_birth_date": "1980-02-29",
27
    "concerned_firstnames": "Kevin",
28
    "concerned_lastname": "Whatever",
29
    "concerned_name_usage": "",
30
    "concerned_parent1_firstnames": "John Oliver",
31
    "concerned_parent1_lastname": "Smith",
32
    "concerned_parent1_name_usage": "Smith",
33
    "concerned_parent1_title": "M",
34
    "concerned_parent1_title_label": "Monsieur",
35
    "concerned_parent1_usual_name": "Smith",
36
    "concerned_parent2_firstnames": "Kim",
37
    "concerned_parent2_lastname": "Smith",
38
    "concerned_parent2_name_usage": "nom d'\u00e9pouse",
39
    "concerned_parent2_title": "Mme",
40
    "concerned_parent2_usual_name": "Smith",
41
    "concerned_sex": "M",
42
    "concerned_title": "M",
43
    "concerned_usual_name": "Whatever",
44
    "document_copies": "1",
45
    "document_type": "CPI",
46
    "document_type_label": "Copie Integrale",
47
    "event_city": "Nancy",
48
    "event_date_end": "",
49
    "event_date_start": "2012-07-14"
50
}
tests/data/cityweb/payload_death.json
1
{
2
    "applicant_address_city": "Nancy",
3
    "applicant_address_complement": "Bat A",
4
    "applicant_address_country": "France",
5
    "applicant_address_county": "Meurthe-et-Moselle",
6
    "applicant_address_email": "chelsea@whatever.com",
7
    "applicant_address_phone": "+33 6 55 44 22 11",
8
    "applicant_address_street": "37 Rue du Cheval Blanc",
9
    "applicant_address_zipcode": "54000",
10
    "applicant_firstnames": "Kim Chelsea",
11
    "applicant_lastname": "Whatever",
12
    "applicant_name_usage": "nom d'epouse",
13
    "applicant_status": "concerne",
14
    "applicant_title": "Mme",
15
    "applicant_title_label": "Madame",
16
    "applicant_usual_name": "Whatever",
17
    "application_origin": "internet",
18
    "application_id": "17-1",
19
    "application_reason": "",
20
    "application_time": "2016-10-20T14:41:20Z",
21
    "certificate_type": "DEC",
22
    "certificate_type_label": "Acte de d\u00e9c\u00e8s",
23
    "concerned_birth_city": "Harare",
24
    "concerned_birth_country": "Zimbabwe",
25
    "concerned_birth_county": "",
26
    "concerned_birth_date": "1980-02-29",
27
    "concerned_firstnames": "Kevin",
28
    "concerned_lastname": "Whatever",
29
    "concerned_sex": "M",
30
    "concerned_title": "M",
31
    "concerned_usual_name": "Whatever",
32
    "document_copies": "1",
33
    "document_type": "EXTSF",
34
    "document_type_label": "Extrait sans filiation",
35
    "event_city": "Nancy",
36
    "event_date_start": "2012-07-14"
37
}
tests/data/cityweb/payload_mariage.json
1
{
2
    "applicant_address_city": "Nancy",
3
    "applicant_address_complement": "Bat A",
4
    "applicant_address_country": "France",
5
    "applicant_address_county": "Meurthe-et-Moselle",
6
    "applicant_address_email": "chelsea@whatever.com",
7
    "applicant_address_phone": "+33 6 55 44 22 11",
8
    "applicant_address_street": "37 Rue du Cheval Blanc",
9
    "applicant_address_zipcode": "54000",
10
    "applicant_firstnames": "Kim Chelsea",
11
    "applicant_lastname": "Whatever",
12
    "applicant_name_usage": "nom d'epouse",
13
    "applicant_status": "concerne",
14
    "applicant_title": "Mme",
15
    "applicant_title_label": "Madame",
16
    "applicant_usual_name": "Whatever",
17
    "application_id": "16-1",
18
    "application_origin": "internet",
19
    "application_reason": "Happy mariage",
20
    "application_time": "2016-10-20T14:41:20Z",
21
    "certificate_type": "MAR",
22
    "certificate_type_label": "Acte de naissance",
23
    "concerned_birth_city": "Harare",
24
    "concerned_birth_country": "Zimbabwe",
25
    "concerned_birth_county": "",
26
    "concerned_birth_date": "1980-02-29",
27
    "concerned_parent1_firstnames": "John Oliver",
28
    "concerned_parent1_lastname": "Smith",
29
    "concerned_parent1_name_usage": "Smith",
30
    "concerned_parent1_title": "M",
31
    "concerned_parent1_title_label": "Monsieur",
32
    "concerned_parent1_usual_name": "Smith",
33
    "concerned_firstnames": "Kevin",
34
    "concerned_lastname": "Whatever",
35
    "concerned_parent2_firstnames": "Kim",
36
    "concerned_parent2_lastname": "Smith",
37
    "concerned_parent2_name_usage": "nom d'\u00e9pouse",
38
    "concerned_parent2_title": "Mme",
39
    "concerned_parent2_usual_name": "Smith",
40
    "concerned_name_usage": "",
41
    "concerned_sex": "M",
42
    "concerned_title": "M",
43
    "document_copies": "1",
44
    "document_type": "CPI",
45
    "document_type_label": "Copie Integrale",
46
    "event_city": "Nancy",
47
    "event_date_end": "",
48
    "event_date_start": "2012-07-14",
49
    "partner_birth_city": "Harare",
50
    "partner_birth_country": "Zimbabwe",
51
    "partner_birth_county": "",
52
    "partner_birth_date": "1984-02-29",
53
    "partner_parent1_firstnames": "Antonio",
54
    "partner_parent1_lastname": "Scaramucci",
55
    "partner_parent1_title": "M",
56
    "partner_parent1_title_label": "Monsieur",
57
    "partner_firstnames": "Chelsea",
58
    "partner_lastname": "Contrao",
59
    "partner_parent2_firstnames": "Marguerite",
60
    "partner_parent2_lastname": "Scaramucci",
61
    "partner_parent2_name_usage": "nom d'\u00e9pouse",
62
    "partner_parent2_title": "Mme",
63
    "partner_parent2_usual_name": "Gaye",
64
    "partner_name_usage": "",
65
    "partner_sex": "F",
66
    "partner_title": "Mme",
67
    "partner_usual_name": "Scaramucci"
68
}
tests/test_cityweb.py
1
# -*- coding: utf-8 -*-
2
# Passerelle - uniform access to data and services
3
# Copyright (C) 2017  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; exclude 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.deepcopy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
from __future__ import unicode_literals
18

  
19
import os
20
import json
21
import shutil
22

  
23
import pytest
24
import mock
25
from lxml import etree, objectify as xobject
26

  
27
import utils
28

  
29
from passerelle.apps.cityweb.models import CityWeb
30

  
31

  
32
def get_test_base_dir(name):
33
    return os.path.join(os.path.dirname(__file__), 'data', name)
34

  
35

  
36
def get_file_from_test_base_dir(filename):
37
    path = os.path.join(get_test_base_dir('cityweb'), filename)
38
    with open(path, 'rb') as fd:
39
        return fd.read()
40

  
41

  
42
@pytest.fixture
43
def setup(db):
44
    return utils.setup_access_rights(CityWeb.objects.create(slug='test'))
45

  
46

  
47
PAYLOAD = [
48
    {
49
        'birth': json.loads(get_file_from_test_base_dir('payload_birth.json'))
50
    },
51
    {
52
        'mariage': json.loads(get_file_from_test_base_dir('payload_mariage.json'))
53
    },
54
    {
55
        'death': json.loads(get_file_from_test_base_dir('payload_death.json'))
56
    }
57
]
58

  
59

  
60
@pytest.fixture(params=PAYLOAD)
61
def payload(request):
62
    return request.param
63

  
64

  
65
def assert_xml_doc(doc):
66
    schema = etree.XMLSchema(
67
        etree.parse(os.path.join(get_test_base_dir('cityweb'), 'cityweb.xsd')))
68
    schema.assertValid(doc)
69

  
70

  
71
@mock.patch('passerelle.apps.cityweb.models.default_storage.path', get_test_base_dir)
72
def test_demand_creation(app, setup, payload):
73
    url = '/cityweb/test/create/'
74
    if 'birth' in payload:
75
        response = app.post_json(url, params=payload['birth'])
76
        assert response.json['data']['demand_id'] == '15-4-NAI'
77
        doc = xobject.parse(
78
            os.path.join(get_test_base_dir('cityweb'), 'test', '15-4-NAI.xml'))
79
        assert_xml_doc(doc)
80

  
81
    elif 'mariage' in payload:
82
        response = app.post_json(url, params=payload['mariage'])
83
        assert response.json['data']['demand_id'] == '16-1-MAR'
84
        doc = etree.parse(
85
            os.path.join(get_test_base_dir('cityweb'), 'test', '16-1-MAR.xml'))
86
        assert_xml_doc(doc)
87
    else:
88
        response = app.post_json(url, params=payload['death'])
89
        assert response.json['data']['demand_id'] == '17-1-DEC'
90
        doc = etree.parse(
91
            os.path.join(get_test_base_dir('cityweb'), 'test', '17-1-DEC.xml'))
92
        assert_xml_doc(doc)
93

  
94

  
95
def test_datasource_titles(app, setup):
96
    response = app.get('/cityweb/test/titles/')
97
    data = response.json['data']
98
    assert len(data) == 3
99
    for datum in data:
100
        if datum['id'] == 'M':
101
            assert datum['text'] == 'Monsieur'
102
        elif datum['id'] == 'Mme':
103
            assert datum['text'] == 'Madame'
104
        else:
105
            assert datum['id'] == 'Mlle'
106
            assert datum['text'] == 'Mademoiselle'
107

  
108

  
109
def test_datasource_sexes(app, setup):
110
    response = app.get('/cityweb/test/sexes/')
111
    data = response.json['data']
112
    assert len(data) == 3
113
    for datum in data:
114
        if datum['id'] == 'M':
115
            assert datum['text']
116
        elif datum['id'] == 'F':
117
            assert datum['text'] == 'Femme'
118
        else:
119
            assert datum['id'] == 'NA'
120
            assert datum['text'] == 'Autre'
121

  
122

  
123
def test_datasource_concerned(app, setup):
124
    response = app.get('/cityweb/test/concerned/')
125
    data = response.json['data']
126
    assert len(data) == 2
127
    for datum in data:
128
        if datum['id'] == 'reconnu':
129
            assert datum['text'] == 'Reconnu'
130
        else:
131
            assert datum['id'] == 'auteur'
132
            assert datum['text'] == 'Auteur'
133

  
134

  
135
def test_datasource_origins(app, setup):
136
    response = app.get('/cityweb/test/origins/')
137
    data = response.json['data']
138
    assert len(data) == 3
139
    for datum in data:
140
        if datum['id'] == 'internet':
141
            assert datum['text'] == 'Internet'
142
        elif datum['id'] == 'guichet':
143
            assert datum['text'] == 'Guichet'
144
        else:
145
            assert datum['id'] == 'courrier'
146
            assert datum['text'] == 'Courrier'
147

  
148

  
149
def test_datasource_document_types(app, setup):
150
    response = app.get('/cityweb/test/document-types/')
151
    data = response.json['data']
152
    assert len(data) == 4
153
    for datum in data:
154
        if datum['id'] == 'CPI':
155
            assert datum['text'] == 'Copie intégrale'
156
        elif datum['id'] == 'EXTAF':
157
            assert datum['text'] == 'Extrait avec filiation'
158
        elif datum['id'] == 'EXTSF':
159
            assert datum['text'] == 'Extrait sans filiation'
160
        else:
161
            datum['id'] == 'EXTPL'
162
            datum['text'] == 'Extrait plurilingue'
163

  
164
    params = {'exclude': 'EXTAF,EXTSF,EXTPL'}
165
    response = app.get('/cityweb/test/document-types/', params=params)
166
    data = response.json['data']
167
    assert len(data) == 1
168
    assert data[0]['id'] == 'CPI'
169
    assert data[0]['text'] == 'Copie intégrale'
170

  
171

  
172
def test_datasource_certificate_types(app, setup):
173
    response = app.get('/cityweb/test/certificate-types/')
174
    data = response.json['data']
175
    assert len(data) == 4
176
    for datum in data:
177
        if datum['id'] == 'NAI':
178
            assert datum['text'] == 'Naissance'
179
        elif datum['id'] == 'MAR':
180
            assert datum['text'] == 'Mariage'
181
        elif datum['id'] == 'REC':
182
            assert datum['text'] == 'Reconnaissance'
183
        else:
184
            assert datum['id'] == 'DEC'
185
            assert datum['text'] == 'Décès'
186

  
187

  
188
def teardown_module(module):
189
    shutil.rmtree(os.path.join(get_test_base_dir('cityweb'), 'test'), ignore_errors=True)
tox.ini
23 23
  pylint
24 24
  pylint-django
25 25
  django-webtest
26
  lxml
26 27
commands =
27 28
  ./getmagic.sh
28 29
  py.test {env:FAST:} {env:COVERAGE:} {posargs:tests/}
29
-