11import logging
2+ from collections import OrderedDict
3+
4+ from openpyxl .cell .cell import ILLEGAL_CHARACTERS_RE
25
36from partner_programs .models import PartnerProgramUserProfile
47from project_rates .models import Criteria , ProjectScore
58
6-
79logger = logging .getLogger ()
810
911
@@ -23,16 +25,14 @@ class ProjectScoreDataPreparer:
2325 "Класс_курс" : "ОШИБКА" ,
2426 }
2527
26- EXPERT_ERROR_FIELDS = {
27- "Фамилия эксперта" : "ОШИБКА"
28- }
28+ EXPERT_ERROR_FIELDS = {"Фамилия эксперта" : "ОШИБКА" }
2929
3030 def __init__ (
3131 self ,
3232 user_profiles : dict [int , PartnerProgramUserProfile ],
3333 scores : dict [int , list [ProjectScore ]],
3434 project_id : int ,
35- program_id : int
35+ program_id : int ,
3636 ):
3737 self ._project_id = project_id
3838 self ._user_profiles = user_profiles
@@ -41,35 +41,54 @@ def __init__(
4141
4242 def get_project_user_info (self ) -> dict [str , str ]:
4343 try :
44- user_program_profile : PartnerProgramUserProfile = self ._user_profiles .get (self ._project_id )
45- user_program_profile_json : dict = user_program_profile .partner_program_data if user_program_profile else {}
44+ user_program_profile : PartnerProgramUserProfile = self ._user_profiles .get (
45+ self ._project_id
46+ )
47+ user_program_profile_json : dict = (
48+ user_program_profile .partner_program_data if user_program_profile else {}
49+ )
4650
4751 user_info : dict [str , str ] = {
48- "Фамилия" : user_program_profile .user .last_name if user_program_profile else '' ,
49- "Имя" : user_program_profile .user .first_name if user_program_profile else '' ,
50- "Отчество" : user_program_profile .user .patronymic if user_program_profile else '' ,
52+ "Фамилия" : (
53+ user_program_profile .user .last_name if user_program_profile else ""
54+ ),
55+ "Имя" : (
56+ user_program_profile .user .first_name if user_program_profile else ""
57+ ),
58+ "Отчество" : (
59+ user_program_profile .user .patronymic if user_program_profile else ""
60+ ),
5161 "Email" : (
52- user_program_profile_json .get ('email' ) if user_program_profile_json .get ('email' )
62+ user_program_profile_json .get ("email" )
63+ if user_program_profile_json .get ("email" )
5364 else user_program_profile .user .email
5465 ),
55- "Регион_РФ" : user_program_profile_json .get ('region' , '' ),
56- "Учебное_заведение" : user_program_profile_json .get ('education_type' , '' ),
57- "Название_учебного_заведения" : user_program_profile_json .get ('institution_name' , '' ),
58- "Класс_курс" : user_program_profile_json .get ('class_course' , '' ),
66+ "Регион_РФ" : user_program_profile_json .get ("region" , "" ),
67+ "Учебное_заведение" : user_program_profile_json .get ("education_type" , "" ),
68+ "Название_учебного_заведения" : user_program_profile_json .get (
69+ "institution_name" , ""
70+ ),
71+ "Класс_курс" : user_program_profile_json .get ("class_course" , "" ),
5972 }
6073 return user_info
6174 except Exception as e :
62- logger .error (f"Prepare export rates data about user error: { str (e )} " , exc_info = True )
75+ logger .error (
76+ f"Prepare export rates data about user error: { str (e )} " , exc_info = True
77+ )
6378 return self .USER_ERROR_FIELDS
6479
6580 def get_project_expert_info (self ) -> dict [str , str ]:
6681 try :
6782 project_scores : list [ProjectScore ] = self ._scores .get (self ._project_id , [])
6883 first_score = project_scores [0 ] if project_scores else None
69- expert_last_name : dict [str , str ] = {"Фамилия эксперта" : first_score .user .last_name if first_score else '' }
84+ expert_last_name : dict [str , str ] = {
85+ "Фамилия эксперта" : first_score .user .last_name if first_score else ""
86+ }
7087 return expert_last_name
7188 except Exception as e :
72- logger .error (f"Prepare export rates data about expert error: { str (e )} " , exc_info = True )
89+ logger .error (
90+ f"Prepare export rates data about expert error: { str (e )} " , exc_info = True
91+ )
7392 return self .EXPERT_ERROR_FIELDS
7493
7594 def get_project_scores_info (self ) -> dict [str , str ]:
@@ -78,16 +97,139 @@ def get_project_scores_info(self) -> dict[str, str]:
7897 project_scores : list [ProjectScore ] = self ._scores .get (self ._project_id , [])
7998 score_info_with_out_comment : dict [str , str ] = {
8099 score .criteria .name : score .value
81- for score in project_scores if score .criteria .name != "Комментарий"
100+ for score in project_scores
101+ if score .criteria .name != "Комментарий"
82102 }
83103 project_scores_dict .update (score_info_with_out_comment )
84- comment = next ((score for score in project_scores if score .criteria .name == "Комментарий" ), None )
104+ comment = next (
105+ (
106+ score
107+ for score in project_scores
108+ if score .criteria .name == "Комментарий"
109+ ),
110+ None ,
111+ )
85112 if comment is not None :
86113 project_scores_dict ["Комментарий" ] = comment .value
87114 return project_scores_dict
88115 except Exception as e :
89- logger .error (f"Prepare export rates data about project_scores error: { str (e )} " , exc_info = True )
116+ logger .error (
117+ f"Prepare export rates data about project_scores error: { str (e )} " ,
118+ exc_info = True ,
119+ )
90120 return {
91121 criteria .name : "ОШИБКА"
92- for criteria in Criteria .objects .filter (partner_program__id = self ._program_id )
122+ for criteria in Criteria .objects .filter (
123+ partner_program__id = self ._program_id
124+ )
93125 }
126+
127+
128+ BASE_COLUMNS = [
129+ ("row_number" , "№ п/п" ),
130+ ("project_name" , "Название проекта" ),
131+ ("project_description" , "Описание проекта" ),
132+ ("project_region" , "Регион проекта" ),
133+ ("project_presentation" , "Ссылка на презентацию" ),
134+ ("team_size" , "Количество человек в команде" ),
135+ ("leader_full_name" , "Имя фамилия лидера" ),
136+ ]
137+ EXCEL_CELL_MAX = 32767 # лимит символов в ячейке Excel
138+
139+
140+ def sanitize_excel_value (value ):
141+ """
142+ Приводит значение к безопасному для openpyxl виду:
143+ - None -> ""
144+ - для строк: вычищает запрещённые символы, нормализует переносы строк,
145+ и обрезает до лимита Excel (32767).
146+ - для чисел/булевых оставляет как есть.
147+ """
148+ if value is None :
149+ return ""
150+
151+ if isinstance (value , (int , float , bool )):
152+ return value
153+
154+ text = str (value )
155+ text = text .replace ("\r \n " , "\n " ).replace ("\r " , "\n " )
156+ text = ILLEGAL_CHARACTERS_RE .sub (" " , text )
157+
158+ if len (text ) > EXCEL_CELL_MAX :
159+ text = text [: EXCEL_CELL_MAX - 3 ] + "..."
160+
161+ return text
162+
163+
164+ def _leader_full_name (user ):
165+ if not user :
166+ return ""
167+ if hasattr (user , "get_full_name" ) and callable (user .get_full_name ):
168+ full = user .get_full_name ()
169+ if full :
170+ return full
171+ first = getattr (user , "first_name" , "" ) or ""
172+ last = getattr (user , "last_name" , "" ) or ""
173+ return (first + " " + last ).strip () or getattr (user , "username" , "" ) or str (user .pk )
174+
175+
176+ def _calc_team_size (project ):
177+ try :
178+ if hasattr (project , "get_collaborators_user_list" ):
179+ return 1 + len (project .get_collaborators_user_list ())
180+ if hasattr (project , "collaborator_set" ):
181+ return 1 + project .collaborator_set .count ()
182+ except Exception :
183+ pass
184+ return 1
185+
186+
187+ def build_program_field_columns (program ) -> list [tuple [str , str ]]:
188+ program_fields = program .fields .all ().order_by ("pk" )
189+ return [
190+ (f"name:{ program_field .name } " , program_field .label )
191+ for program_field in program_fields
192+ ]
193+
194+
195+ def row_dict_for_link (
196+ program_project_link ,
197+ extra_field_keys_order : list [str ],
198+ row_number : int ,
199+ ) -> OrderedDict :
200+ """
201+ program_project_link: PartnerProgramProject
202+ extra_field_keys_order: список псевдоключей "name:<field.name>" в нужном порядке
203+ row_number: порядковый номер строки в Excel (начиная с 1)
204+ """
205+ project = program_project_link .project
206+ row = OrderedDict ()
207+
208+ row ["row_number" ] = row_number
209+
210+ row ["project_name" ] = project .name or ""
211+ row ["project_description" ] = project .description or ""
212+ row ["project_region" ] = project .region or ""
213+ row ["project_presentation" ] = project .presentation_address or ""
214+ row ["team_size" ] = _calc_team_size (project )
215+ row ["leader_full_name" ] = _leader_full_name (getattr (project , "leader" , None ))
216+
217+ values_map : dict [str , str ] = {}
218+ prefetched_values = getattr (program_project_link , "_prefetched_field_values" , None )
219+ field_values_iterable = (
220+ prefetched_values
221+ if prefetched_values is not None
222+ else program_project_link .field_values .all ()
223+ )
224+
225+ for field_value in field_values_iterable :
226+ if (
227+ field_value .field .partner_program_id
228+ == program_project_link .partner_program_id
229+ ):
230+ values_map [f"name:{ field_value .field .name } " ] = field_value .get_value ()
231+
232+ for field_key in extra_field_keys_order :
233+ row [field_key ] = values_map .get (field_key , "" )
234+
235+ return row
0 commit comments