55from sentry_sdk .crons import monitor
66
77from 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+ )
915from main .sentry import SentryMonitor
1016
1117DEFAULT_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 ,
@@ -89,6 +94,10 @@ def handle(self, *args, **kwargs):
8994 updated_remote_ids = set ()
9095 created_ns_initiatives_pk = []
9196
97+ # In-memory alignment helpers
98+ category_by_remote_index = {} # remote_id -> list[NSDInitiativesCategory]
99+ pending_translations = {} # remote_id -> list[(lang, index, label)]
100+
92101 for lang , resp in responses :
93102 for element in resp :
94103 try :
@@ -114,24 +123,99 @@ def handle(self, *args, **kwargs):
114123 for field , value in defaults .items ():
115124 setattr (ni , field , value )
116125 ni .save (update_fields = defaults .keys ())
117- updated_remote_ids .add (remote_id ) # Mark as updated, only for EN entries
126+ updated_remote_ids .add (remote_id )
118127 created_ns_initiatives_pk .append (ni .pk )
119- else :
120- try :
121- # We could use ISO also to identify the entry, but remote_id is more robust
122- ni = NSDInitiatives .objects .get (remote_id = remote_id )
123- setattr (ni , title_field , element .get ("InitiativeTitle" ))
124- setattr (ni , risk_field , element .get ("Risk" ))
125- ni .save (update_fields = [title_field , risk_field ])
126- except NSDInitiatives .DoesNotExist :
127- # Should not happen – only if EN entry is missing
128- ni = NSDInitiatives .objects .create (
129- remote_id = remote_id ,
130- ** defaults ,
131- )
132- added += 1
133- created_ns_initiatives_pk .append (ni .pk )
134- logger .warning (f"Created non-EN entry: { remote_id } / { lang } " )
128+
129+ # Establish baseline categories from EN by index
130+ raw_categories = element .get ("Categories" ) or []
131+ cats_en = []
132+ cat_objs = []
133+ if isinstance (raw_categories , (list , tuple )):
134+ for idx , raw in enumerate (raw_categories ):
135+ label = (raw or "" ).strip ()
136+ if not label :
137+ cats_en .append (None )
138+ continue
139+ # Reuse/create a global category by English label
140+ cat = NSDInitiativesCategory .objects .filter (name_en__iexact = label ).first ()
141+ if not cat :
142+ cat = NSDInitiativesCategory .objects .create (name = label , name_en = label )
143+ else :
144+ # Ensure plain 'name' mirrors EN for convenience
145+ to_update = []
146+ if getattr (cat , "name_en" , None ) != label :
147+ cat .name_en = label
148+ to_update .append ("name_en" )
149+ if getattr (cat , "name" , None ) != label :
150+ cat .name = label
151+ to_update .append ("name" )
152+ if to_update :
153+ cat .save (update_fields = to_update )
154+ cats_en .append (cat )
155+ cat_objs .append (cat )
156+
157+ if cat_objs :
158+ ni .categories .set (cat_objs )
159+ else :
160+ ni .categories .clear ()
161+
162+ # Save baseline for this remote_id
163+ category_by_remote_index [remote_id ] = cats_en
164+
165+ # Apply any pending translations queued before EN
166+ for item in pending_translations .pop (remote_id , []):
167+ plang , pidx , plabel = item
168+ if 0 <= pidx < len (cats_en ) and cats_en [pidx ]:
169+ field = f"name_{ plang } "
170+ cat = cats_en [pidx ]
171+ if getattr (cat , field , None ) != plabel :
172+ setattr (cat , field , plabel )
173+ cat .save (update_fields = [field ])
174+ continue # Done with EN row; go next element
175+
176+ # Non-EN branch: update fields and queue/apply category translations
177+ try :
178+ ni = NSDInitiatives .objects .get (remote_id = remote_id )
179+ setattr (ni , title_field , element .get ("InitiativeTitle" ))
180+ setattr (ni , risk_field , element .get ("Risk" ))
181+ ni .save (update_fields = [title_field , risk_field ])
182+ except NSDInitiatives .DoesNotExist :
183+ # Should not happen – only if EN entry is missing
184+ ni = NSDInitiatives .objects .create (
185+ remote_id = remote_id ,
186+ ** defaults ,
187+ )
188+ added += 1
189+ created_ns_initiatives_pk .append (ni .pk )
190+ logger .warning (f"Created non-EN entry: { remote_id } / { lang } " )
191+
192+ # Align categories by index to the EN baseline for this remote_id
193+ raw_categories = element .get ("Categories" ) or []
194+ if not isinstance (raw_categories , (list , tuple )):
195+ continue
196+
197+ cats_en = category_by_remote_index .get (remote_id )
198+ if cats_en is None :
199+ # Baseline not processed yet; queue these translations
200+ q = pending_translations .setdefault (remote_id , [])
201+ for idx , raw in enumerate (raw_categories ):
202+ label = (raw or "" ).strip ()
203+ if label :
204+ q .append ((lang , idx , label ))
205+ continue
206+
207+ # Baseline exists: update translated fields by index
208+ field = f"name_{ lang } "
209+ for idx , raw in enumerate (raw_categories ):
210+ label = (raw or "" ).strip ()
211+ if not label or idx >= len (cats_en ):
212+ continue
213+ cat = cats_en [idx ]
214+ if not cat :
215+ continue
216+ if getattr (cat , field , None ) != label :
217+ setattr (cat , field , label )
218+ cat .save (update_fields = [field ])
135219
136220 # Remove old entries not present in the latest fetch
137221 NSDInitiatives .objects .exclude (id__in = created_ns_initiatives_pk ).delete ()
0 commit comments