Skip to content

Commit 038d7f8

Browse files
Merge pull request #2670 from IFRCGo/feature/molnix-appraisals-and-resp-cap
Sync Molnix appraisals and appraisers
2 parents b28615a + 4c5b9e0 commit 038d7f8

9 files changed

Lines changed: 1350 additions & 3 deletions

api/management/commands/sync_molnix_appraisals.py

Lines changed: 789 additions & 0 deletions
Large diffs are not rendered by default.

assets

deployments/drf_views.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from drf_spectacular.utils import extend_schema
1616
from openpyxl import Workbook
1717
from rest_framework import viewsets
18-
from rest_framework.authentication import TokenAuthentication
18+
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
1919
from rest_framework.decorators import action
2020
from rest_framework.permissions import IsAuthenticated
2121
from rest_framework.response import Response
@@ -31,7 +31,15 @@
3131
from main.serializers import CsvListMixin
3232
from main.utils import is_tableau
3333

34-
from .filters import EmergencyProjectFilter, ERUOwnerFilter, ProjectFilter
34+
from .filters import (
35+
EmergencyProjectFilter,
36+
ERUOwnerFilter,
37+
MolnixAppraisalFilter,
38+
MolnixAppraiserFilter,
39+
ProjectFilter,
40+
RrmsEventParticipationFilter,
41+
RrmsPersonSnapshotFilter,
42+
)
3543
from .models import (
3644
ERU,
3745
EmergencyProject,
@@ -41,13 +49,17 @@
4149
ERUReadiness,
4250
ERUReadinessType,
4351
ERUType,
52+
MolnixAppraisal,
53+
MolnixAppraiser,
4454
OperationTypes,
4555
PartnerSocietyDeployment,
4656
Personnel,
4757
PersonnelDeployment,
4858
ProgrammeTypes,
4959
Project,
5060
RegionalProject,
61+
RrmsEventParticipation,
62+
RrmsPersonSnapshot,
5163
Sector,
5264
Statuses,
5365
)
@@ -65,6 +77,8 @@
6577
GlobalProjectNSOngoingProjectsStatsSerializer,
6678
GlobalProjectOverviewSerializer,
6779
MiniERUReadinessTypeSerializer,
80+
MolnixAppraisalSerializer,
81+
MolnixAppraiserSerializer,
6882
PartnerDeploymentSerializer,
6983
PartnerDeploymentTableauSerializer,
7084
PersonnelCsvSerializer,
@@ -77,6 +91,8 @@
7791
ProjectRegionOverviewSerializer,
7892
ProjectSerializer,
7993
RegionalProjectSerializer,
94+
RrmsEventParticipationSerializer,
95+
RrmsPersonSnapshotSerializer,
8096
)
8197
from .utils import get_previous_months
8298

@@ -410,6 +426,56 @@ def get_renderer_context(self):
410426
return context
411427

412428

