Projet

Général

Profil

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

Josué Kouka, 17 août 2017 14:32

Télécharger (40,7 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                 | 344 +++++++++++++++++++++
 passerelle/apps/cityweb/migrations/0001_initial.py |  39 +++
 passerelle/apps/cityweb/migrations/__init__.py     |   0
 passerelle/apps/cityweb/models.py                  | 146 +++++++++
 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 +++++++++++
 13 files changed, 1057 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,
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
# Copyright (C) 2017  Entr'ouvert
2
#
3
# This program is free software: you can redistribute it and/or modify it
4
# under the terms of the GNU Affero General Public License as published
5
# by the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU Affero General Public License for more details.
12
#
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

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

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

  
24
from passerelle.utils.jsonresponse import APIError
25

  
26

  
27
def is_clean(element):
28
    if not element.getchildren() and element.text is None:
29
        return False
30
    return all(is_clean(child) for child in element.iterchildren())
31

  
32

  
33
def zipdir(path):
34
    """Zip directory
35
    """
36
    archname = path + '.zip'
37
    with zipfile.ZipFile(archname, 'w', zipfile.ZIP_DEFLATED) as zipf:
38
        for root, dirs, files in os.walk(path):
39
            for f in files:
40
                fpath = os.path.join(root, f)
41
                zipf.write(fpath, os.path.basename(fpath))
42
    return archname
43

  
44

  
45
class BaseType(object):
46
    """Base data binding object
47
    """
48
    tagname = None
49

  
50
    def __repr__(self):
51
        return '<%s>' % self.tagname
52

  
53
    @classmethod
54
    def make_element(cls, tagname, value=None, namespace=None, nsmap=None):
55
        M = xobject.ElementMaker(annotate=False, namespace=namespace,
56
                                 nsmap=nsmap)
57
        return M(tagname, value)
58

  
59
    @property
60
    def xml(self):
61
        if isinstance(self, (SimpleType, DateType)):
62
            return self.make_element(self.tagname, self.value, namespace=self.namespace)
63
        tag = self.make_element(self.tagname, namespace=self.namespace, nsmap=self.nsmap)
64
        for subelt in self.sequence:
65
            attr = getattr(self, subelt, None)
66
            if not attr:
67
                continue
68
            if isinstance(attr, (str, unicode)) and attr:
69
                tag.append(self.make_element(subelt, attr, namespace=self.namespace))
70
            else:
71
                xml = attr.xml
72
                if not is_clean(xml):
73
                    continue
74
                tag.append(xml)
75
        xobject.deannotate(tag, xsi_nil=True)
76
        return tag
77

  
78
    def pretty(self):
79
        print(etree.tostring(self.xml, pretty_print=True))
80

  
81

  
82
class CityWebType(BaseType):
83
    namespace = 'http://tempuri.org/XMLSchema.xsd'
84
    nsmap = {'xs': 'http://tempuri.org/XMLSchema.xsd'}
85

  
86

  
87
class SimpleType(CityWebType):
88
    """Data binding class for SimpleType
89
    """
90
    allowed_values = None
91

  
92
    def __init__(self, value):
93
        if value not in self.allowed_values:
94
            raise APIError('<%s> value (%s) not in %s' % (self.tagname, value,
95
                                                          self.allowed_values))
96
        self.value = value
97

  
98

  
99
class DateType(CityWebType):
100

  
101
    def __init__(self, value):
102
        try:
103
            self.value = dateutil_parse(value).date().isoformat()
104
        except (ValueError,) as exc:
105
            raise APIError(exc.message)
106

  
107

  
108
class ComplexType(CityWebType):
109
    """Data binding class for ComplexType
110
    """
111
    sequence = None
112
    pattern = None
113

  
114
    def __init__(self, data):
115
        if self.pattern:
116
            data = self.extract_by_pattern(data)
117
        self.data = data
118

  
119
    def extract_by_pattern(self, data):
120
        data = {key: value for key, value in data.iteritems() if self.pattern in key}
121
        data = {key.replace(self.pattern, ''): value for key, value in data.iteritems()}
122
        return data
123

  
124

  
125
class BirthDate(DateType):
126
    tagname = 'date'
127

  
128

  
129
class StartDate(DateType):
130
    tagname = 'dateDebut'
131

  
132

  
133
class EndDate(DateType):
134
    tagname = 'dateFin'
135

  
136

  
137
# SIMPLE TYPES
138

  
139

  
140
class Document(SimpleType):
141
    tagname = 'natureDocument'
142
    allowed_values = ('CPI', 'EXTAF', 'EXTSF', 'EXTPL')
143

  
144

  
145
class Origin(SimpleType):
146
    tagname = 'origine'
147
    allowed_values = ('internet', 'guichet', 'courrier')
148

  
149

  
150
class Title(SimpleType):
151
    tagname = 'genre'
152
    allowed_values = ('M', 'Mme', 'Mlle')
153

  
154

  
155
class Sex(SimpleType):
156
    tagname = 'sexe'
157
    allowed_values = ('F', 'M', 'NA')
158

  
159

  
160
class Certificate(SimpleType):
161
    tagname = 'natureEvenement'
162
    allowed_values = ('NAI', 'DEC', 'MAR', 'REC')
163

  
164

  
165
class ConcernedKind(SimpleType):
166
    tagname = 'typeInteresse'
167
    allowed_values = ('reconnu', 'auteur')
168

  
169

  
170
# COMPLEX TYPES
171

  
172

  
173
class Names(ComplexType):
174
    tagname = 'noms'
175
    sequence = ('nomDeFamille', 'nomUsgae', 'typeUsage')
176

  
177
    def __init__(self, data):
178
        super(Names, self).__init__(data)
179
        if self.data.get('lastname'):
180
            self.nomDeFamille = self.data['lastname']
181
            self.nomUsage = self.data.get('usual_name', '')
182
            self.typeUsage = self.data.get('name_usage', '')
183

  
184

  
185
class Place(ComplexType):
186
    tagname = 'lieu'
187
    sequence = ('ville', 'province', 'pays')
188

  
189
    def __init__(self, data):
190
        super(Place, self).__init__(data)
191
        if self.data.get('city'):
192
            self.ville = self.data['city']
193
            self.province = self.data.get('county', '')
194
            self.pays = self.data.get('country', '')
195

  
196

  
197
class Address(ComplexType):
198
    tagname = 'adresse'
199
    sequence = ('ligneAdr1', 'ligneAdr2', 'codePostal',
200
                'lieu', 'mail', 'tel')
201
    pattern = 'address_'
202

  
203
    def __init__(self, data):
204
        super(Address, self).__init__(data)
205
        self.ligneAdr1 = self.data.get('street', '')
206
        self.ligneAdr2 = self.data.get('complement', '')
207
        self.lieu = Place(self.data)
208
        self.mail = self.data.get('email', '')
209
        self.tel = self.data.get('phone', '')
210

  
211

  
212
class Birth(ComplexType):
213
    tagname = 'naissance'
214
    sequence = ('date', 'lieu')
215
    pattern = 'birth_'
216

  
217
    def __init__(self, data):
218
        super(Birth, self).__init__(data)
219
        birthdate = self.data.get('date', None)
220
        if birthdate:
221
            self.date = BirthDate(birthdate)
222
        self.lieu = Place(self.data)
223

  
224

  
225
class EventDate(ComplexType):
226
    tagname = 'dateEvenement'
227
    sequence = ('dateDebut', 'dateFin')
228

  
229
    def __init__(self, data):
230
        super(EventDate, self).__init__(data)
231
        self.dateDebut = StartDate(self.data['event_date_start'])
232
        if data.get('event_date_end', None):
233
            self.dateFin = EndDate(self.data['event_date_end'])
234

  
235

  
236
class EventPlace(Place):
237
    tagname = 'lieuEvenement'
238
    pattern = 'event_'
239

  
240

  
241
class Person(ComplexType):
242
    sequence = ('noms', 'prenoms', 'genre', 'adresse', 'sexe',
243
                'pere', 'mere', 'naissance')
244

  
245
    def __init__(self, data):
246
        super(Person, self).__init__(data)
247
        self.noms = Names(self.data)
248
        self.prenoms = self.data.get('firstnames', '')
249
        if self.data.get('title', None):
250
            self.genre = Title(self.data['title'])
251
        self.adresse = Address(self.data)
252
        if 'sex' in self.data:
253
            self.sexe = Sex(self.data['sex'])
254
        self.naissance = Birth(self.data)
255

  
256

  
257
class ApplicantPerson(Person):
258
    tagname = 'individu'
259
    pattern = 'applicant_'
260
    sequence = ('noms', 'prenoms', 'genre', 'adresse')
261

  
262

  
263
class Father(Person):
264
    tagname = 'pere'
265
    pattern = 'father_'
266
    sequence = ('noms', 'prenoms', 'genre')
267

  
268

  
269
class Mother(Person):
270
    tagname = 'mere'
271
    pattern = 'mother_'
272
    sequence = ('noms', 'prenoms', 'genre')
273

  
274

  
275
class ConcernedCommon(Person):
276
    sequence = ('noms', 'prenoms', 'genre', 'sexe',
277
                'pere', 'mere', 'naissance')
278

  
279
    def __init__(self, data):
280
        super(ConcernedCommon, self).__init__(data)
281
        self.pere = Father(self.data)
282
        self.mere = Mother(self.data)
283

  
284

  
285
class Concerned(ConcernedCommon):
286
    tagname = 'interesse'
287
    pattern = 'concerned_'
288

  
289

  
290
class Partner(ConcernedCommon):
291
    tagname = 'conjoint'
292
    pattern = 'partner_'
293

  
294

  
295
class Applicant(ComplexType):
296
    tagname = 'demandeur'
297
    sequence = ('qualiteDemandeur', 'individu')
298

  
299
    def __init__(self, data):
300
        self.qualiteDemandeur = data.get('applicant_status', '')
301
        self.individu = ApplicantPerson(data)
302

  
303

  
304
class Event(ComplexType):
305
    tagname = 'evenement'
306
    sequence = ('interesse', 'conjoint', 'natureEvenement',
307
                'typeInteresse', 'dateEvenement', 'lieuEvenement')
308

  
309
    def __init__(self, data):
310
        certificate_type = data['certificate_type']
311
        self.interesse = Concerned(data)
312
        if certificate_type == 'MAR':
313
            self.conjoint = Partner(data)
314
        self.natureEvenement = Certificate(data['certificate_type'])
315
        if data.get('concerned_kind', None):
316
            self.typeInteresse = ConcernedKind(data['concerned_kind'])
317
        self.dateEvenement = EventDate(data)
318
        self.lieuEvenement = EventPlace(data)
319

  
320

  
321
class CivilStatusApplication(ComplexType):
322
    tagname = 'demandeEtatCivil'
323
    sequence = (
324
        'identifiant', 'demandeur', 'natureDocument', 'nbExemplaire',
325
        'dateDemande', 'evenement', 'motif', 'origine', 'commentaire')
326

  
327
    def __init__(self, data):
328
        self.identifiant = data['application_id']
329
        self.demandeur = Applicant(data)
330
        self.natureDocument = Document(data['document_type'])
331
        self.nbExemplaire = data['document_copies']
332
        self.dateDemande = data['application_time']
333
        self.evenement = Event(data)
334
        self.motif = data.get('application_reason', '')
335
        if data.get('application_origin', None):
336
            self.origine = Origin(data['application_origin'])
337
        self.commentaire = data.get('application_comment', '')
338

  
339
    def save(self, path, name):
340
        filepath = os.path.join(path, name + '.xml')
341
        xml = self.xml
342
        content = etree.tostring(xml, pretty_print=True)
343
        default_storage.save(filepath, ContentFile(content))
344
        return zipdir(path)
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
        migrations.CreateModel(
29
            name='Demand',
30
            fields=[
31
                ('created_at', models.DateTimeField(auto_now_add=True)),
32
                ('updated_at', models.DateTimeField(auto_now=True)),
33
                ('received_at', models.DateTimeField()),
34
                ('demand_id', models.CharField(max_length=32, serialize=False, primary_key=True)),
35
                ('kind', models.CharField(max_length=32)),
36
                ('resource', models.ForeignKey(to='cityweb.CityWeb')),
37
            ],
38
        ),
39
    ]
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 dateutil.parser import parse as dateutil_parse
21

  
22
from django.db import models
23
from django.utils.translation import ugettext_lazy as _
24
from django.core.files.storage import default_storage
25

  
26
from passerelle.base.models import BaseResource
27
from passerelle.utils.api import endpoint
28
from passerelle.utils.jsonresponse import APIError
29

  
30
from .cityweb import CivilStatusApplication
31

  
32

  
33
CERTIFICATE_TYPES = [
34
    {"id": "NAI", "text": "Naissance"},
35
    {"id": "MAR", "text": "Mariage"},
36
    {"id": "REC", "text": "Reconnaissance"},
37
    {"id": "DEC", "text": "Décès"}
38
]
39

  
40
SEXES = [
41
    {"id": "M", "text": "Homme"},
42
    {"id": "F", "text": "Femme"},
43
    {"id": "NA", "text": "Autre"}
44
]
45

  
46
TITLES = [
47
    {"id": "M", "text": "Monsieur"},
48
    {"id": "Mme", "text": "Madame"},
49
    {"id": "Mlle", "text": "Mademoiselle"}
50
]
51

  
52
DOCUMENT_TYPES = [
53
    {"id": "CPI", "text": "Copie intégrale"},
54
    {"id": "EXTAF", "text": "Extrait avec filiation"},
55
    {"id": "EXTSF", "text": "Extrait sans filiation"},
56
    {"id": "EXTPL", "text": "Extrait plurilingue"}
57
]
58

  
59
CONCERNED = [
60
    {"id": "reconnu", "text": "Reconnu"},
61
    {"id": "auteur", "text": "Auteur"}
62
]
63

  
64
ORIGINGS = [
65
    {"id": "internet", "text": "Internet"},
66
    {"id": "guichet", "text": "Guichet"},
67
    {"id": "courrier", "text": "Courrier"}
68
]
69

  
70

  
71
class CityWeb(BaseResource):
72
    category = _('Business Process Connectors')
