@@ -414,6 +414,34 @@ def delete(self, *args, **kwargs) -> tuple[int, dict[str, int]]: # type: ignore
414414 return super ().delete (* args , ** kwargs )
415415
416416
417+ class Assignment (models .Model ):
418+ title = models .CharField (max_length = 200 )
419+ description = models .TextField ()
420+ is_blocking = models .BooleanField (
421+ default = True ,
422+ help_text = "Whether the learner is required to submit the assignment to proceed to the next content." ,
423+ )
424+ deadline_days = models .IntegerField (
425+ help_text = "Time limit to complete the assignment in days. 0 indicates no deadline." ,
426+ validators = [MinValueValidator (0 )],
427+ )
428+ requires_text_submission = models .BooleanField (
429+ help_text = "Whether the assignment requires text submission."
430+ )
431+ requires_file_submission = models .BooleanField (
432+ help_text = "Whether the assignment requires file submission."
433+ )
434+ reminder_interval_days = models .IntegerField (
435+ help_text = "For assignments without a deadline (deadline_days = 0), send reminder emails every N days." ,
436+ validators = [MinValueValidator (0 )],
437+ null = True ,
438+ blank = True ,
439+ )
440+
441+ def __str__ (self ) -> str :
442+ return self .title
443+
444+
417445class CourseContent (models .Model ):
418446 course = models .ForeignKey (Course , on_delete = models .CASCADE )
419447 priority = models .IntegerField ()
@@ -422,10 +450,14 @@ class CourseContent(models.Model):
422450 choices = [
423451 ("lesson" , "Lesson" ),
424452 ("quiz" , "Quiz" ),
453+ ("assignment" , "Assignment" ),
425454 ],
426455 )
427456 lesson = models .ForeignKey (Lesson , null = True , blank = True , on_delete = models .CASCADE )
428457 quiz = models .ForeignKey (Quiz , null = True , blank = True , on_delete = models .CASCADE )
458+ assignment = models .ForeignKey (
459+ Assignment , null = True , blank = True , on_delete = models .CASCADE
460+ )
429461 waiting_period = models .IntegerField (
430462 help_text = "Waiting period in seconds after previous content is sent or submited."
431463 )
@@ -436,14 +468,34 @@ def __str__(self) -> str:
436468 return f"{ self .priority } - Lesson: { self .lesson .title } "
437469 elif self .type == "quiz" and self .quiz :
438470 return f"{ self .priority } - Quiz: { self .quiz .title } "
471+ elif self .type == "assignment" and self .assignment :
472+ return f"{ self .priority } - Assignment: { self .assignment .title } "
439473 return f"{ self .course .title } content #{ self .priority } "
440474
475+ @property
476+ def deadline_days (self ) -> Optional [int ]:
477+ if self .type == "quiz" and self .quiz :
478+ return self .quiz .deadline_days
479+ elif self .type == "assignment" and self .assignment :
480+ return self .assignment .deadline_days
481+ return None
482+
483+ @property
484+ def reminder_interval_days (self ) -> Optional [int ]:
485+ if self .type == "quiz" and self .quiz :
486+ return self .quiz .reminder_interval_days
487+ elif self .type == "assignment" and self .assignment :
488+ return self .assignment .reminder_interval_days
489+ return None
490+
441491 @property
442492 def title (self ) -> str :
443493 if self .type == "lesson" and self .lesson :
444494 return self .lesson .title
445495 elif self .type == "quiz" and self .quiz :
446496 return self .quiz .title
497+ elif self .type == "assignment" and self .assignment :
498+ return self .assignment .title
447499 return "Untitled Content"
448500
449501 @property
@@ -456,6 +508,8 @@ def limited_attempts(self) -> Optional[bool]:
456508 def is_blocking (self ) -> Optional [bool ]:
457509 if self .type == "quiz" and self .quiz :
458510 return self .quiz .is_blocking
511+ elif self .type == "assignment" and self .assignment :
512+ return self .assignment .is_blocking
459513 return None
460514
461515 def human_readable_waiting_period (self ) -> str :
@@ -482,10 +536,14 @@ def _validate_content(self) -> None:
482536 raise ValidationError ("Lesson must be provided for lesson content." )
483537 if self .type == "quiz" and not self .quiz :
484538 raise ValidationError ("Quiz must be provided for quiz content." )
539+ if self .type == "assignment" and not self .assignment :
540+ raise ValidationError ("Assignment must be provided for assignment content." )
485541 if self .type == "lesson" and self .lesson :
486542 self .lesson .full_clean ()
487543 elif self .type == "quiz" and self .quiz :
488544 self .quiz .full_clean ()
545+ elif self .type == "assignment" and self .assignment :
546+ self .assignment .full_clean ()
489547
490548 def full_clean (self , * args , ** kwargs ) -> None : # type: ignore[no-untyped-def]
491549 self ._validate_content ()
@@ -517,6 +575,11 @@ class Meta:
517575 condition = models .Q (lesson__isnull = False ),
518576 name = "unique_lesson_per_course" ,
519577 ),
578+ models .UniqueConstraint (
579+ fields = ["course" , "assignment" ],
580+ condition = models .Q (assignment__isnull = False ),
581+ name = "unique_assignment_per_course" ,
582+ ),
520583 models .UniqueConstraint (
521584 fields = ["course" , "priority" ],
522585 name = "unique_priority_per_course" ,
@@ -861,28 +924,29 @@ def repeat_delivery_in_days(self, days: int) -> bool:
861924 return True
862925
863926 def calculate_remind_at (self ) -> Optional [datetime ]:
864- if self .course_content .quiz :
865- if self .course_content .quiz .deadline_days > 0 :
866- if self .course_content .quiz .deadline_days > 1 :
927+ if self .course_content .quiz or self .course_content .assignment :
928+ if (
929+ self .course_content .deadline_days
930+ and self .course_content .deadline_days > 0
931+ ):
932+ if self .course_content .deadline_days > 1 :
867933 return timezone .now () + timedelta (
868- days = self .course_content .quiz . deadline_days - 1
934+ days = self .course_content .deadline_days - 1
869935 )
870936 else :
871937 return timezone .now () + timedelta (
872- hours = (self .course_content .quiz . deadline_days * 24 ) - 10
938+ hours = (self .course_content .deadline_days * 24 ) - 10
873939 )
874940 else :
875- if self .course_content .quiz . reminder_interval_days :
941+ if self .course_content .reminder_interval_days :
876942 return timezone .now () + timedelta (
877- days = self .course_content .quiz . reminder_interval_days
943+ days = self .course_content .reminder_interval_days
878944 )
879945 return None
880946
881947 def calculate_valid_until (self ) -> Optional [datetime ]:
882- if self .course_content .quiz and self .course_content .quiz .deadline_days > 0 :
883- return timezone .now () + timedelta (
884- days = self .course_content .quiz .deadline_days
885- )
948+ if self .course_content .deadline_days and self .course_content .deadline_days > 0 :
949+ return timezone .now () + timedelta (days = self .course_content .deadline_days )
886950 return None
887951
888952 def save (self , * args , ** kwargs ) -> None : # type: ignore[no-untyped-def]
@@ -947,6 +1011,9 @@ def generate_link(self) -> str:
9471011 self .link = link
9481012 self .save ()
9491013 return link
1014+ elif self .delivery .course_content .assignment :
1015+ # TODO: Implement assignment link generation
1016+ return ""
9501017 else :
9511018 # TODO: Implement lesson link generation
9521019 return ""
0 commit comments