Projet

Général

Profil

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

Josué Kouka, 16 août 2017 18:31

Télécharger (39,5 ko)

Voir les différences:

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

 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                  | 144 +++++++++
 passerelle/settings.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 +++++++++++
 11 files changed, 1053 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
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
from dateutil.parser import parse as dateutil_parse
18
from lxml import etree, objectify as xobject
19

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

  
23
from passerelle.utils.jsonresponse import APIError
24
from passerelle.contrib.mdel.utils import zipdir
25

  
26

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

  
34

  
35
class BaseType(object):
36
    """Base data binding object
37
    """
38
    tagname = None
39

  
40
    def __repr__(self):
41
        return '<%s>' % self.tagname
42

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

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

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

  
71

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

  
76

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

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

  
88

  
89
class DateType(CityWebType):
90

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

  
97

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

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

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

  
114
    def set_only_if_all_present(self, data):
115
        """Set
116
        """
117
        if not self.sequence:
118
            return
119
        if all(elt in data for elt in self.sequence):
120
            for elt in self.sequence:
121
                setattr(self, elt, data[elt])
122

  
123

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

  
127

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

  
131

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

  
135

  
136
# SIMPLE TYPES
137

  
138

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

  
143

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

  
148

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

  
153

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

  
158

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

  
163

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

  
168

  
169
# COMPLEX TYPES
170

  
171

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

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

  
183

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

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

  
195

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

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

  
210

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

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

  
223

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

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

  
234

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

  
239

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

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

  
255

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

  
261

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

  
267

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

  
273

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

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

  
283

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

  
288

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

  
293

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

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

  
302

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

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

  
319

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

  
326
    def __init__(self, data):
327
        print(data['certificate_type'])
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_channel', None):
336
            self.origine = Channel(data['application_channel'])
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
EVENTS_KIND = [
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
DOCUMENTS_KIND = [
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
CHANNELS = [
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'])
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')
99
    def titles(self, request):
100
        return {'data': TITLES}
101

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

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

  
110
    @endpoint(perm='can_access')
111
    def channels(self, request):
112
        return {'data': CHANNELS}
113

  
114
    @endpoint(name='events-kind', perm='can_access')
115
    def events_kind(self, request, without=''):
116
        return {'data': [item for item in EVENTS_KIND
117
                if item.get('id') not in without.split(',')]}
118

  
119
    @endpoint(name='documents-kind', perm='can_access')
120
    def documents_kind(self, request, without=''):
121
        return {'data': [item for item in DOCUMENTS_KIND
122
                if item.get('id') not in without.split(',')]}
123

  
124

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

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

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

  
141
    def create(self, data):
142
        application = CivilStatusApplication(data)
143
        application.save(self.filepath, self.demand_id)
144
        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
)
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_channel": "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_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_channel": "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_channel": "internet",
18
    "application_id": "16-1",
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_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
    "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_firstnames": "Chelsea",
54
    "partner_lastname": "Contrao",
55
    "partner_name_usage": "",
56
    "partner_parent1_firstnames": "Antonio",
57
    "partner_parent1_lastname": "Scaramucci",
58
    "partner_parent1_title": "M",
59
    "partner_parent1_title_label": "Monsieur",
60
    "partner_parent2_firstnames": "Marguerite",
61
    "partner_parent2_lastname": "Scaramucci",
62
    "partner_parent2_name_usage": "nom d'\u00e9pouse",
63
    "partner_parent2_title": "Mme",
64
    "partner_parent2_usual_name": "Gaye",
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; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a.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_channels(app, setup):
136
    response = app.get('/cityweb/test/channels/')
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_documents_kind(app, setup):
150
    response = app.get('/cityweb/test/documents-kind/')
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 = {'without': 'EXTAF,EXTSF,EXTPL'}
165
    response = app.get('/cityweb/test/documents-kind/', 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_events_kind(app, setup):
173
    response = app.get('/cityweb/test/events-kind/')
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
-