73

  
74
    class Meta:
75
        verbose_name = "CityWeb - Demande d'acte d'état civil"
76

  
77
    @classmethod
78
    def get_verbose_name(cls):
79
        return cls._meta.verbose_name
80

  
81
    @endpoint(perm='can_access', methods=['post'], description=_('Create a demand'))
82
    def create(self, request, *args, **kwargs):
83
        payload = json.loads(request.body)
84
        # check mandatory keys
85
        for key in ('application_id', 'application_time', 'certificate_type'):
86
            if key not in payload:
87
                raise APIError('<%s> is required' % key)
88

  
89
        attrs = {
90
            'demand_id': '%s-%s' % (payload['application_id'], payload['certificate_type']),
91
            'kind': payload['certificate_type'], 'resource': self,
92
            'received_at': dateutil_parse(payload['application_time'])}
93

  
94
        demand, created = Demand.objects.get_or_create(**attrs)
95
        result = demand.create(payload)
96
        return {'data': {'demand_id': result}}
97

  
98
    @endpoint(perm='can_access', description=_('Get title list'))
99
    def titles(self, request):
100
        return {'data': TITLES}
101

  
102
    @endpoint(perm='can_access', description=_('Get sex list'))
103
    def sexes(self, request):
104
        return {'data': SEXES}
