@@ -44,7 +44,6 @@ class EngagementService:
4444 """Engagement management service."""
4545
4646 otherdateformat = '%Y-%m-%d'
47- default_language_code = 'en'
4847 JSONScalar = Union [str , int , float , bool , None ]
4948 JSONValue = Union [
5049 JSONScalar ,
@@ -146,7 +145,9 @@ def _get_scope_options(user_roles, has_team_access):
146145 # If user has VIEW_ENGAGEMENT role, e.g. TEAM MEMBER, return scope options to include assigned
147146 # engagements and public engagements
148147 return EngagementScopeOptions (
149- engagement_status_ids = [Status .Published .value , Status .Closed .value ],
148+ engagement_status_ids = [
149+ Status .Published .value , Status .Closed .value
150+ ],
150151 include_assigned = True ,
151152 )
152153 if Role .VIEW_ASSIGNED_ENGAGEMENTS .value in user_roles :
@@ -175,7 +176,9 @@ def publish_scheduled_engagements():
175176 engagements = EngagementModel .publish_scheduled_engagements_due ()
176177
177178 if not engagements :
178- current_app .logger .info ('There are no engagements scheduled for publication' )
179+ current_app .logger .info (
180+ 'There are no engagements scheduled for publication'
181+ )
179182 return None
180183
181184 current_app .logger .info ('Engagements published: %s' , engagements )
@@ -186,7 +189,9 @@ def publish_scheduled_engagements():
186189 SourceAction .PUBLISHED .value ,
187190 True ,
188191 )
189- current_app .logger .info ('Engagement published added to email queue: %s' , engagement .id )
192+ current_app .logger .info (
193+ 'Engagement published added to email queue: %s' , engagement .id
194+ )
190195 return engagements
191196
192197 @staticmethod
@@ -198,7 +203,9 @@ def create_engagement(request_json: dict):
198203 eng_model = EngagementService ._create_engagement_model (request_json )
199204
200205 if request_json .get ('status_block' ):
201- EngagementService ._create_eng_status_block (eng_model .id , request_json )
206+ EngagementService ._create_eng_status_block (
207+ eng_model .id , request_json
208+ )
202209
203210 # Always sync languages so an English translation row is created immediately.
204211 # If the caller didn't request specific languages, default to English-only.
@@ -257,7 +264,9 @@ def _save_or_update_eng_block(engagement_id, status_block):
257264 survey_status = survey_block .get ('survey_status' )
258265 survey_block_text = survey_block .get ('block_text' )
259266 status_block : EngagementStatusBlockModel = (
260- EngagementStatusBlockModel .get_by_status (engagement_id , survey_status )
267+ EngagementStatusBlockModel .get_by_status (
268+ engagement_id , survey_status
269+ )
261270 )
262271 # If the status block exists, update it. Otherwise, create a new one.
263272 if status_block :
@@ -301,7 +310,9 @@ def _save_or_update_surveys(engagement_id, surveys):
301310 engagement_id = engagement_id ,
302311 is_hidden = survey .get ('is_hidden' , False ),
303312 is_template = survey .get ('is_template' , False ),
304- generate_dashboard = survey .get ('generate_dashboard' , True ),
313+ generate_dashboard = survey .get (
314+ 'generate_dashboard' , True
315+ ),
305316 )
306317 )
307318
@@ -328,7 +339,9 @@ def _validate_and_assign_survey(survey_id: int, engagement_id: int):
328339 raise ValueError ('selected survey does not exist' )
329340
330341 if survey .engagement_id != engagement_id :
331- raise ValueError ('selected survey does not belong to this engagement' )
342+ raise ValueError (
343+ 'selected survey does not belong to this engagement'
344+ )
332345
333346 return survey_id
334347
@@ -357,14 +370,16 @@ def edit_engagement(data: dict):
357370 requested_languages = data .pop ('languages' , None )
358371 suggested_engagements = data .pop ('suggested_engagements' , None )
359372 if suggested_engagements is None :
360- suggested_engagements = data .pop ('suggested_engagements_input' , None )
373+ suggested_engagements = data .pop (
374+ 'suggested_engagements_input' , None )
361375 # Defensive: relationship keys are not valid for SQL UPDATE mappings.
362376 data .pop ('suggested_engagement_links' , None )
363377 epic_fields = 'end_date' in data or 'start_date' in data
364378 selected_survey_id = data .get ('selected_survey_id' , None )
365379 engagement_id = data .get ('id' , None )
366380 authorization .check_auth (
367- one_of_roles = (MembershipType .TEAM_MEMBER .name , Role .EDIT_ENGAGEMENT .value ),
381+ one_of_roles = (MembershipType .TEAM_MEMBER .name ,
382+ Role .EDIT_ENGAGEMENT .value ),
368383 engagement_id = engagement_id ,
369384 )
370385
@@ -379,9 +394,11 @@ def edit_engagement(data: dict):
379394 if data :
380395 if selected_survey_id :
381396 data ['selected_survey_id' ] = \
382- EngagementService ._validate_and_assign_survey (selected_survey_id , engagement_id )
397+ EngagementService ._validate_and_assign_survey (
398+ selected_survey_id , engagement_id )
383399
384- updated_engagement = EngagementModel .edit_engagement (data , commit = False )
400+ updated_engagement = EngagementModel .edit_engagement (
401+ data , commit = False )
385402
386403 if not updated_engagement :
387404 raise ValueError (engagement )
@@ -395,10 +412,12 @@ def edit_engagement(data: dict):
395412 )
396413
397414 if suggested_engagements is not None :
398- EngagementService ._sync_suggestions (engagement , suggested_engagements )
415+ EngagementService ._sync_suggestions (
416+ engagement , suggested_engagements )
399417
400418 if requested_languages is not None :
401- EngagementService ._sync_translation_languages (engagement_id , requested_languages )
419+ EngagementService ._sync_translation_languages (
420+ engagement_id , requested_languages )
402421
403422 db .session .commit ()
404423 except (BusinessException , ValueError , ValidationError , SQLAlchemyError ):
@@ -424,7 +443,8 @@ def _sync_suggestions(engagement: EngagementModel, suggestions_data):
424443 if r .suggested_engagement_id is not None
425444 }
426445
427- normalized = SuggestedEngagementSyncItemSchema (many = True ).load (suggestions_data )
446+ normalized = SuggestedEngagementSyncItemSchema (
447+ many = True ).load (suggestions_data )
428448 now = datetime .now (timezone .utc )
429449 ordered : list [SuggestedEngagementModel ] = []
430450
@@ -460,10 +480,13 @@ def _normalize_language_codes(language_codes: object) -> list[str]:
460480 if not isinstance (language_codes , list ):
461481 raise ValueError ('languages must be a list of language codes' )
462482
483+ default_language_code = EngagementService ._get_default_language_code ()
484+
463485 normalized_codes : list [str ] = []
464486 for code in language_codes :
465487 if not isinstance (code , str ):
466- raise ValueError ('languages must only contain string language codes' )
488+ raise ValueError (
489+ 'languages must only contain string language codes' )
467490
468491 normalized = code .strip ().lower ()
469492 if not normalized :
@@ -472,32 +495,48 @@ def _normalize_language_codes(language_codes: object) -> list[str]:
472495 if normalized not in normalized_codes :
473496 normalized_codes .append (normalized )
474497
475- # English is always implicitly available.
476- if EngagementService . default_language_code not in normalized_codes :
477- normalized_codes .append (EngagementService . default_language_code )
498+ # The default language is always implicitly available.
499+ if default_language_code not in normalized_codes :
500+ normalized_codes .append (default_language_code )
478501
479502 return normalized_codes
480503
504+ @staticmethod
505+ def _get_default_language_code () -> str :
506+ default_language = (
507+ (current_app .config .get ('DEFAULT_LANGUAGE' )
508+ if has_app_context () else None )
509+ or os .getenv ('DEFAULT_LANGUAGE' )
510+ or 'en'
511+ )
512+ return default_language .strip ().lower ()
513+
481514 @staticmethod
482515 def _sync_translation_languages (engagement_id : int , language_codes : object ) -> None :
483- normalized_codes = EngagementService ._normalize_language_codes (language_codes )
484- translation_codes = set (normalized_codes ) # Includes English — all languages get a translation row.
516+ normalized_codes = EngagementService ._normalize_language_codes (
517+ language_codes )
518+ # Includes English — all languages get a translation row.
519+ translation_codes = set (normalized_codes )
485520
486521 requested_languages = (
487- LanguageModel .query .filter (LanguageModel .code .in_ (translation_codes )).all ()
522+ LanguageModel .query .filter (
523+ LanguageModel .code .in_ (translation_codes )).all ()
488524 if translation_codes
489525 else []
490526 )
491527
492528 requested_codes = {language .code for language in requested_languages }
493529 missing_codes = translation_codes - requested_codes
494530 if missing_codes :
495- raise ValueError (f'Invalid language code(s): { ", " .join (sorted (missing_codes ))} ' )
531+ raise ValueError (
532+ f'Invalid language code(s): { ", " .join (sorted (missing_codes ))} ' )
496533
497- requested_language_ids = {language .id for language in requested_languages }
534+ requested_language_ids = {
535+ language .id for language in requested_languages }
498536
499537 existing_translations = (
500- EngagementTranslation .query .filter_by (engagement_id = engagement_id ).all ()
538+ EngagementTranslation .query .filter_by (
539+ engagement_id = engagement_id ).all ()
501540 )
502541 existing_by_language_id = {
503542 translation .language_id : translation for translation in existing_translations
@@ -519,7 +558,8 @@ def _sync_translation_languages(engagement_id: int, language_codes: object) -> N
519558 @staticmethod
520559 def validate_fields (data ):
521560 """Validate all fields."""
522- empty_fields = [not data [field ] for field in ['name' , 'start_date' , 'end_date' ]]
561+ empty_fields = [not data [field ]
562+ for field in ['name' , 'start_date' , 'end_date' ]]
523563
524564 if data ['start_date' ] > data ['end_date' ]:
525565 raise ValueError ('Start date cannot be after End date' )
@@ -565,7 +605,8 @@ def _send_closeout_emails(engagement: EngagementModel) -> None:
565605 @staticmethod
566606 def _render_email_template (engagement : EngagementModel , lang_code ):
567607 template = Template .get_template ('email_engagement_closeout.html' )
568- dashboard_path = EngagementService ._get_dashboard_path (engagement , lang_code )
608+ dashboard_path = EngagementService ._get_dashboard_path (
609+ engagement , lang_code )
569610 engagement_url = notification .get_tenant_site_url (
570611 engagement .tenant_id , dashboard_path
571612 )
@@ -596,7 +637,8 @@ def _get_tenant_name(tenant_id):
596637
597638 @staticmethod
598639 def _get_dashboard_path (engagement : EngagementModel , lang_code ):
599- engagement_slug = EngagementSlugModel .find_by_engagement_id (engagement .id )
640+ engagement_slug = EngagementSlugModel .find_by_engagement_id (
641+ engagement .id )
600642 paths = current_app .config ['PATH_CONFIG' ]
601643 if engagement_slug :
602644 return paths ['ENGAGEMENT' ]['DASHBOARD_SLUG' ].format (
@@ -609,7 +651,8 @@ def _get_dashboard_path(engagement: EngagementModel, lang_code):
609651 @classmethod
610652 def delete (cls , engagement_id : int ):
611653 """Delete an existing engagement and its translations."""
612- one_of_roles = (Role .SUPER_ADMIN .value , Role .UNPUBLISH_ENGAGEMENT .value )
654+ one_of_roles = (Role .SUPER_ADMIN .value ,
655+ Role .UNPUBLISH_ENGAGEMENT .value )
613656 authorization .check_auth (one_of_roles = one_of_roles )
614657
615658 current_env = (
@@ -620,7 +663,8 @@ def delete(cls, engagement_id: int):
620663 ).strip ().lower ()
621664
622665 if current_env in ('prod' , 'production' ):
623- abort (HTTPStatus .FORBIDDEN , 'Cannot delete an engagement in production environment' )
666+ abort (HTTPStatus .FORBIDDEN ,
667+ 'Cannot delete an engagement in production environment' )
624668
625669 engagement = EngagementModel .find_by_id (engagement_id )
626670 if not engagement :
@@ -629,11 +673,13 @@ def delete(cls, engagement_id: int):
629673 raise ValueError ('Cannot delete an engagement that is published' )
630674
631675 try :
632- for translation in (EngagementTranslation .get_available_translation_languages (engagement .id ) or []):
633- EngagementTranslation .delete_engagement_translation (translation .id )
676+ for translation in (EngagementTranslation .get_available_translations (engagement .id ) or []):
677+ EngagementTranslation .delete_engagement_translation (
678+ translation .id )
634679 deleted = EngagementModel .delete_engagement (engagement_id )
635680 except ValueError as exc :
636681 raise ValueError (str (exc )) from exc
637682 except SQLAlchemyError as e :
638- raise RuntimeError ('Database error while deleting engagement' , e ) from e
683+ raise RuntimeError (
684+ 'Database error while deleting engagement' , e ) from e
639685 return {'id' : deleted .id if hasattr (deleted , 'id' ) else engagement_id }
0 commit comments