Projet

Général

Profil

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

Josué Kouka, 18 août 2017 14:29

Télécharger (40,8 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                 | 335 +++++++++++++++++++++
 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 ++++++++++++
 tox.ini                                            |   1 +
 14 files changed, 1049 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
class BaseType(object):
34
    """Base data binding object
35
    """
36
    tagname = None
37

  
38
    def __repr__(self):
39
        return '<%s>' % self.tagname
40

  
41
    @classmethod
42
    def make_element(cls, tagname, value=None, namespace=None, nsmap=None):
43
        M = xobject.ElementMaker(annotate=False, namespace=namespace,
44
                                 nsmap=nsmap)
45
        return M(tagname, value)
46

  
47
    @property
48
    def xml(self):
49
        if isinstance(self, (SimpleType, DateType)):
50
            return self.make_element(self.tagname, self.value, namespace=self.namespace)
51
        tag = self.make_element(self.tagname, namespace=self.namespace, nsmap=self.nsmap)
52
        for subelt in self.sequence:
53
            attr = getattr(self, subelt, None)
54
            if not attr:
55
                continue
56
            if isinstance(attr, (str, unicode)) and attr:
57
                tag.append(self.make_element(subelt, attr, namespace=self.namespace))
58
            else:
59
                xml = attr.xml
60
                if not is_clean(xml):
61
                    continue
62
                tag.append(xml)
63
        xobject.deannotate(tag, xsi_nil=True)
64
        return tag
65

  
66
    def pretty(self):
67
        print(etree.tostring(self.xml, pretty_print=True))
68

  
69

  
70
class CityWebType(BaseType):
71
    namespace = 'http://tempuri.org/XMLSchema.xsd'
72
    nsmap = {'xs': 'http://tempuri.org/XMLSchema.xsd'}
73

  
74

  
75
class SimpleType(CityWebType):
76
    """Data binding class for SimpleType
77
    """
78
    allowed_values = None
79

  
80
    def __init__(self, value):
81
        if value not in self.allowed_values:
82
            raise APIError('<%s> value (%s) not in %s' % (self.tagname, value,
83
                                                          self.allowed_values))
84
        self.value = value
85

  
86

  
87
class DateType(CityWebType):
88

  
89
    def __init__(self, value):
90
        try:
91
            self.value = dateutil_parse(value).date().isoformat()
92
        except (ValueError,) as exc:
93
            raise APIError(exc.message)
94

  
95

  
96
class ComplexType(CityWebType):
97
    """Data binding class for ComplexType
98
    """
99
    sequence = None
100
    pattern = None
101

  
102
    def __init__(self, data):
103
        if self.pattern:
104
            data = self.extract_by_pattern(data)
105
        self.data = data
106

  
107
    def extract_by_pattern(self, data):
108
        data = {key: value for key, value in data.iteritems() if self.pattern in key}
109
        data = {key.replace(self.pattern, ''): value for key, value in data.iteritems()}
110
        return data
111

  
112

  
113
class BirthDate(DateType):
114
    tagname = 'date'
115

  
116

  
117
class StartDate(DateType):
118
    tagname = 'dateDebut'
119

  
120

  
121
class EndDate(DateType):
122
    tagname = 'dateFin'
123

  
124

  
125
# SIMPLE TYPES
126

  
127

  
128
class Document(SimpleType):
129
    tagname = 'natureDocument'
130
    allowed_values = ('CPI', 'EXTAF', 'EXTSF', 'EXTPL')
131

  
132

  
133
class Origin(SimpleType):
134
    tagname = 'origine'
135
    allowed_values = ('internet', 'guichet', 'courrier')
136

  
137

  
138
class Title(SimpleType):
139
    tagname = 'genre'
140
    allowed_values = ('M', 'Mme', 'Mlle')
141

  
142

  
143
class Sex(SimpleType):
144
    tagname = 'sexe'
145
    allowed_values = ('F', 'M', 'NA')
146

  
147

  
148
class Certificate(SimpleType):
149
    tagname = 'natureEvenement'
150
    allowed_values = ('NAI', 'DEC', 'MAR', 'REC')
151

  
152

  
153
class ConcernedKind(SimpleType):
154
    tagname = 'typeInteresse'
155
    allowed_values = ('reconnu', 'auteur')
156

  
157

  
158
# COMPLEX TYPES
159

  
160

  
161
class Names(ComplexType):
162
    tagname = 'noms'
163
    sequence = ('nomDeFamille', 'nomUsgae', 'typeUsage')
164

  
165
    def __init__(self, data):
166
        super(Names, self).__init__(data)
167
        if self.data.get('lastname'):
168
            self.nomDeFamille = self.data['lastname']
169
            self.nomUsage = self.data.get('usual_name', '')
170
            self.typeUsage = self.data.get('name_usage', '')
171

  
172

  
173
class Place(ComplexType):
174
    tagname = 'lieu'
175
    sequence = ('ville', 'province', 'pays')
176

  
177
    def __init__(self, data):
178
        super(Place, self).__init__(data)
179
        if self.data.get('city'):
180
            self.ville = self.data['city']
181
            self.province = self.data.get('county', '')
182
            self.pays = self.data.get('country', '')
183

  
184

  
185
class Address(ComplexType):
186
    tagname = 'adresse'
187
    sequence = ('ligneAdr1', 'ligneAdr2', 'codePostal',
188
                'lieu', 'mail', 'tel')
189
    pattern = 'address_'
190

  
191
    def __init__(self, data):
192
        super(Address, self).__init__(data)
193
        self.ligneAdr1 = self.data.get('street', '')
194
        self.ligneAdr2 = self.data.get('complement', '')
195
        self.lieu = Place(self.data)
196
        self.mail = self.data.get('email', '')
197
        self.tel = self.data.get('phone', '')
198

  
199

  
200
class Birth(ComplexType):
201
    tagname = 'naissance'
202
    sequence = ('date', 'lieu')
203
    pattern = 'birth_'
204

  
205
    def __init__(self, data):
206
        super(Birth, self).__init__(data)
207
        birthdate = self.data.get('date', None)
208
        if birthdate:
209
            self.date = BirthDate(birthdate)
210
        self.lieu = Place(self.data)
211

  
212

  
213
class EventDate(ComplexType):
214
    tagname = 'dateEvenement'
215
    sequence = ('dateDebut', 'dateFin')
216

  
217
    def __init__(self, data):
218
        super(EventDate, self).__init__(data)
219
        self.dateDebut = StartDate(self.data['event_date_start'])
220
        if data.get('event_date_end', None):
221
            self.dateFin = EndDate(self.data['event_date_end'])
222

  
223

  
224
class EventPlace(Place):
225
    tagname = 'lieuEvenement'
226
    pattern = 'event_'
227

  
228

  
229
class Person(ComplexType):
230
    sequence = ('noms', 'prenoms', 'genre', 'adresse', 'sexe',
231
                'pere', 'mere', 'naissance')
232

  
233
    def __init__(self, data):
234
        super(Person, self).__init__(data)
235
        self.noms = Names(self.data)
236
        self.prenoms = self.data.get('firstnames', '')
237
        if self.data.get('title', None):
238
            self.genre = Title(self.data['title'])
239
        self.adresse = Address(self.data)
240
        if 'sex' in self.data:
241
            self.sexe = Sex(self.data['sex'])
242
        self.naissance = Birth(self.data)
243

  
244

  
245
class ApplicantPerson(Person):
246
    tagname = 'individu'
247
    pattern = 'applicant_'
248
    sequence = ('noms', 'prenoms', 'genre', 'adresse')
249

  
250

  
251
class Father(Person):
252
    tagname = 'pere'
253
    pattern = 'father_'
254
    sequence = ('noms', 'prenoms', 'genre')
255

  
256

  
257
class Mother(Person):
258
    tagname = 'mere'
259
    pattern = 'mother_'
260
    sequence = ('noms', 'prenoms', 'genre')
261

  
262

  
263
class ConcernedCommon(Person):
264
    sequence = ('noms', 'prenoms', 'genre', 'sexe',
265
                'pere', 'mere', 'naissance')
266

  
267
    def __init__(self, data):
268
        super(ConcernedCommon, self).__init__(data)
269
        self.pere = Father(self.data)
270
        self.mere = Mother(self.data)
271

  
272

  
273
class Concerned(ConcernedCommon):
274
    tagname = 'interesse'
275
    pattern = 'concerned_'
276

  
277

  
278
class Partner(ConcernedCommon):
279
    tagname = 'conjoint'
280
    pattern = 'partner_'
281

  
282

  
283
class Applicant(ComplexType):
284
    tagname = 'demandeur'
285
    sequence = ('qualiteDemandeur', 'individu')
286

  
287
    def __init__(self, data):
288
        self.qualiteDemandeur = data.get('applicant_status', '')
289
        self.individu = ApplicantPerson(data)
290

  
291

  
292
class Event(ComplexType):
293
    tagname = 'evenement'
294
    sequence = ('interesse', 'conjoint', 'natureEvenement',
295
                'typeInteresse', 'dateEvenement', 'lieuEvenement')
296

  
297
    def __init__(self, data):
298
        certificate_type = data['certificate_type']
299
        self.interesse = Concerned(data)
300
        if certificate_type == 'MAR':
301
            self.conjoint = Partner(data)
302
        self.natureEvenement = Certificate(data['certificate_type'])
303
        if data.get('concerned_kind', None):
304
            self.typeInteresse = ConcernedKind(data['concerned_kind'])
305
        self.dateEvenement = EventDate(data)
306
        self.lieuEvenement = EventPlace(data)
307

  
308

  
309
class CivilStatusApplication(ComplexType):
310
    tagname = 'demandeEtatCivil'
311
    sequence = (
312
        'identifiant', 'demandeur', 'natureDocument', 'nbExemplaire',
313
        'dateDemande', 'evenement', 'motif', 'origine', 'commentaire')
314

  
315
    def __init__(self, data):
316
        self.identifiant = data['application_id']
317
        self.demandeur = Applicant(data)
318
        self.natureDocument = Document(data['document_type'])
319
        self.nbExemplaire = data['document_copies']
320
        self.dateDemande = data['application_time']
321
        self.evenement = Event(data)
322
        self.motif = data.get('application_reason', '')
323
        if data.get('application_origin', None):
324
            self.origine = Origin(data['application_origin'])
325
        self.commentaire = data.get('application_comment', '')
326

  
327
    def zip(self, path, filename):
328
        basename = os.path.join(path, filename)
329
        archname = basename + '.zip'
330
        filepath = basename + '.xml'
331
        content = etree.tostring(self.xml, pretty_print=True)
332
        default_storage.save(filepath, ContentFile(content))
333
        with zipfile.ZipFile(archname, 'w') as zipf:
334
            zipf.write(filepath, os.path.basename(filepath))
335
        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
        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
ORIGINS = [
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': ORIGINS}
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 basepath(self):
140
        return os.path.join(
141
            default_storage.path('cityweb'), self.resource.slug)
142

  
143
    def create(self, data):
144
        application = CivilStatusApplication(data)
145
        application.zip(self.basepath, 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.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
-