@@ -46,6 +46,185 @@ class Meta:
4646 )
4747
4848
49+ class SurgeAlertCsvSerializer (ModelSerializer ):
50+ """CSV-friendly serializer with flattened fields for consistent column count across pages"""
51+
52+ atype_display = serializers .CharField (source = "get_atype_display" , read_only = True )
53+ molnix_status_display = serializers .CharField (source = "get_molnix_status_display" , read_only = True )
54+ category_display = serializers .CharField (source = "get_category_display" , read_only = True )
55+
56+ # Flattened country fields
57+ country_id = serializers .IntegerField (source = "country.id" , read_only = True )
58+ country_name = serializers .CharField (source = "country.name" , read_only = True )
59+ country_iso = serializers .CharField (source = "country.iso" , read_only = True )
60+ country_iso3 = serializers .CharField (source = "country.iso3" , read_only = True )
61+ country_society_name = serializers .CharField (source = "country.society_name" , read_only = True )
62+ country_region = serializers .IntegerField (source = "country.region.id" , read_only = True )
63+
64+ # Flattened event fields
65+ event_id = serializers .IntegerField (source = "event.id" , read_only = True )
66+ event_name = serializers .CharField (source = "event.name" , read_only = True )
67+ event_dtype_id = serializers .IntegerField (source = "event.dtype.id" , read_only = True )
68+ event_dtype_name = serializers .CharField (source = "event.dtype.name" , read_only = True )
69+ event_glide = serializers .CharField (source = "event.glide" , read_only = True )
70+ event_disaster_start_date = serializers .DateTimeField (source = "event.disaster_start_date" , read_only = True )
71+ event_auto_generated = serializers .BooleanField (source = "event.auto_generated" , read_only = True )
72+ event_ifrc_severity_level = serializers .IntegerField (source = "event.ifrc_severity_level" , read_only = True )
73+ event_num_affected = serializers .IntegerField (source = "event.num_affected" , read_only = True )
74+ event_parent_event = serializers .IntegerField (source = "event.parent_event.id" , read_only = True )
75+ event_summary = serializers .SerializerMethodField ()
76+ event_tab_one_title = serializers .CharField (source = "event.tab_one_title" , read_only = True )
77+ event_tab_two_title = serializers .CharField (source = "event.tab_two_title" , read_only = True )
78+ event_tab_three_title = serializers .CharField (source = "event.tab_three_title" , read_only = True )
79+ event_translation_module_original_language = serializers .CharField (
80+ source = "event.translation_module_original_language" , read_only = True
81+ )
82+ event_updated_at = serializers .DateTimeField (source = "event.updated_at" , read_only = True )
83+
84+ # Comma-separated lists for many-to-many relationships
85+ event_countries = serializers .SerializerMethodField ()
86+ event_appeals = serializers .SerializerMethodField ()
87+ molnix_tags_names = serializers .SerializerMethodField ()
88+ molnix_tags_ids = serializers .SerializerMethodField ()
89+ molnix_tags_types = serializers .SerializerMethodField ()
90+
91+ class Meta :
92+ model = SurgeAlert
93+ fields = (
94+ "id" ,
95+ "molnix_id" ,
96+ "created_at" ,
97+ "operation" ,
98+ "message" ,
99+ "deployment_needed" ,
100+ "is_private" ,
101+ "atype" ,
102+ "atype_display" ,
103+ "category" ,
104+ "category_display" ,
105+ "molnix_status" ,
106+ "molnix_status_display" ,
107+ "opens" ,
108+ "closes" ,
109+ "start" ,
110+ "end" ,
111+ # Country fields (flattened)
112+ "country_id" ,
113+ "country_name" ,
114+ "country_iso" ,
115+ "country_iso3" ,
116+ "country_society_name" ,
117+ "country_region" ,
118+ # Event fields (flattened)
119+ "event_id" ,
120+ "event_name" ,
121+ "event_dtype_id" ,
122+ "event_dtype_name" ,
123+ "event_glide" ,
124+ "event_disaster_start_date" ,
125+ "event_auto_generated" ,
126+ "event_ifrc_severity_level" ,
127+ "event_num_affected" ,
128+ "event_parent_event" ,
129+ "event_summary" ,
130+ "event_tab_one_title" ,
131+ "event_tab_two_title" ,
132+ "event_tab_three_title" ,
133+ "event_translation_module_original_language" ,
134+ "event_updated_at" ,
135+ "event_countries" ,
136+ "event_appeals" ,
137+ # Molnix tags (comma-separated)
138+ "molnix_tags_names" ,
139+ "molnix_tags_ids" ,
140+ "molnix_tags_types" ,
141+ )
142+
143+ @staticmethod
144+ def get_event_summary (obj ):
145+ """Return HTML-stripped first 300 characters of event summary with empty lines removed"""
146+ if obj .event and obj .event .summary :
147+ import re
148+ from html import unescape
149+
150+ text = obj .event .summary
151+
152+ # Remove Microsoft Office table styles and metadata (before HTML stripping)
153+ # Match content between /* Style Definitions */ and the closing brace
154+ text = re .sub (r"/\*\s*Style Definitions\s*\*/.*?}" , "" , text , flags = re .DOTALL )
155+ # Remove other common MS Office artifacts
156+ text = re .sub (r"Normal\s+0\s+\d+\s+false\s+false\s+false\s+[A-Z-]+(?:\s+[A-Z-]+)*" , "" , text )
157+ text = re .sub (r"table\.MsoNormalTable\s*{[^}]*}" , "" , text , flags = re .DOTALL )
158+
159+ # Strip HTML tags
160+ text = re .sub (r"<[^>]+>" , "" , text )
161+ # Decode HTML entities (é -> é, etc.)
162+ text = unescape (text )
163+ # Remove lines that contain only whitespace or are empty
164+ lines = [line .strip () for line in text .split ("\n " ) if line .strip ()]
165+ # Remove lines that only contain "DISASTER OVERVIEW" or "Summary"
166+ lines = [line for line in lines if line .strip ().upper () != "DISASTER OVERVIEW" and line .strip ().upper () != "SUMMARY" ]
167+ # Join remaining lines with newline
168+ text = "\n " .join (lines )
169+ # Return first 300 characters
170+ return text [:300 ]
171+ return ""
172+
173+ @staticmethod
174+ def get_event_countries (obj ):
175+ """Return structured list of countries with name, fdrs, iso, iso3 (separated by |)"""
176+ if obj .event and obj .event .countries .exists ():
177+ country_data = []
178+ for country in obj .event .countries .all ():
179+ # Format: name;fdrs;iso;iso3
180+ parts = [country .name or "" , country .fdrs or "" , country .iso or "" , country .iso3 or "" ]
181+ country_data .append (";" .join (parts ))
182+ return " | " .join (country_data )
183+ return ""
184+
185+ @staticmethod
186+ def get_event_appeals (obj ):
187+ """Return structured list of appeals with all details (separated by |)
188+ in a format code;amount_funded;amount_requested;atype;atype_display;start_date;end_date;
189+ num_beneficiaries;sector;status_display
190+ """
191+ if obj .event :
192+ appeals = obj .event .appeals .all ()
193+ if appeals :
194+ appeal_data = []
195+ for appeal in appeals :
196+ parts = [
197+ appeal .code or "" ,
198+ str (appeal .amount_funded ) if appeal .amount_funded is not None else "" ,
199+ str (appeal .amount_requested ) if appeal .amount_requested is not None else "" ,
200+ str (appeal .atype ) if appeal .atype is not None else "" ,
201+ appeal .get_atype_display () if hasattr (appeal , "get_atype_display" ) else "" ,
202+ str (appeal .start_date ) if appeal .start_date else "" ,
203+ str (appeal .end_date ) if appeal .end_date else "" ,
204+ str (appeal .num_beneficiaries ) if appeal .num_beneficiaries is not None else "" ,
205+ appeal .sector or "" ,
206+ appeal .get_status_display () if hasattr (appeal , "get_status_display" ) else "" ,
207+ ]
208+ appeal_data .append (";" .join (parts ))
209+ return " | " .join (appeal_data )
210+ return ""
211+
212+ @staticmethod
213+ def get_molnix_tags_names (obj ):
214+ """Return comma-separated list of molnix tag names"""
215+ return ", " .join ([tag .name for tag in obj .molnix_tags .all ()])
216+
217+ @staticmethod
218+ def get_molnix_tags_ids (obj ):
219+ """Return comma-separated list of molnix tag IDs"""
220+ return ", " .join ([str (tag .molnix_id ) for tag in obj .molnix_tags .all ()])
221+
222+ @staticmethod
223+ def get_molnix_tags_types (obj ):
224+ """Return comma-separated list of molnix tag types"""
225+ return ", " .join ([tag .tag_type for tag in obj .molnix_tags .all ()])
226+
227+
49228# class UnauthenticatedSurgeAlertSerializer(ModelSerializer):
50229# event = MiniEventSerializer()
51230# atype_display = serializers.CharField(source='get_atype_display', read_only=True)
0 commit comments