1616)
1717from .course import Course
1818
19+ ALLOWED_IMAGE_EXTENSIONS = {
20+ "bmp" ,
21+ "gif" ,
22+ "jpg" ,
23+ "jpeg" ,
24+ "png" ,
25+ "svg" ,
26+ "webp" ,
27+ }
28+
29+
30+ def looks_like_image_file (* , mime_type : str | None = None , extension : str | None = None ) -> bool :
31+ normalized_mime_type = (mime_type or "" ).strip ().lower ()
32+ if normalized_mime_type .startswith ("image/" ):
33+ return True
34+
35+ normalized_extension = (extension or "" ).strip ().lower ().lstrip ("." )
36+ return normalized_extension in ALLOWED_IMAGE_EXTENSIONS
37+
1938
2039class CourseModule (models .Model ):
2140 course = models .ForeignKey (
@@ -273,11 +292,36 @@ def __str__(self):
273292 def _require_non_blank (value : str | None ) -> bool :
274293 return bool (value and value .strip ())
275294
295+ def _has_image_source (self ) -> bool :
296+ return self .image_file_id is not None or getattr (
297+ self ,
298+ "_has_pending_image_upload" ,
299+ False ,
300+ )
301+
302+ def _has_attachment_source (self ) -> bool :
303+ return self .attachment_file_id is not None or getattr (
304+ self ,
305+ "_has_pending_attachment_upload" ,
306+ False ,
307+ )
308+
309+ def _has_valid_image_file (self ) -> bool :
310+ if self .image_file_id is None :
311+ return False
312+ return looks_like_image_file (
313+ mime_type = self .image_file .mime_type ,
314+ extension = self .image_file .extension ,
315+ )
316+
276317 def clean (self ):
277318 super ().clean ()
278319
279320 errors = {}
280321
322+ if self .image_file_id is not None and not self ._has_valid_image_file ():
323+ errors ["image_file" ] = "В поле изображения можно выбрать только файл изображения."
324+
281325 if self .task_kind == CourseTaskKind .INFORMATIONAL :
282326 if self .informational_type == CourseTaskInformationalType .VIDEO_TEXT :
283327 if not self ._require_non_blank (self .body_text ):
@@ -292,7 +336,7 @@ def clean(self):
292336 errors ["body_text" ] = (
293337 "Поле обязательно для типа 'Текст и изображение'."
294338 )
295- if self .image_file_id is None :
339+ if not self ._has_image_source () :
296340 errors ["image_file" ] = (
297341 "Поле обязательно для типа 'Текст и изображение'."
298342 )
@@ -303,15 +347,15 @@ def clean(self):
303347 errors ["body_text" ] = (
304348 "Поле обязательно для типа вопроса 'Изображение и текст'."
305349 )
306- if self .image_file_id is None :
350+ if not self ._has_image_source () :
307351 errors ["image_file" ] = (
308352 "Поле обязательно для типа вопроса 'Изображение и текст'."
309353 )
310354 elif self .question_type == CourseTaskQuestionType .VIDEO :
311355 if not self ._require_non_blank (self .video_url ):
312356 errors ["video_url" ] = "Поле обязательно для типа вопроса 'Видео'."
313357 elif self .question_type == CourseTaskQuestionType .IMAGE :
314- if self .image_file_id is None :
358+ if not self ._has_image_source () :
315359 errors ["image_file" ] = (
316360 "Поле обязательно для типа вопроса 'Изображение'."
317361 )
@@ -320,7 +364,7 @@ def clean(self):
320364 errors ["body_text" ] = (
321365 "Поле обязательно для типа вопроса 'Текст с файлом'."
322366 )
323- if self .attachment_file_id is None :
367+ if not self ._has_attachment_source () :
324368 errors ["attachment_file" ] = (
325369 "Поле обязательно для типа вопроса 'Текст с файлом'."
326370 )
0 commit comments