@@ -125,6 +125,10 @@ class Assignment(models.Model):
125125
126126 last_action_output = models .CharField (max_length = 16 * 1024 , default = "" , null = False , blank = True )
127127
128+ is_quiz = models .BooleanField (default = False )
129+ QUIZ_ACTIONS = (("0" , "Log only" ), ("1" , "Color Change" ), ("2" , "Lock" ))
130+ quiz_action = models .CharField (max_length = 1 , choices = QUIZ_ACTIONS , default = "2" )
131+
128132 objects = AssignmentQuerySet .as_manager ()
129133
130134 def __str__ (self ):
@@ -279,11 +283,23 @@ def grader_log_filename(self):
279283 else None
280284 )
281285
282- @property
283- def is_quiz (self ):
284- if hasattr (self , "quiz" ):
285- return self .quiz
286- return False
286+ def quiz_open_for_student (self , student ):
287+ is_teacher = self .course .teacher .filter (id = student .id ).exists ()
288+ if is_teacher or student .is_superuser :
289+ return True
290+ return not (self .quiz_ended_for_student (student ) or self .quiz_locked_for_student (student ))
291+
292+ def quiz_ended_for_student (self , student ):
293+ return self .log_messages .filter (student = student , content = "Ended quiz" ).exists ()
294+
295+ def quiz_locked_for_student (self , student ):
296+ return self .quiz_issues_for_student (student ) and self .quiz_action == "2"
297+
298+ def quiz_issues_for_student (self , student ):
299+ return (
300+ sum (lm .severity for lm in self .log_messages .filter (student = student ))
301+ >= settings .QUIZ_ISSUE_THRESHOLD
302+ )
287303
288304
289305class CooldownPeriod (models .Model ):
@@ -335,6 +351,9 @@ def get_time_to_end(self) -> datetime.timedelta:
335351 )
336352
337353
354+ # WARNING: This model is deprecated and will be removed in the future.
355+ # It is kept for backwards compatibility with existing data.
356+ # All fields and methods have been migrated to the Assignment model.
338357class Quiz (models .Model ):
339358 QUIZ_ACTIONS = (("0" , "Log only" ), ("1" , "Color Change" ), ("2" , "Lock" ))
340359
@@ -358,7 +377,7 @@ def __repr__(self):
358377
359378 def issues_for_student (self , student ):
360379 return (
361- sum (lm .severity for lm in self .log_messages .filter (student = student ))
380+ sum (lm .severity for lm in self .assignment . log_messages .filter (student = student ))
362381 >= settings .QUIZ_ISSUE_THRESHOLD
363382 )
364383
@@ -372,11 +391,13 @@ def locked_for_student(self, student):
372391 return self .issues_for_student (student ) and self .action == "2"
373392
374393 def ended_for_student (self , student ):
375- return self .log_messages .filter (student = student , content = "Ended quiz" ).exists ()
394+ return self .assignment . log_messages .filter (student = student , content = "Ended quiz" ).exists ()
376395
377396
378- class LogMessage (models .Model ):
379- quiz = models .ForeignKey (Quiz , on_delete = models .CASCADE , related_name = "log_messages" )
397+ class QuizLogMessage (models .Model ):
398+ assignment = models .ForeignKey (
399+ Assignment , on_delete = models .CASCADE , related_name = "log_messages"
400+ )
380401 student = models .ForeignKey (
381402 get_user_model (), on_delete = models .CASCADE , related_name = "log_messages"
382403 )
@@ -386,15 +407,13 @@ class LogMessage(models.Model):
386407 severity = models .IntegerField ()
387408
388409 def __str__ (self ):
389- return f"{ self .content } for { self .quiz } "
410+ return f"{ self .content } for { self .assignment } by { self . student } "
390411
391412 def get_absolute_url (self ):
392- return reverse (
393- "assignments:student_submission" , args = (self .quiz .assignment .id , self .student .id )
394- )
413+ return reverse ("assignments:student_submission" , args = (self .assignment .id , self .student .id ))
395414
396415 def __repr__ (self ):
397- return f"{ self .content } for { self .quiz } "
416+ return f"{ self .content } for { self .assignment } by { self . student } "
398417
399418
400419def moss_base_file_path (obj , _ ): # pylint: disable=unused-argument
0 commit comments