-
Notifications
You must be signed in to change notification settings - Fork 3
/
models.py
150 lines (123 loc) · 5.71 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from sqlalchemy import event, or_
from dataservice.extensions import db
from dataservice.api.common.model import Base, KfId
from dataservice.api.participant.models import Participant
REVERSE_RELS = {
'mother': 'Child',
'father': 'Child',
'sibling': 'Sibling'
}
class FamilyRelationship(db.Model, Base):
"""
Represents a relationship between two family members.
The relationship table represents a directed graph. One or more
relationships may exist between any two participants.
(P1 -> P2 is different than P2 -> P1)
:param kf_id: Primary key given by the Kid's First DCC
:param created_at: Time of object creation
:param modified_at: Last time of object modification
:param external_id: Name given to family_relationship by contributor
:param participant1_id: Kids first id of the first Participant in the
relationship
:param participant2_id: Kids first id of the second Participant
in the relationship
:param relationship_type: Text describing the nature of the
relationship (i.e. father, mother, sister, brother)
:param _rel_name: an autogenerated parameter used to ensure that the
relationships are not duplicated and the graph is undirected
:param source_text_notes: Text notes from source describing the
relationship
"""
__tablename__ = 'family_relationship'
__prefix__ = 'FR'
__table_args__ = (db.UniqueConstraint(
'participant1_id', 'participant2_id',
'participant1_to_participant2_relation',
'participant2_to_participant1_relation'),)
external_id = db.Column(db.Text(),
doc='external id used by contributor')
participant1_id = db.Column(
KfId(),
db.ForeignKey('participant.kf_id'),
nullable=False,
doc='kf_id of one participant in the relationship')
participant2_id = db.Column(
KfId(),
db.ForeignKey('participant.kf_id'),
nullable=False,
doc='kf_id of the other participant in the relationship')
participant1_to_participant2_relation = db.Column(db.Text(),
nullable=False)
participant2_to_participant1_relation = db.Column(db.Text())
source_text_notes = db.Column(db.Text(),
doc='Text notes from source describing '
'the relationship')
participant1 = db.relationship(
Participant,
primaryjoin=participant1_id == Participant.kf_id,
backref=db.backref('outgoing_family_relationships',
cascade='all, delete-orphan'))
participant2 = db.relationship(
Participant,
primaryjoin=participant2_id == Participant.kf_id,
backref=db.backref('incoming_family_relationships',
cascade='all, delete-orphan'))
@classmethod
def query_all_relationships(cls, participant_kf_id=None,
model_filter_params=None):
"""
Find all family relationships for a participant
:param participant_kf_id: Kids First ID of the participant
:param model_filter_params: Filter parameters to the query
Given a participant's kf_id, return all of the biological
family relationships of the participant and the relationships
of the participant's family members.
If the participant does not have a family defined, then return
all of the immediate/direct family relationships of the participant.
"""
# Apply model property filter params
if model_filter_params is None:
model_filter_params = {}
q = FamilyRelationship.query.filter_by(**model_filter_params)
# Get family relationships and join with participants
q = q.join(Participant, or_(FamilyRelationship.participant1,
FamilyRelationship.participant2))
# Do this bc query.get() errors out if passed None
if participant_kf_id:
pt = Participant.query.get(participant_kf_id)
family_id = pt.family_id if pt else None
# Use family to get all family relationships in participants family
if family_id:
q = q.filter(Participant.family_id == family_id)
# No family provided, use just family relationships
# to get only immediate family relationships for participant
else:
q = q.filter(or_(
FamilyRelationship.participant1_id == participant_kf_id,
FamilyRelationship.participant2_id == participant_kf_id))
# Don't want duplicates - return unique family relationships
q = q.group_by(FamilyRelationship.kf_id)
return q
def __repr__(self):
return '<{} is {} of {}>'.format(
self.participant1.kf_id,
self.participant1_to_participant2_relation,
self.participant2.kf_id)
@event.listens_for(FamilyRelationship, 'before_update')
@event.listens_for(FamilyRelationship, 'before_insert')
def set_reverse_relation(mapper, connection, target):
"""
Set the appropriate relation types on both relation fields.
Given a relation type, look up the reverse relation in the REVERSE_RELS map
If a relation type is not found in the map do nothing.
"""
# Participant 1 to 2 relation
rel = target.participant1_to_participant2_relation
rev = REVERSE_RELS.get(rel.lower() if rel else None)
if rev:
target.participant2_to_participant1_relation = rev
# Participant 2 to 1 relation
rel = target.participant2_to_participant1_relation
rev = REVERSE_RELS.get(rel.lower() if rel else None)
if rev:
target.participant1_to_participant2_relation = rev