passerelle/passerelle/apps/cityweb/cityweb.py

387 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2017 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import stat
from lxml import etree, objectify as xobject
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.utils import six
from django.utils.dateparse import parse_date
from django.utils.encoding import force_bytes
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.files import atomic_write
CERTIFICATE_TYPES = [
{"id": "NAI", "text": "Naissance"},
{"id": "MAR", "text": "Mariage"},
{"id": "REC", "text": "Reconnaissance"},
{"id": "DEC", "text": "Décès"},
]
SEXES = [{"id": "M", "text": "Homme"}, {"id": "F", "text": "Femme"}, {"id": "NA", "text": "Autre"}]
TITLES = [
{"id": "M", "text": "Monsieur"},
{"id": "Mme", "text": "Madame"},
{"id": "Mlle", "text": "Mademoiselle"},
]
DOCUMENT_TYPES = [
{"id": "CPI", "text": "Copie intégrale"},
{"id": "EXTAF", "text": "Extrait avec filiation"},
{"id": "EXTSF", "text": "Extrait sans filiation"},
{"id": "EXTPL", "text": "Extrait plurilingue"},
]
CONCERNED = [{"id": "reconnu", "text": "Reconnu"}, {"id": "auteur", "text": "Auteur"}]
ORIGINS = [
{"id": "internet", "text": "Internet"},
{"id": "guichet", "text": "Guichet"},
{"id": "courrier", "text": "Courrier"},
]
def is_clean(element):
if not element.getchildren() and element.text is None:
return False
return all(is_clean(child) for child in element.iterchildren())
class BaseType(object):
"""Base data binding object"""
tagname = None
def __repr__(self):
return '<%s>' % self.tagname
@classmethod
def make_element(cls, tagname, value=None, namespace=None, nsmap=None):
M = xobject.ElementMaker(annotate=False, namespace=namespace, nsmap=nsmap)
return M(tagname, value)
@property
def xml(self):
if isinstance(self, (SimpleType, DateType)):
return self.make_element(self.tagname, self.value, namespace=self.namespace)
tag = self.make_element(self.tagname, namespace=self.namespace, nsmap=self.nsmap)
for subelt in self.sequence:
attr = getattr(self, subelt, None)
if not attr:
continue
if isinstance(attr, six.string_types):
tag.append(self.make_element(subelt, attr, namespace=self.namespace))
else:
xml = attr.xml
if not is_clean(xml):
continue
tag.append(xml)
xobject.deannotate(tag, xsi_nil=True, cleanup_namespaces=True)
return tag
def __str__(self):
return etree.tostring(self.xml, pretty_print=True)
class CityWebType(BaseType):
namespace = 'http://tempuri.org/XMLSchema.xsd'
nsmap = {'xs': 'http://tempuri.org/XMLSchema.xsd'}
class SimpleType(CityWebType):
"""Data binding class for SimpleType"""
allowed_values = None
def __init__(self, value):
if value not in self.allowed_values:
raise APIError('<%s> value (%s) not in %s' % (self.tagname, value, self.allowed_values))
self.value = value
class DateType(CityWebType):
def __init__(self, value):
try:
self.value = parse_date(value)
except (ValueError,) as exc:
raise APIError('Invalid date (%s) for <%s>: %s' % (value, self.tagname, exc))
if not self.value:
raise APIError('Invalid date (%s) for <%s>' % (value, self.tagname))
self.value = self.value.isoformat()
class ComplexType(CityWebType):
"""Data binding class for ComplexType"""
sequence = None
pattern = None
def __init__(self, data):
if self.pattern:
data = self.extract_by_pattern(data)
self.data = data
def extract_by_pattern(self, data):
data = {key: value for key, value in data.items() if self.pattern in key}
data = {key.replace(self.pattern, ''): value for key, value in data.items()}
return data
class BirthDate(DateType):
tagname = 'date'
class StartDate(DateType):
tagname = 'dateDebut'
class EndDate(DateType):
tagname = 'dateFin'
# SIMPLE TYPES
class Document(SimpleType):
tagname = 'natureDocument'
allowed_values = list(map(lambda x: x['id'], DOCUMENT_TYPES))
class Origin(SimpleType):
tagname = 'origine'
allowed_values = list(map(lambda x: x['id'], ORIGINS))
class Title(SimpleType):
tagname = 'genre'
allowed_values = list(map(lambda x: x['id'], TITLES))
class Sex(SimpleType):
tagname = 'sexe'
allowed_values = list(map(lambda x: x['id'], SEXES))
class Certificate(SimpleType):
tagname = 'natureEvenement'
allowed_values = list(map(lambda x: x['id'], CERTIFICATE_TYPES))
class ConcernedKind(SimpleType):
tagname = 'typeInteresse'
allowed_values = ('reconnu', 'auteur')
# COMPLEX TYPES
class Names(ComplexType):
tagname = 'noms'
sequence = ('nomDeFamille', 'nomUsgae', 'typeUsage')
def __init__(self, data):
super(Names, self).__init__(data)
if self.data.get('lastname'):
self.nomDeFamille = self.data['lastname']
self.nomUsage = self.data.get('usual_name', '')
self.typeUsage = self.data.get('name_usage', '')
class Place(ComplexType):
tagname = 'lieu'
sequence = ('ville', 'province', 'pays')
def __init__(self, data):
super(Place, self).__init__(data)
if self.data.get('city'):
self.ville = self.data['city']
self.province = self.data.get('county', '')
self.pays = self.data.get('country', '')
class Address(ComplexType):
tagname = 'adresse'
sequence = ('ligneAdr1', 'ligneAdr2', 'codePostal', 'lieu', 'mail', 'tel')
pattern = 'address_'
def __init__(self, data):
super(Address, self).__init__(data)
self.ligneAdr1 = self.data.get('street', '')
self.ligneAdr2 = self.data.get('complement', '')
self.codePostal = self.data.get('zipcode', '')
self.lieu = Place(self.data)
self.mail = self.data.get('email', '')
self.tel = self.data.get('phone', '')
class Birth(ComplexType):
tagname = 'naissance'
sequence = ('date', 'lieu')
pattern = 'birth_'
def __init__(self, data):
super(Birth, self).__init__(data)
birthdate = self.data.get('date', None)
if birthdate:
self.date = BirthDate(birthdate)
self.lieu = Place(self.data)
class EventDate(ComplexType):
tagname = 'dateEvenement'
sequence = ('dateDebut', 'dateFin')
def __init__(self, data):
super(EventDate, self).__init__(data)
self.dateDebut = StartDate(self.data['event_date_start'])
if data.get('event_date_end', None):
self.dateFin = EndDate(self.data['event_date_end'])
class EventPlace(Place):
tagname = 'lieuEvenement'
pattern = 'event_'
class Person(ComplexType):
sequence = ('noms', 'prenoms', 'genre', 'adresse', 'sexe', 'pere', 'mere', 'naissance')
def __init__(self, data):
super(Person, self).__init__(data)
self.noms = Names(self.data)
self.prenoms = self.data.get('firstnames', '')
if self.data.get('title', None):
self.genre = Title(self.data['title'])
self.adresse = Address(self.data)
if self.data.get('sex', None):
self.sexe = Sex(self.data['sex'])
self.naissance = Birth(self.data)
class ApplicantPerson(Person):
tagname = 'individu'
pattern = 'applicant_'
sequence = ('noms', 'prenoms', 'genre', 'adresse')
class Parent(Person):
sequence = ('noms', 'prenoms', 'genre')
def __init__(self, data, pattern):
self.pattern = pattern
super(Parent, self).__init__(data)
sex = self.data.get('sex')
if sex:
if sex == 'M':
self.tagname = 'pere'
else:
self.tagname = 'mere'
else:
if pattern.startswith('parent1'):
self.tagname = 'pere'
else:
self.tagname = 'mere'
class ConcernedCommon(Person):
sequence = ('noms', 'prenoms', 'genre', 'sexe', 'parent1', 'parent2', 'naissance')
def __init__(self, data):
super(ConcernedCommon, self).__init__(data)
self.parent1 = Parent(self.data, 'parent1_')
self.parent2 = Parent(self.data, 'parent2_')
class Concerned(ConcernedCommon):
tagname = 'interesse'
pattern = 'concerned_'
class Partner(ConcernedCommon):
tagname = 'conjoint'
pattern = 'partner_'
class Applicant(ComplexType):
tagname = 'demandeur'
sequence = ('qualiteDemandeur', 'individu')
def __init__(self, data):
self.qualiteDemandeur = data.get('applicant_status', '')
self.individu = ApplicantPerson(data)
class Event(ComplexType):
tagname = 'evenement'
sequence = ('interesse', 'conjoint', 'natureEvenement', 'typeInteresse', 'dateEvenement', 'lieuEvenement')
def __init__(self, data):
certificate_type = data['certificate_type']
self.interesse = Concerned(data)
if certificate_type == 'MAR':
self.conjoint = Partner(data)
self.natureEvenement = Certificate(data['certificate_type'])
if data.get('concerned_kind', None):
self.typeInteresse = ConcernedKind(data['concerned_kind'])
self.dateEvenement = EventDate(data)
self.lieuEvenement = EventPlace(data)
class CivilStatusApplication(ComplexType):
tagname = 'demandeEtatCivil'
sequence = (
'identifiant',
'demandeur',
'natureDocument',
'nbExemplaire',
'dateDemande',
'evenement',
'motif',
'origine',
'commentaire',
)
def __init__(self, data):
self.identifiant = data['application_id']
self.demandeur = Applicant(data)
self.natureDocument = Document(data['document_type'])
self.nbExemplaire = data['document_copies']
self.dateDemande = data['application_time']
self.evenement = Event(data)
self.motif = data.get('application_reason', '')
if data.get('application_origin', None):
self.origine = Origin(data['application_origin'])
self.commentaire = data.get('application_comment', '')
def save(self, path):
basename = os.path.join(path, self.identifiant)
filename = basename + '.xml'
content = etree.tostring(self.xml, pretty_print=True)
filepath = default_storage.path(filename)
dirname = os.path.dirname(filepath)
if not os.path.exists(dirname):
os.makedirs(dirname)
with atomic_write(filepath) as fd:
fd.write(force_bytes(content))
# set read only permission for owner and group
os.chmod(filepath, stat.S_IRUSR | stat.S_IRGRP)
return filename