105

  
106
    @endpoint(perm='can_access', description=_('Get concerned status list'))
107
    def concerned(self, request):
108
        return {'data': CONCERNED}
109

  
110
    @endpoint(perm='can_access', description=_('Get application origin list'))
111
    def origins(self, request):
112
        return {'data': ORIGINGS}
113

  
114
    @endpoint(name='certificate-types', perm='can_access',
115
              description=_('Get certificate type list'), parameters={'exclude': {'example_value': 'REC'}})
116
    def certificate_types(self, request, exclude=''):
117
        return {'data': [item for item in CERTIFICATE_TYPES
118
                if item.get('id') not in exclude.split(',')]}
119

  
120
    @endpoint(name='document-types', perm='can_access',
121
              description=_('Get document type list'), parameters={'exclude': {'example_value': 'EXTPL'}})
122
    def document_types(self, request, exclude=''):
123
        return {'data': [item for item in DOCUMENT_TYPES
124
                if item.get('id') not in exclude.split(',')]}
125

  
126

  
127
class Demand(models.Model):
128
    created_at = models.DateTimeField(auto_now_add=True)
129
    updated_at = models.DateTimeField(auto_now=True)
130
    received_at = models.DateTimeField()
131
    resource = models.ForeignKey(CityWeb)
132
    demand_id = models.CharField(max_length=32, primary_key=True)
