@@ -28,21 +28,37 @@ class Trajectory(models.Model):
2828 null = True ,
2929 blank = True ,
3030 )
31- mentors = models .ManyToManyField (CustomUser , related_name = "mentored_trajectories" , verbose_name = "Наставники" )
31+ mentors = models .ManyToManyField (
32+ CustomUser , related_name = "mentored_trajectories" , verbose_name = "Наставники"
33+ )
3234 start_date = models .DateField (default = timezone .now , verbose_name = "Дата начала" )
33- duration_months = models .IntegerField (verbose_name = "Количество месяцев" , validators = [validate_positive ])
35+ duration_months = models .IntegerField (
36+ verbose_name = "Количество месяцев" , validators = [validate_positive ]
37+ )
3438 company = models .CharField (max_length = 255 , verbose_name = "Компания" )
3539 background_color = models .CharField (
36- max_length = 7 , default = "#FFFFFF" , verbose_name = "Цвет заднего фона билета" , validators = [validate_hex_color ]
40+ max_length = 7 ,
41+ default = "#FFFFFF" ,
42+ verbose_name = "Цвет заднего фона билета" ,
43+ validators = [validate_hex_color ],
3744 )
3845 button_color = models .CharField (
39- max_length = 7 , default = "#6c27ff" , verbose_name = "Цвет кнопки 'Подробнее'" , validators = [validate_hex_color ]
46+ max_length = 7 ,
47+ default = "#6c27ff" ,
48+ verbose_name = "Цвет кнопки 'Подробнее'" ,
49+ validators = [validate_hex_color ],
4050 )
4151 select_button_color = models .CharField (
42- max_length = 7 , default = "#6c27ff" , verbose_name = "Цвет кнопки 'Выбрать'" , validators = [validate_hex_color ]
52+ max_length = 7 ,
53+ default = "#6c27ff" ,
54+ verbose_name = "Цвет кнопки 'Выбрать'" ,
55+ validators = [validate_hex_color ],
4356 )
4457 text_color = models .CharField (
45- max_length = 7 , default = "#332e2d" , verbose_name = "Цвет текста на билете" , validators = [validate_hex_color ]
58+ max_length = 7 ,
59+ default = "#332e2d" ,
60+ verbose_name = "Цвет текста на билете" ,
61+ validators = [validate_hex_color ],
4662 )
4763
4864 def __str__ (self ):
@@ -59,7 +75,9 @@ class Month(models.Model):
5975 Связана с траекторией и набором навыков.
6076 """
6177
62- trajectory = models .ForeignKey (Trajectory , on_delete = models .CASCADE , related_name = "months" )
78+ trajectory = models .ForeignKey (
79+ Trajectory , on_delete = models .CASCADE , related_name = "months"
80+ )
6381 skills = models .ManyToManyField ("courses.Skill" , verbose_name = "Навыки" )
6482 order = models .PositiveIntegerField (verbose_name = "Порядковый номер месяца" )
6583
@@ -90,7 +108,9 @@ def clean(self):
90108 """
91109 current_months_count = self .trajectory .months .count ()
92110 if current_months_count >= self .trajectory .duration_months :
93- raise ValidationError (f"Нельзя добавить больше { self .trajectory .duration_months } месяцев в эту траекторию." )
111+ raise ValidationError (
112+ f"Нельзя добавить больше { self .trajectory .duration_months } месяцев в эту траекторию."
113+ )
94114
95115 def save (self , * args , ** kwargs ):
96116 self .clean ()
@@ -108,12 +128,18 @@ class UserTrajectory(models.Model):
108128 Каждый пользователь может быть привязан только к одной активной траектории в любой момент времени.
109129 """
110130
111- user = models .ForeignKey (CustomUser , on_delete = models .CASCADE , related_name = "user_trajectories" )
131+ user = models .ForeignKey (
132+ CustomUser , on_delete = models .CASCADE , related_name = "user_trajectories"
133+ )
112134 trajectory = models .ForeignKey (Trajectory , on_delete = models .CASCADE )
113135 start_date = models .DateField (default = timezone .now )
114136 is_active = models .BooleanField (default = True )
115137 mentor = models .ForeignKey (
116- CustomUser , on_delete = models .SET_NULL , null = True , blank = True , related_name = "mentored_users"
138+ CustomUser ,
139+ on_delete = models .SET_NULL ,
140+ null = True ,
141+ blank = True ,
142+ related_name = "mentored_users" ,
117143 )
118144
119145 def __str__ (self ):
@@ -141,13 +167,72 @@ class Meta:
141167 verbose_name_plural = "Пользовательские траектории"
142168
143169
170+ class UserIndividualSkill (models .Model ):
171+ """
172+ Модель для хранения индивидуальных навыков пользователя в рамках активной траектории.
173+ Поле user_trajectory заполняется автоматически.
174+ """
175+
176+ user = models .ForeignKey (
177+ CustomUser ,
178+ on_delete = models .CASCADE ,
179+ related_name = "individual_skills" ,
180+ verbose_name = "Пользователь" ,
181+ )
182+ user_trajectory = models .ForeignKey (
183+ UserTrajectory ,
184+ on_delete = models .CASCADE ,
185+ related_name = "individual_skills" ,
186+ verbose_name = "Активная траектория" ,
187+ editable = False ,
188+ )
189+ skills = models .ManyToManyField ("courses.Skill" , verbose_name = "Навыки" )
190+ start_date = models .DateTimeField (
191+ default = timezone .now , verbose_name = "Дата добавления"
192+ )
193+
194+ class Meta :
195+ verbose_name = "Индивидуальный навык"
196+ verbose_name_plural = "Индивидуальные навыки"
197+
198+ def __str__ (self ):
199+ return f"{ self .user .email } (Траектория: { self .user_trajectory .trajectory .name } )"
200+
201+ def clean (self ):
202+ active_trajectory = self .user .user_trajectories .filter (is_active = True ).first ()
203+
204+ if not active_trajectory :
205+ raise ValidationError (
206+ "Невозможно сохранить навык: у пользователя нет активной траектории."
207+ )
208+
209+ if not self .user_trajectory_id :
210+ self .user_trajectory = active_trajectory
211+
212+ if self .user_trajectory .user != self .user :
213+ raise ValidationError ("Траектория не принадлежит указанному пользователю." )
214+
215+ super ().clean ()
216+
217+ def save (self , * args , ** kwargs ):
218+ """
219+ Удаляем явный вызов clean(), так как он вызывается автоматически
220+ через full_clean() при сохранении через формы/админку
221+ """
222+ super ().save (* args , ** kwargs )
223+
224+
144225class Meeting (models .Model ):
145226 """
146227 Модель для отражения статуса встреч пользователя с наставником в рамках траектории.
147228 """
148229
149- user_trajectory = models .ForeignKey (UserTrajectory , on_delete = models .CASCADE , related_name = "meetings" )
150- initial_meeting = models .BooleanField (default = False , verbose_name = "Начальная встреча" )
230+ user_trajectory = models .ForeignKey (
231+ UserTrajectory , on_delete = models .CASCADE , related_name = "meetings"
232+ )
233+ initial_meeting = models .BooleanField (
234+ default = False , verbose_name = "Начальная встреча"
235+ )
151236 final_meeting = models .BooleanField (default = False , verbose_name = "Финальная встреча" )
152237
153238 def __str__ (self ):
0 commit comments