429+
@extend_schema(
430+
request=None,
431+
responses=MolnixAppraisalSerializer(many=True),
432+
)
433+
class MolnixAppraisalViewset(viewsets.ReadOnlyModelViewSet):
434+
authentication_classes = (SessionAuthentication, TokenAuthentication)
435+
permission_classes = (IsAuthenticated, DenyGuestUserPermission)
436+
queryset = MolnixAppraisal.objects.all()
437+
serializer_class = MolnixAppraisalSerializer
438+
filterset_class = MolnixAppraisalFilter
439+
ordering_fields = ("updated_at", "created_at", "molnix_id", "appraised_person_id")
440+
441+
442+
@extend_schema(
443+
request=None,
444+
responses=MolnixAppraiserSerializer(many=True),
445+
)
446+
class MolnixAppraiserViewset(viewsets.ReadOnlyModelViewSet):
447+
authentication_classes = (SessionAuthentication, TokenAuthentication)
448+
permission_classes = (IsAuthenticated, DenyGuestUserPermission)
449+
queryset = MolnixAppraiser.objects.all()
450+
serializer_class = MolnixAppraiserSerializer
451+
filterset_class = MolnixAppraiserFilter
452+
ordering_fields = ("updated_at", "created_at", "molnix_id", "appraisal_molnix_id")
453+
454+
455+
@extend_schema(
456+
request=None,
457+
responses=RrmsPersonSnapshotSerializer(many=True),
458+
)
459+
class RrmsPersonSnapshotViewset(viewsets.ReadOnlyModelViewSet):
460+
authentication_classes = (SessionAuthentication, TokenAuthentication)
461+
permission_classes = (IsAuthenticated, DenyGuestUserPermission)
462+
queryset = RrmsPersonSnapshot.objects.all()
463+
serializer_class = RrmsPersonSnapshotSerializer
464+
filterset_class = RrmsPersonSnapshotFilter
465+
ordering_fields = ("source_updated_at", "person_id")
466+
467+
468+
@extend_schema(
469+
request=None,
470+
responses=RrmsEventParticipationSerializer(many=True),
471+
)
472+
class RrmsEventParticipationViewset(viewsets.ReadOnlyModelViewSet):
473+
queryset = RrmsEventParticipation.objects.all()
474+
serializer_class = RrmsEventParticipationSerializer
475+
filterset_class = RrmsEventParticipationFilter
476+
ordering_fields = ("event_from", "event_to", "event_id", "person_id")
477+
478+
413479
class AggregateDeployments(APIView):
414480
"""
415481
Get aggregated data for personnel deployments

deployments/filters.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
EmergencyProjectActivitySector,
1111
ERUOwner,
1212
ERUType,
13+
MolnixAppraisal,
14+
MolnixAppraiser,
1315
OperationTypes,
1416
ProgrammeTypes,
1517
Project,
18+
RrmsEventParticipation,
19+
RrmsPersonSnapshot,
1620
Sector,
1721
SectorTag,
1822
Statuses,
@@ -161,3 +165,56 @@ def qs(self):
161165
if available is not None:
162166
eru_qs = eru_qs.filter(available=available)
163167
return qs.filter(eru__in=eru_qs).distinct()
168+
169+
170+
class MolnixAppraisalFilter(filters.FilterSet):
171+
appraised_person_id = filters.NumberFilter(field_name="appraised_person_id", lookup_expr="exact")
172+
molnix_id = filters.NumberFilter(field_name="molnix_id", lookup_expr="exact")
173+
deployment_molnix_id = filters.NumberFilter(field_name="deployment_molnix_id", lookup_expr="exact")
174+
stage = filters.CharFilter(field_name="stage", lookup_expr="exact")
175+
176+
class Meta:
177+
model = MolnixAppraisal
178+
fields = {
179+
"updated_at": ("exact", "gt", "gte", "lt", "lte"),
180+
"created_at": ("exact", "gt", "gte", "lt", "lte"),
181+
}
182+
183+
184+
class MolnixAppraiserFilter(filters.FilterSet):
185+
appraisal_molnix_id = filters.NumberFilter(field_name="appraisal_molnix_id", lookup_expr="exact")
186+
person_id = filters.NumberFilter(field_name="person_id", lookup_expr="exact")
187+
appraiser_type = filters.CharFilter(field_name="appraiser_type", lookup_expr="exact")
188+
189+
class Meta:
190+
model = MolnixAppraiser
191+
fields = {
192+
"updated_at": ("exact", "gt", "gte", "lt", "lte"),
193+
"created_at": ("exact", "gt", "gte", "lt", "lte"),
194+
}
195+
196+
197+
class RrmsPersonSnapshotFilter(filters.FilterSet):
198+
person_id = filters.NumberFilter(field_name="person_id", lookup_expr="exact")
199+
organization_id = filters.NumberFilter(field_name="organization_id", lookup_expr="exact")
200+
person_status = filters.CharFilter(field_name="person_status", lookup_expr="exact")
201+
202+
class Meta:
203+
model = RrmsPersonSnapshot
204+
fields = {
205+
"source_updated_at": ("exact", "gt", "gte", "lt", "lte"),
206+
}
207+
208+
209+
class RrmsEventParticipationFilter(filters.FilterSet):
210+
event_id = filters.NumberFilter(field_name="event_id", lookup_expr="exact")
211+
person_id = filters.NumberFilter(field_name="person_id", lookup_expr="exact")
212+
event_person_role = filters.CharFilter(field_name="event_person_role", lookup_expr="exact")
213+
event_type = filters.CharFilter(field_name="event_type", lookup_expr="exact")
214+
215+
class Meta:
216+
model = RrmsEventParticipation
217+
fields = {
218+
"event_from": ("exact", "gt", "gte", "lt", "lte"),
219+
"event_to": ("exact", "gt", "gte", "lt", "lte"),
220+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Generated by Django 4.2.29 on 2026-04-10 11:39
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('deployments', '0093_sector_title_ar_sector_title_en_sector_title_es_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='MolnixAppraisal',
16+
fields=[
17+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('molnix_id', models.BigIntegerField(unique=True)),
19+
('target_id', models.BigIntegerField()),
20+
('deployment_molnix_id', models.BigIntegerField(blank=True, null=True)),
21+
('stage', models.CharField(blank=True, max_length=64, null=True)),
22+
('appraisers_count', models.IntegerField(blank=True, null=True)),
23+
('score', models.DecimalField(blank=True, decimal_places=3, max_digits=7, null=True)),
24+
('deployment_country_id', models.IntegerField(blank=True, null=True)),
25+
('deployment_start', models.DateTimeField(blank=True, null=True)),
26+
('deployment_end', models.DateTimeField(blank=True, null=True)),
27+
('deployment_title', models.CharField(blank=True, max_length=255, null=True)),
28+
('sending_organization_id', models.BigIntegerField(blank=True, null=True)),
29+
('receiving_organization_id', models.BigIntegerField(blank=True, null=True)),
30+
('deployment_tags_json', models.JSONField(blank=True, null=True)),
31+
('competencies_json', models.JSONField(blank=True, null=True)),
32+
('created_at', models.DateTimeField(blank=True, null=True)),
33+
('updated_at', models.DateTimeField(blank=True, null=True)),
34+
],
35+
options={
36+
'verbose_name': 'Molnix Appraisal',
37+
'verbose_name_plural': 'Molnix Appraisals',
38+
},
39+
),
40+
migrations.CreateModel(
41+
name='MolnixAppraiser',
42+
fields=[
43+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
44+
('molnix_id', models.BigIntegerField(unique=True)),
45+
('appraisal_molnix_id', models.BigIntegerField()),
46+
('appraiser_type', models.CharField(blank=True, max_length=32, null=True)),
47+
('person_id', models.BigIntegerField(blank=True, null=True)),
48+
('required', models.BooleanField(blank=True, null=True)),
49+
('notified_at', models.DateTimeField(blank=True, null=True)),
50+
('completed_at', models.DateTimeField(blank=True, null=True)),
51+
('created_at', models.DateTimeField(blank=True, null=True)),
52+
('updated_at', models.DateTimeField(blank=True, null=True)),
53+
],
54+
options={
55+
'verbose_name': 'Molnix Appraiser',
56+
'verbose_name_plural': 'Molnix Appraisers',
57+
},
58+
),
59+
migrations.CreateModel(
60+
name='RrmsPersonSnapshot',
61+
fields=[
62+
('person_id', models.BigIntegerField(primary_key=True, serialize=False)),
63+
('person_status', models.CharField(blank=True, max_length=32, null=True)),
64+
('sex', models.CharField(blank=True, max_length=32, null=True)),
65+
('current_availability', models.CharField(blank=True, max_length=64, null=True)),
66+
('outofscope', models.BooleanField(blank=True, null=True)),
67+
('organization_id', models.BigIntegerField(blank=True, null=True)),
68+
('organization_name', models.CharField(blank=True, max_length=255, null=True)),
69+
('roles_json', models.JSONField(blank=True, null=True)),
70+
('languages_json', models.JSONField(blank=True, null=True)),
71+
('tags_json', models.JSONField(blank=True, null=True)),
72+
('source_updated_at', models.DateTimeField(blank=True, null=True)),
73+
('personnel', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rrms_person_snapshots', to='deployments.personnel')),
74+
],
75+
options={
76+
'verbose_name': 'RRMS Person Snapshot',
77+
'verbose_name_plural': 'RRMS Person Snapshots',
78+
'db_table': 'rrms_person_snapshot',
79+
},
80+
),
81+
migrations.CreateModel(
82+
name='RrmsEventParticipation',
83+
fields=[
84+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
85+
('event_id', models.BigIntegerField()),
86+
('event_name', models.CharField(blank=True, max_length=255, null=True)),
87+
('person_id', models.BigIntegerField()),
88+
('event_person_role', models.CharField(blank=True, max_length=128, null=True)),
89+
('event_type', models.CharField(blank=True, max_length=128, null=True)),
90+
('event_scale_type', models.CharField(blank=True, max_length=128, null=True)),
91+
('event_from', models.DateTimeField(blank=True, null=True)),
92+
('event_to', models.DateTimeField(blank=True, null=True)),
93+
('participant_start', models.DateTimeField(blank=True, null=True)),
94+
('participant_end', models.DateTimeField(blank=True, null=True)),
95+
('requested', models.BooleanField(blank=True, null=True)),
96+
('event_organization_id', models.BigIntegerField(blank=True, null=True)),
97+
('event_organization_name', models.CharField(blank=True, max_length=255, null=True)),
98+
('venue', models.CharField(blank=True, max_length=255, null=True)),
99+
('tags_json', models.JSONField(blank=True, null=True)),
100+
],
101+
options={
102+
'verbose_name': 'RRMS Event Participation',
103+
'verbose_name_plural': 'RRMS Event Participation',
104+
'db_table': 'rrms_event_participation',
105+
'indexes': [models.Index(fields=['event_id'], name='rrms_event_id_idx'), models.Index(fields=['person_id'], name='rrms_event_person_idx')],
106+
},
107+
),
108+
migrations.AddConstraint(
109+
model_name='rrmseventparticipation',
110+
constraint=models.UniqueConstraint(fields=('event_id', 'person_id', 'event_person_role'), name='rrms_event_person_role_uniq'),
111+
),
112+
migrations.AddField(
113+
model_name='molnixappraiser',
114+
name='appraisal',
115+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='appraisers', to='deployments.molnixappraisal'),
116+
),
117+
migrations.AddField(
118+
model_name='molnixappraiser',
119+
name='personnel',
120+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='molnix_appraisers', to='deployments.personnel'),
121+
),
122+
migrations.AddField(
123+
model_name='molnixappraisal',
124+
name='personnel',
125+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='molnix_appraisals', to='deployments.personnel'),
126+
),
127+
migrations.AddIndex(
128+
model_name='rrmspersonsnapshot',
129+
index=models.Index(fields=['organization_id'], name='rrms_person_org_idx'),
130+
),
131+
migrations.AddIndex(
132+
model_name='rrmspersonsnapshot',
133+
index=models.Index(fields=['personnel'], name='rrms_personnel_idx'),
134+
),
135+
migrations.AddIndex(
136+
model_name='molnixappraiser',
137+
index=models.Index(fields=['appraisal_molnix_id'], name='molnix_appr_mol_idx'),
138+
),
139+
migrations.AddIndex(
140+
model_name='molnixappraiser',
141+
index=models.Index(fields=['appraisal'], name='molnix_appr_idx'),
142+
),
143+
migrations.AddIndex(
144+
model_name='molnixappraiser',
145+
index=models.Index(fields=['person_id'], name='molnix_appr_person_idx'),
146+
),
147+
migrations.AddIndex(
148+
model_name='molnixappraiser',
149+
index=models.Index(fields=['personnel'], name='molnix_appr_personnel_idx'),
150+
),
151+
migrations.AddIndex(
152+
model_name='molnixappraisal',
153+
index=models.Index(fields=['target_id'], name='molnix_app_target_idx'),
154+
),
155+
migrations.AddIndex(
156+
model_name='molnixappraisal',
157+
index=models.Index(fields=['personnel'], name='molnix_app_personnel_idx'),
158+
),
159+
migrations.AddIndex(
160+
model_name='molnixappraisal',
161+
index=models.Index(fields=['updated_at'], name='molnix_app_updated_idx'),
162+
),
163+
]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 4.2.29 on 2026-05-11 00:00
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("deployments", "0094_erureadinesstype_ns_contribution"),
10+
("deployments", "0094_molnixappraisal_molnixappraiser_rrmspersonsnapshot_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="molnixappraisal",
16+
name="appraised_person_id",
17+
field=models.BigIntegerField(blank=True, null=True),
18+
),
19+
migrations.AddIndex(
20+
model_name="molnixappraisal",
21+
index=models.Index(fields=["appraised_person_id"], name="molnix_appraised_person_idx"),
22+
),
23+
migrations.RemoveConstraint(
24+
model_name="rrmseventparticipation",
25+
name="rrms_event_person_role_uniq",
26+
),
27+
]

0 commit comments

Comments
 (0)