133
    kind = models.CharField(max_length=32)
134

  
135
    def __unicode__(self):
136
        return '%s - %s' % (self.resource.slug, self.demand_id)
137

  
138
    @property
139
    def filepath(self):
140
        return os.path.join(
141
            default_storage.path('cityweb'), self.resource.slug, self.demand_id)
142

  
143
    def create(self, data):
144
        application = CivilStatusApplication(data)
145
        application.save(self.filepath, self.demand_id)
146
        return self.demand_id
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',
99 99
            'cmislib',
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_origin": "internet",
18
    "application_id": "15-4",
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_father_firstnames": "John Oliver",
28
    "concerned_father_lastname": "Smith",
29
    "concerned_father_name_usage": "Smith",
30
    "concerned_father_title": "M",
31
    "concerned_father_title_label": "Monsieur",
32
    "concerned_father_usual_name": "Smith",
33
    "concerned_firstnames": "Kevin",
34
    "concerned_lastname": "Whatever",
35
    "concerned_mother_firstnames": "Kim",
36
    "concerned_mother_lastname": "Smith",
37
    "concerned_mother_name_usage": "nom d'\u00e9pouse",
38
    "concerned_mother_title": "Mme",
39
    "concerned_mother_usual_name": "Smith",
40
    "concerned_name_usage": "",
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_father_firstnames": "John Oliver",
28
    "concerned_father_lastname": "Smith",
29
    "concerned_father_name_usage": "Smith",
30
    "concerned_father_title": "M",
31
    "concerned_father_title_label": "Monsieur",
32
    "concerned_father_usual_name": "Smith",
33
    "concerned_firstnames": "Kevin",
34
    "concerned_lastname": "Whatever",
35
    "concerned_mother_firstnames": "Kim",
36
    "concerned_mother_lastname": "Smith",
37
    "concerned_mother_name_usage": "nom d'\u00e9pouse",
38
    "concerned_mother_title": "Mme",
39
    "concerned_mother_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_father_firstnames": "Antonio",
54
    "partner_father_lastname": "Scaramucci",
55
    "partner_father_title": "M",
56
    "partner_father_title_label": "Monsieur",
57
    "partner_firstnames": "Chelsea",
58
    "partner_lastname": "Contrao",
59
    "partner_mother_firstnames": "Marguerite",
60
    "partner_mother_lastname": "Scaramucci",
61
    "partner_mother_name_usage": "nom d'\u00e9pouse",
62
    "partner_mother_title": "Mme",
63
    "partner_mother_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', '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', '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', '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)
0
-