Skip to content

Commit 46021ac

Browse files
committed
NS initiatives – 4 lang categories
1 parent 197783f commit 46021ac

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed

api/management/commands/ingest_ns_initiatives.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
from sentry_sdk.crons import monitor
66

77
from api.logger import logger
8-
from api.models import Country, CronJob, CronJobStatus, NSDInitiatives
8+
from api.models import (
9+
Country,
10+
CronJob,
11+
CronJobStatus,
12+
NSDInitiatives,
13+
NSDInitiativesCategory,
14+
)
915
from main.sentry import SentryMonitor
1016

1117
DEFAULT_COUNTRY_ID = 289 # IFRC
@@ -32,7 +38,6 @@ def get_defaults(element, country, funding_period, lang):
3238
"fund_type": (
3339
f"{element.get('Fund')}{element.get('FundingType')}" if element.get("FundingType") else element.get("Fund")
3440
),
35-
"categories": element.get("Categories"),
3641
"allocation": element.get("AllocationInCHF"),
3742
"funding_period": funding_period,
3843
"translation_module_original_language": lang,
@@ -133,6 +138,26 @@ def handle(self, *args, **kwargs):
133138
created_ns_initiatives_pk.append(ni.pk)
134139
logger.warning(f"Created non-EN entry: {remote_id} / {lang}")
135140

141+
# Handle categories (language-aware)
142+
raw_categories = element.get("Categories") or []
143+
if isinstance(raw_categories, (list, tuple)):
144+
cat_objs = []
145+
for c in raw_categories:
146+
if not c:
147+
continue
148+
name = str(c).strip()
149+
if not name:
150+
continue
151+
# Create / fetch category for this specific language
152+
obj, _ = NSDInitiativesCategory.objects.get_or_create(
153+
name=name,
154+
lang=lang,
155+
)
156+
cat_objs.append(obj)
157+
158+
if cat_objs:
159+
ni.categories.add(*cat_objs)
160+
136161
# Remove old entries not present in the latest fetch
137162
NSDInitiatives.objects.exclude(id__in=created_ns_initiatives_pk).delete()
138163

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated by Django 4.2.19 on 2025-09-25 12:15
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0225_nsdinitiatives_nsia_risk_ar_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='NSDInitiativesCategory',
15+
fields=[
16+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('name', models.CharField(max_length=255, unique=True, verbose_name='Name')),
18+
],
19+
options={
20+
'verbose_name': 'NSD initiative category',
21+
'verbose_name_plural': 'NSD initiative categories',
22+
'ordering': ('name',),
23+
},
24+
),
25+
migrations.RemoveField(
26+
model_name='nsdinitiatives',
27+
name='categories',
28+
),
29+
migrations.AddField(
30+
model_name='nsdinitiatives',
31+
name='categories',
32+
field=models.ManyToManyField(blank=True, related_name='initiatives', to='api.nsdinitiativescategory', verbose_name='Funding categories'),
33+
),
34+
migrations.AddField(
35+
model_name='nsdinitiativescategory',
36+
name='lang',
37+
field=models.CharField(choices=[('en', 'English'), ('es', 'Spanish'), ('fr', 'French'), ('ar', 'Arabic')], db_index=True, default='en', max_length=5),
38+
),
39+
migrations.AlterField(
40+
model_name='nsdinitiativescategory',
41+
name='name',
42+
field=models.CharField(max_length=255),
43+
),
44+
migrations.AlterUniqueTogether(
45+
name='nsdinitiativescategory',
46+
unique_together={('name', 'lang')},
47+
),
48+
]

api/models.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,41 @@ class CountryOrganizationalCapacity(models.Model):
419419
financial_capacity = models.TextField(verbose_name=_("Financial Capacity"), null=True, blank=True)
420420

421421

422+
LANG_CHOICES = (
423+
("en", "English"),
424+
("es", "Spanish"),
425+
("fr", "French"),
426+
("ar", "Arabic"),
427+
)
428+
429+
430+
class NSDInitiativesCategory(models.Model):
431+
name = models.CharField(max_length=255)
432+
lang = models.CharField(max_length=5, choices=LANG_CHOICES, db_index=True, default="en")
433+
434+
class Meta:
435+
ordering = ("name",)
436+
unique_together = ("name", "lang")
437+
verbose_name = _("NSD initiative category")
438+
verbose_name_plural = _("NSD initiative categories")
439+
440+
def __str__(self):
441+
return f"{self.name} ({self.lang})"
442+
443+
422444
class NSDInitiatives(models.Model):
423445
country = models.ForeignKey(Country, verbose_name=_("Country"), on_delete=models.CASCADE)
424446
title = models.CharField(verbose_name=_("Title"), max_length=255)
425447
fund_type = models.CharField(verbose_name=_("Fund Type"), max_length=255)
426448
allocation = models.IntegerField(verbose_name=_("Allocation in CHF"))
427449
year = models.CharField(verbose_name=_("Year"), max_length=20)
428450
funding_period = models.IntegerField(verbose_name=_("Funding Period in Month"))
429-
categories = ArrayField(models.CharField(max_length=255), verbose_name=_("Funding categories"), default=list, null=True)
451+
categories = models.ManyToManyField(
452+
NSDInitiativesCategory,
453+
blank=True,
454+
related_name="initiatives",
455+
verbose_name=_("Funding categories"),
456+
)
430457
remote_id = models.IntegerField(db_index=True, unique=True, null=True, blank=True)
431458
nsia_risk = models.CharField(verbose_name=_("NSIA Risk"), max_length=30, null=True, blank=True)
432459

api/serializers.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,20 @@ class Meta:
657657
fields = ("id", "first_name", "last_name", "position")
658658

659659

660-
class NSDInitiativesSerialzier(ModelSerializer):
660+
class NSDInitiativesSerializer(ModelSerializer):
661+
categories = serializers.SerializerMethodField()
662+
661663
class Meta:
662664
model = NSDInitiatives
663665
fields = "__all__"
664666

667+
@staticmethod
668+
def get_categories(obj):
669+
out = {}
670+
for lang, name in obj.categories.values_list("lang", "name"):
671+
out.setdefault(lang, []).append(name)
672+
return out
673+
665674

666675
class CountryCapacityStrengtheningSerializer(ModelSerializer):
667676
assessment_type_display = serializers.CharField(read_only=True, source="get_assessment_type_display")
@@ -691,7 +700,7 @@ class CountryRelationSerializer(ModelSerializer):
691700
centroid = serializers.SerializerMethodField()
692701
regions_details = RegionSerializer(source="region", read_only=True)
693702
directory = CountryDirectorySerializer(source="countrydirectory_set", read_only=True, many=True)
694-
initiatives = NSDInitiativesSerialzier(source="nsdinitiatives_set", read_only=True, many=True)
703+
initiatives = NSDInitiativesSerializer(source="nsdinitiatives_set", read_only=True, many=True)
695704
capacity = CountryCapacityStrengtheningSerializer(source="countrycapacitystrengthening_set", read_only=True, many=True)
696705
organizational_capacity = CountryOrganizationalCapacitySerializer(
697706
source="countryorganizationalcapacity",
@@ -2521,6 +2530,7 @@ def create(self, validated_data):
25212530
else:
25222531
title = "Export"
25232532
user = self.context["request"].user
2533+
25242534
if export_type == Export.ExportType.PER:
25252535
validated_data["url"] = f"{settings.GO_WEB_INTERNAL_URL}/countries/{country_id}/{export_type}/{export_id}/export/"
25262536
else:

0 commit comments

Comments
 (0)