zoo/zoo/zoo_nanterre/fragments.py

870 lines
34 KiB
Python

# -*- coding: utf-8 -*-
import json
import datetime
import requests
from requests.exceptions import RequestException
from django.utils.timezone import now
from django.conf import settings
from django.db import DatabaseError
from zoo.models import Entity, Job
from . import utils
def adresse_to_fragment(adresse, principale=False):
'''Converti une adresse pour la construction d'un fragment'''
adresse_content = adresse.content
return {
'chez': adresse_content['at'],
'complement1': adresse_content['ext1'],
'complement2': adresse_content['ext2'],
'numero': adresse_content['streetnumber'],
'extension': adresse_content['streetnumberext'],
'voie': adresse_content['streetname'],
'code-voie': adresse_content['streetmatriculation'],
'code-postal': adresse_content['zipcode'],
'ville': adresse_content['city'],
'insee': adresse_content['inseecode'],
'pays': adresse_content['country'],
'estAdressePrincipale': principale,
}
def individu_to_fragment(individu, inclure_adresse=True, application=None, identite=True,
contact=True):
'''Converti un individu pour la construction d'un fragment'''
content = individu.content
SEXE = {
'femme': 'F',
'homme': 'H',
'autre': 'O',
}
sexe = SEXE[content['genre']]
fragment = {
}
if identite:
fragment.update({
'sexe': sexe,
'prenom': content['prenoms'],
'nom': content['nom_d_usage'] or content['nom_de_naissance'],
'nom-naissance': content['nom_de_naissance'],
'date-naissance': content['date_de_naissance'],
})
if contact:
fragment['email'] = content.get('email', u'')
telephones = []
for telephone in content.get('telephones', []):
telephone = telephone.copy()
t = telephone.get('type')
if t == 'maison':
telephone['type'] = 'domicile'
elif t == 'pro':
telephone['type'] = 'professionnel'
elif t == 'mobile':
pass
else:
continue
telephone['poste'] = None
telephones.append(telephone)
fragment['telephones'] = telephones
if inclure_adresse:
if content['statut_legal'] == 'mineur':
adresses = utils.adresses(individu)
fragment['adresses'] = [adresse_to_fragment(adresse,
principale=rel.content['principale'])
for adresse, rel in adresses]
else:
adresse = utils.adresse(individu)
assert adresse, 'individu %s avec plus d\'une adresse ou sans adresse' % individu.id
if adresse:
fragment['adresse'] = adresse_to_fragment(adresse)
if application:
id_metier = content.get('cles_de_federation', {}).get(application)
assert id_metier
fragment['id-metier'] = id_metier
return fragment
class FakeResponse(object):
status_code = 200
def __init__(self, content):
self.content = content
@property
def text(self):
return json.dumps(self.content, ensure_ascii=False)
def json(self):
return self.content
class FragmentBuilder(object):
ID_FRAGMENT = 'id-fragment'
human_result = None
state_on_network_error = Job.STATE_ERROR
lock_individus = False
def __init__(self):
self.fragments = []
self.individu_ids = {}
self.beneficiaires = []
self.origine = 'agent'
self.error = {}
self.response = {}
self.fragment_counter = 1
self.fragment_to_id = {}
self.id_to_fragment = {}
self.date_soumission = None
self.meta = None
def add_fragment(self, fragment):
old_fragment_counter = self.fragment_counter
fragment[self.ID_FRAGMENT] = self.fragment_counter
self.fragments.append(fragment)
self.fragment_counter += 1
return old_fragment_counter
@classmethod
def create(cls, application, individus, meta=None):
self = cls()
self.application_id = application
self.application = utils.get_application(application)
assert 'rsu_ws_url' in self.application, \
'application %s does not have a rsu_ws_url' % application
assert individus, 'pas d\'individus, pas de flux'
self.individus = individus
self.meta = meta
if self.lock_individus:
# on verrouille les entités si nécessaire
Entity.objects.filter(
id__in=[individu.id for individu in self.individus]
).select_for_update()
return self
def message(self):
content = {
'metadonnees': {
'service': self.service,
'origine': self.origine,
'date-soumission': self.date_soumission,
},
'fragments': self.fragments,
}
if self.beneficiaires:
content['metadonnees']['beneficiaires'] = self.beneficiaires
return content
def debug_response(self):
return []
def do(self, job=None, **kwargs):
# verrouille les objets
try:
if self.lock_individus:
Entity.objects.filter(
id__in=[individu.id for individu in self.individus]).select_for_update(
nowait=True)
except DatabaseError:
self.human_result = (
'Erreur due à une action concurrente sur les mêmes individus dans le RSU')
self.error = {
'code': 'lock-error',
}
for individu in self.individus:
utils.journalize(
individu,
meta=self.meta,
job_url=job and job.admin_url,
transaction=job and job.transaction,
text=self.human_result,
**kwargs)
return self.state_on_network_error
error_detail = None
try:
if settings.ZOO_NANTERRE_RSU_WS_DEBUG:
response = FakeResponse(self.debug_response())
else:
self.date_soumission = datetime.datetime.now().isoformat(' ').split('.')[0]
response = requests.post(
self.application['rsu_ws_url'],
data=json.dumps(self.message(), ensure_ascii=False).encode('utf-8'),
timeout=settings.ZOO_NANTERRE_RSU_TIMEOUT,
headers={
'Content-Type': 'application/json'
})
except RequestException as e:
error_detail = u'erreur réseau/SSL ou expiration'
self.error = {
'code': 'transport-error',
'detail': str(e),
}
state = self.state_on_network_error
else:
state = self.handle_response(response, job=job)
app_name = self.application['name']
kwargs = {}
message = self.journal_message
if state == job.STATE_SUCCESS:
# reset errors
self.error = {}
message += u': ok'
elif state == Job.STATE_ERROR:
message += u': erreur temporaire'
kwargs['error'] = self.error
elif state == Job.STATE_UNRECOVERABLE_ERROR:
message += u': erreur irrécupérable'
kwargs['error'] = self.error
else:
raise NotImplementedError
if (self.error
and self.error.get('code') == 'rsu-error'
and isinstance(self.error.get('content'), dict)
and self.error['content'].get('metier')):
message += ': ' + self.error['content']['metier']
if error_detail:
message += u': ' + error_detail
self.human_result = message
# on journalise:
# - si c'est un succès ou une erreur irrécupérable
# - dans tous les cas si l'état actuel du job n'est pas une erreur (TODO), i.e. on
# journalise toujours au premier essai
if (state == Job.STATE_SUCCESS or state == Job.STATE_UNRECOVERABLE_ERROR
or (job.state != Job.STATE_ERROR
and job.state != Job.STATE_UNRECOVERABLE_ERROR)):
for individu in self.individus:
utils.journalize(
individu,
meta=self.meta,
job_url=job and job.admin_url,
transaction=job and job.transaction,
text=message,
**kwargs)
return state
@property
def journal_message(self):
app_name = self.application['name']
return u'Synchronisation avec %s' % app_name
def to_json(self):
s = {
'application_id': self.application_id,
'application': self.application,
'individus': [],
'fragment_to_id': {k: v.id for k, v in self.fragment_to_id.items()},
'fragments': self.fragments,
'beneficiaires': self.beneficiaires,
'meta': self.meta,
'response': self.response,
'date_soumission': self.date_soumission,
'state_on_network_error': self.state_on_network_error,
}
for individu in sorted(self.individus, key=lambda i: i.id):
s['individus'].append(individu.id)
if self.error:
s['error'] = self.error
return s
@classmethod
def from_json(cls, serialization):
self = cls()
self.application_id = serialization['application_id']
self.application = serialization['application']
ids = [individu_id for individu_id in serialization.get('individus', [])]
self.individus = Entity.objects.filter(id__in=ids)
id_map = {individu.id: individu for individu in self.individus}
self.fragments = serialization['fragments']
self.fragment_to_id = serialization['fragment_to_id']
# JSON normalize keys to strings
self.fragment_to_id = {int(k): v for k, v in self.fragment_to_id.items()}
for other_id in set(self.fragment_to_id.values()) - set(id_map.keys()):
id_map[other_id] = Entity.objects.get(id=other_id)
# rebuild reverse of fragment_to_id
self.fragment_to_id = {k: id_map[v] for k, v in self.fragment_to_id.items()}
self.id_to_fragment = {v: k for k, v in self.fragment_to_id.items()}
self.error = serialization.get('error', {})
self.beneficiaires = serialization['beneficiaires']
self.meta = serialization['meta']
self.response = serialization['response']
self.date_soumission = serialization.get('date_soumission')
self.state_on_network_error = serialization.get('state_on_network_error',
cls.state_on_network_error)
return self
def reference(self, individu):
ref = individu.content['cles_de_federation'].get(self.application_id)
if ref:
return ref
if individu in self.id_to_fragment:
return '@%s' % self.id_to_fragment[individu]
return None
def relation_situation_familiale(self, individu1, individu2, relation):
if relation.content['statut'] == 'pacs/mariage':
type_de_relation = 'Marie'
elif relation.content['statut'] == 'unionlibre':
type_de_relation = 'UnionLibre'
else:
raise NotImplementedError
beneficiaire1 = self.reference(individu1)
beneficiaire2 = self.reference(individu2)
assert beneficiaire1
assert beneficiaire2
fragment = {
'type': 'relation-situation-familiale',
'fragment': {
'beneficiaire1': beneficiaire1,
'beneficiaire2': beneficiaire2,
'type-de-relation': type_de_relation,
}
}
self.add_fragment(fragment)
def suppression_relation(self, relation):
if relation.content['statut'] == 'pacs/mariage':
type_de_relation = 'Marie'
elif relation.content['statut'] == 'unionlibre':
type_de_relation = 'UnionLibre'
else:
type_de_relation = None
beneficiaire1 = self.reference(relation.left)
beneficiaire2 = self.reference(relation.right)
assert beneficiaire1
assert beneficiaire2
fragment = {
'type': 'suppression-relation',
'fragment': {
'beneficiaire1': beneficiaire1,
'beneficiaire2': beneficiaire2,
}
}
if type_de_relation:
fragment['fragment']['type-de-relation'] = type_de_relation
self.add_fragment(fragment)
def relation_responsabilite_legale(self, parent, enfant, relation):
if relation.content['statut'] == 'parent':
type_de_relation = 'Parent'
elif relation.content['statut'] == 'tiers_de_confiance':
type_de_relation = 'TiersDeConfiance'
elif relation.content['statut'] == 'representant_personne_morale_qualifiee':
type_de_relation = 'RepresentantPersonneMoraleQualifiee'
else:
raise NotImplementedError
parent_ref = self.reference(parent)
assert parent_ref
enfant_ref = self.reference(enfant)
assert enfant_ref
fragment = {
'type': 'relation-responsabilite-legale',
'fragment': {
'responsable': parent_ref,
'beneficiaire': enfant_ref,
'type-de-relation': type_de_relation,
},
}
self.add_fragment(fragment)
def mise_a_jour_contact(self, individu):
fragment = {
'type': 'maj-contact-usager',
'fragment': individu_to_fragment(individu, application=self.application_id,
inclure_adresse=False, identite=False)
}
return self.add_fragment(fragment)
def mise_a_jour_identite_usager(self, individu):
fragment = {
'type': 'maj-identite-usager',
'fragment': individu_to_fragment(individu, application=self.application_id,
inclure_adresse=False, contact=False)
}
return self.add_fragment(fragment)
def mise_a_jour_adresse(self, individu):
beneficiaire = self.reference(individu)
assert beneficiaire
adresses = [adresse_to_fragment(adresse, principale=rel.content['principale'])
for adresse, rel in utils.adresses(individu)]
fragment = {
'type': 'maj-adresse',
'fragment': {
'id-metier': beneficiaire,
'adresses': adresses,
}
}
return self.add_fragment(fragment)
def nouvel_individu(self, individu):
if self.application_id in individu.content['cles_de_federation']:
return # individu déjà fédéré, pas nouveau, on ne fait rien
# tout message contenant une création de fédération n'est pas rejoué
self.state_on_network_error = Job.STATE_UNRECOVERABLE_ERROR
content = individu_to_fragment(individu)
if utils.is_majeur(individu):
fragment = {
'type': 'nouvel-adulte',
'fragment': content,
}
else:
fragment = {
'type': 'creation-enfant',
'fragment': content,
}
fragment_id = self.add_fragment(fragment)
self.fragment_to_id[fragment_id] = individu
self.id_to_fragment[individu] = fragment_id
if individu in self.individus:
self.beneficiaires.append(fragment_id)
def mise_a_jour_individu(self, individu):
# ne gère que les individus déjà fédérés, sinon ne fait rien
if self.application_id in individu.content['cles_de_federation']:
self.mise_a_jour_identite_usager(individu)
self.mise_a_jour_adresse(individu)
self.mise_a_jour_contact(individu)
def archivage(self, individu):
beneficiaire = self.reference(individu)
assert beneficiaire
assert not beneficiaire.startswith('@')
fragment = {
'type': 'archivage',
'fragment': {
'id-metier': beneficiaire,
}
}
return self.add_fragment(fragment)
def handle_response(self, response, job):
try:
result = response.json()
except ValueError:
self.error = {
'code': 'response-is-not-json',
'status-code': response.status_code,
'content': response.text[:1024],
}
return self.state_on_network_error
self.response = result
if isinstance(result, dict) and 'http_code' in result:
# code spécifique pour infor :(
status_code = result['http_code']
content = result.get('response')
else:
status_code = response.status_code
content = result
if status_code == 200:
return self.handle_response_200(response, content, job)
else:
self.error = {
'code': 'rsu-error',
'status-code': status_code,
'content': content,
}
return Job.STATE_UNRECOVERABLE_ERROR
def handle_response_200(self, response, content, job):
return Job.STATE_SUCCESS
@property
def description(self):
from django.utils.html import mark_safe
trame = json.dumps(self.message(), indent=4, sort_keys=True,
ensure_ascii=False)
args = ()
content = mark_safe('<div style="display: inline-block">')
if self.error:
content += mark_safe('<div>Erreur: <pre>%s</pre></div>')
args += (json.dumps(self.error, indent=4, sort_keys=True,
ensure_ascii=False),)
content += mark_safe('<div>Trame: <pre>%s</pre></div>')
content += mark_safe('</div>')
args += (trame,)
return content % args
class Synchronization(FragmentBuilder):
# pour chaque adulte, on l'ajoute en nouvel-individu ou en mise à jour profil+adresse
# pour chaque enfant on ajoute les parents en nouvel-individu s'il n'existe pas
# pour chaque enfant on ajoute l'enfant ou mise à jour
# pour chaque adulte on ajoute son union si son conjoint est déjà lié ou en cours de l'être
# pour chaque enfant on ajoute ses liens de parentalité
# pour chaque individu pour lequel la synchronisation est demandée et qui est créé, son
# identifiant de fragment est ajouté à la métadonnée bénéficiaires
service = 'synchronisation'
lock_individus = True
@classmethod
def create(cls, application, individus, meta=None):
self = super(Synchronization, cls).create(application, individus, meta=meta)
individus = self.individus
adultes = set([individu for individu in individus if utils.is_majeur(individu)])
enfants = set([individu for individu in individus if utils.is_mineur(individu)])
for enfant in enfants:
# on n'ajoute les parents en synchronisation que si l'enfant
# n'a pas encore de fédération avec l'application concernée
# on appelle ça une synchronisation manuelle
if self.application_id not in enfant.content['cles_de_federation']:
for parent, rel in utils.parents(enfant):
if parent not in adultes:
adultes.add(parent)
# on ajoute les nouveaux adultes puis enfants
for adulte in adultes:
self.nouvel_individu(adulte)
for enfant in enfants:
self.nouvel_individu(enfant)
# on met à jour les autres
for adulte in (adultes & set(individus)): # uniquement les adultes bénéficiaires
self.mise_a_jour_individu(adulte)
for enfant in enfants:
self.mise_a_jour_individu(enfant)
# on ajoute les relations parentales
relation_parentale_seen = set()
for enfant in enfants:
for parent, rel in utils.parents(enfant):
parent_ref = self.reference(parent)
if not parent_ref:
continue
relation_parentale_seen.add(rel)
self.relation_responsabilite_legale(parent, enfant, rel)
# on envoie les relations parentales des enfants uniquement pour les adultes bénéficiaires
for adulte in (adultes & set(individus)):
for enfant, rel in utils.enfants(adulte):
if rel in relation_parentale_seen:
continue
enfant_ref = self.reference(enfant)
if not enfant_ref:
continue
self.relation_responsabilite_legale(adulte, enfant, rel)
# on ajoute les unions
adultes_beneficiaires = adultes & set(self.individus)
for adulte in list(adultes_beneficiaires):
if adulte not in adultes_beneficiaires:
continue
conjoint, rel = utils.conjoint(adulte)
if not conjoint:
continue
adultes_beneficiaires.discard(conjoint)
conjoint_ref = self.reference(conjoint)
if conjoint_ref:
self.relation_situation_familiale(adulte, conjoint, rel)
return self
def handle_response_200(self, response, result, job):
state = self.handle_response_200_real(response, result, job)
if state != Job.STATE_SUCCESS:
for individu in self.fragment_to_id.values():
old_key = individu.content['cles_de_federation'].pop(self.application_id, None)
if old_key:
individu.content.setdefault('anciennes_cles_de_federation', []).insert(
0, [self.application_id, now().isoformat(), old_key])
individu.save()
return state
def handle_response_200_real(self, response, result, job):
id_metiers_map = []
# result can be "null" we must accept it
if result is None:
result = []
# verify that response is coherent
if not isinstance(result, list):
self.error = {
'code': 'response-is-not-a-list',
}
return Job.STATE_UNRECOVERABLE_ERROR
if len(result) != len(self.id_to_fragment):
self.error = {
'code': 'less-responses-than-creation-fragments',
}
return Job.STATE_UNRECOVERABLE_ERROR
for fragment in result:
id_fragment = fragment.get(self.ID_FRAGMENT)
if not id_fragment:
self.error = {
'code': 'missing-id-fragment-in-response',
}
return Job.STATE_UNRECOVERABLE_ERROR
if fragment.get('ignore'):
if id_fragment not in self.beneficiaires:
continue
else:
self.error = {
'code': 'beneficiaire-fragment-was-ignored',
}
return Job.STATE_UNRECOVERABLE_ERROR
id_metier = fragment.get('id-metier')
if not id_metier:
self.error = {
'code': 'missing-id-metier-in-response',
}
return Job.STATE_UNRECOVERABLE_ERROR
if id_fragment not in self.fragment_to_id:
self.error = {
'code': 'unknown-fragment-id-in-response',
}
return Job.STATE_UNRECOVERABLE_ERROR
individu = self.fragment_to_id[id_fragment]
id_metiers_map.append((individu, id_metier))
# apply new id_metier
for individu, id_metier in id_metiers_map:
old_key = individu.content['cles_de_federation'].get(self.application_id)
individu.content['cles_de_federation'][self.application_id] = id_metier
# on sauvegarde les anciennes clés en cas d'écrasement
if old_key:
individu.content.setdefault('anciennes_cles_de_federation', []).insert(
0, [self.application_id, now().isoformat(), old_key])
individu.save()
return Job.STATE_SUCCESS
def debug_response(self):
import uuid
response = []
for fragment in self.fragments:
if fragment['type'] in ('creation-enfant', 'nouvel-adulte'):
id_fragment = fragment[self.ID_FRAGMENT]
if id_fragment in self.beneficiaires:
response.append({
self.ID_FRAGMENT: fragment[self.ID_FRAGMENT],
'id-metier': uuid.uuid4().hex,
})
else:
response.append({
self.ID_FRAGMENT: fragment[self.ID_FRAGMENT],
'ignore': True,
})
return response
def synchronize(applications, individus, meta=None, transaction=None):
'''Send synchronisation messages to applications for individus'''
for application in applications:
app_dfn = utils.get_application(application)
assert 'rsu_ws_url' in app_dfn, 'application %s has no rsu_ws_url' % application
action = Synchronization.create(application, individus, meta=meta)
job = Job.create(action, transaction=transaction)
if job.state != Job.STATE_SUCCESS:
if action.human_result:
yield action.human_result
else:
yield 'Erreur interne'
class SimpleSynchro(FragmentBuilder):
@classmethod
def create(cls, application, individus, meta=None):
self = super(SimpleSynchro, cls).create(application, individus, meta=meta)
# on ajoute les fragments de mise à jour, les enfants en dernier
individus = sorted(self.individus, key=utils.is_mineur)
for individu in individus:
assert application in individu.content['cles_de_federation']
self.pour_chaque_individu(individu)
return self
@classmethod
def par_application(cls, application, individus, meta=None, transaction=None):
action = cls.create(application, individus, meta=meta)
Job.create(action, transaction=transaction)
yield action.human_result
@classmethod
def pour_chaque_application(cls, individus, meta=None, transaction=None):
def trier_par_application(individus):
for application in utils.get_applications(rsu_ws_url=True):
federated = []
for individu in individus:
if application in individu.content['cles_de_federation']:
federated.append(individu)
if federated:
yield application, federated
def helper():
for application, federated in trier_par_application(individus):
for message in cls.par_application(application, federated, meta=meta,
transaction=transaction):
yield message
return list(helper())
class SignalementChangementAdresse(SimpleSynchro):
service = 'signalement-changement-adresse'
def pour_chaque_individu(self, individu):
self.mise_a_jour_adresse(individu)
class MiseAJourIdentite(SimpleSynchro):
service = 'mise-a-jour-identite'
def pour_chaque_individu(self, individu):
self.mise_a_jour_identite_usager(individu)
class MiseAJourInformationsContact(SimpleSynchro):
service = 'mise-a-jour-informations-contact'
def pour_chaque_individu(self, individu):
self.mise_a_jour_contact(individu)
class RelationSynchro(FragmentBuilder):
@classmethod
def create(cls, application, relation, meta=None):
self = super(RelationSynchro, cls).create(application, [relation.left, relation.right],
meta=meta)
assert cls.condition(application, relation)
self.pour_relation(relation)
return self
@classmethod
def condition(self, application, relation):
left_cond = application in relation.left.content['cles_de_federation']
right_cond = application in relation.right.content['cles_de_federation']
return left_cond and right_cond
@classmethod
def par_application(cls, application, relation, meta=None, transaction=None):
if not cls.condition(application, relation):
return
action = cls.create(application, relation, meta=meta)
Job.create(action, transaction=transaction)
yield action.human_result
@classmethod
def pour_chaque_application(cls, relation, meta=None, transaction=None):
def helper():
for application in utils.get_applications(rsu_ws_url=True):
for message in cls.par_application(application, relation, meta=meta,
transaction=transaction):
yield message
return list(helper())
class DeclarationResponsabiliteLegaleEnfant(RelationSynchro):
service = 'declaration-responsabilite-legale-enfant'
def pour_relation(self, relation):
assert relation.schema.slug == utils.RESPONSABILITE_LEGALE_REL
self.relation_responsabilite_legale(relation.left, relation.right, relation)
self.mise_a_jour_adresse(relation.right)
class DeclarationUnion(RelationSynchro):
service = 'declaration-union'
@classmethod
def par_application(cls, application, relation, meta=None, transaction=None):
for message in super(DeclarationUnion, cls).par_application(application, relation,
meta=meta,
transaction=transaction):
yield message
# Si la relation ne peut pas être envoyée on génère un simple signalement de changement
# d'adresse
if not cls.condition(application, relation):
individus = set([relation.left, relation.right]) | set(utils.enfants_couple(relation))
individus = [individu for individu in individus if application in
individu.content['cles_de_federation']]
if not individus:
return
for message in SignalementChangementAdresse.par_application(application, individus,
meta=meta,
transaction=transaction):
yield message
def pour_relation(self, relation):
# On crée la relation si les deux individus sont fédérés, sinon on envoie simplement un
# changement d'adresse
assert relation.schema.slug == utils.UNION_REL
self.relation_situation_familiale(relation.left, relation.right, relation)
self.mise_a_jour_adresse(relation.left)
self.mise_a_jour_adresse(relation.right)
# mettre à jour les adresses des enfants fédérés
for enfant in utils.enfants_couple(relation):
if self.application_id in enfant.content['cles_de_federation']:
self.mise_a_jour_adresse(enfant)
class DeclarationSeparation(RelationSynchro):
service = 'declaration-separation'
def pour_relation(self, relation):
assert relation.schema.slug == utils.UNION_REL
self.suppression_relation(relation)
class SuppressionResponsabiliteEnfant(RelationSynchro):
service = 'suppression-responsabilite-enfant'
def pour_relation(self, relation):
assert relation.schema.slug == utils.RESPONSABILITE_LEGALE_REL
self.suppression_relation(relation)
self.mise_a_jour_adresse(relation.right)
class PassageALaMajorite(RelationSynchro):
# PassageALaMajorite does not send fragments immediatelly, it's special
service = 'passage-majorite'
lock_individus = True
@classmethod
def create(cls, application, individu, relations, meta=None):
individus = set([individu])
for relation in relations:
individus.add(relation.left)
individus.add(relation.right)
self = super(RelationSynchro, cls).create(application, individus, meta=meta)
self.mise_a_jour_adresse(individu)
for relation in relations:
assert cls.condition(application, relation)
self.suppression_relation(relation)
return self
@classmethod
def condition(self, application, relation):
left_cond = application in relation.left.content['cles_de_federation']
right_cond = application in relation.right.content['cles_de_federation']
return left_cond and right_cond
@classmethod
def par_application(cls, application, individu, relations, meta=None, transaction=None):
relations = [relation for relation in relations if cls.condition(application, relation)]
action = cls.create(application, individu, relations, meta=meta)
# PassageALaMajorite does not send fragments immediatelly, it's special
Job.create(action, do_later=True, transaction=transaction)
@classmethod
def pour_chaque_application(cls, individu, relations, meta=None, transaction=None):
def helper():
for application in utils.get_applications(rsu_ws_url=True):
if application not in individu.content['cles_de_federation']:
continue
cls.par_application(application, individu, relations,
meta=meta, transaction=transaction)
helper()
@property
def journal_message(self):
app_name = self.application['name']
return u'Passage à la majorité sur %s' % app_name