2626from lms .lmsdb import database_config
2727from lms .models .errors import AlreadyExists
2828from lms .utils import hashing
29+ from lms .utils .colors import get_hex_color
30+ from lms .utils .consts import (
31+ DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR , DEFAULT_ASSESSMENT_BUTTON_COLOR ,
32+ )
2933from lms .utils .log import log
3034
3135
@@ -393,12 +397,23 @@ def open_for_new_solutions(self) -> bool:
393397 return datetime .now () < self .due_date and not self .is_archived
394398
395399 @classmethod
396- def get_highest_number (cls ):
397- return cls .select (fn .MAX (cls .number )).scalar ()
400+ def get_highest_number (cls , course : Course ):
401+ return (
402+ cls
403+ .select (fn .MAX (cls .number ))
404+ .where (cls .course == course )
405+ .group_by (cls .course )
406+ .scalar ()
407+ )
398408
399409 @classmethod
400- def is_number_exists (cls , number : int ) -> bool :
401- return cls .select ().where (cls .number == number ).exists ()
410+ def is_number_exists (cls , course : Course , number : int ) -> bool :
411+ return (
412+ cls
413+ .select ()
414+ .where (cls .course == course , cls .number == number )
415+ .exists ()
416+ )
402417
403418 @classmethod
404419 def get_objects (
@@ -446,9 +461,8 @@ def __str__(self):
446461@pre_save (sender = Exercise )
447462def exercise_number_save_handler (model_class , instance , created ):
448463 """Change the exercise number to the highest consecutive number."""
449-
450- if model_class .is_number_exists (instance .number ):
451- instance .number = model_class .get_highest_number () + 1
464+ if model_class .is_number_exists (instance .course , instance .number ):
465+ instance .number = model_class .get_highest_number (instance .course ) + 1
452466
453467
454468class SolutionState (enum .Enum ):
@@ -482,6 +496,36 @@ def to_choices(cls: enum.EnumMeta) -> Tuple[Tuple[str, str], ...]:
482496 return tuple ((choice .name , choice .value ) for choice in choices )
483497
484498
499+ class SolutionAssessment (BaseModel ):
500+ name = CharField ()
501+ icon = CharField (null = True )
502+ color = CharField ()
503+ active_color = CharField ()
504+ order = IntegerField (default = 0 , index = True )
505+ course = ForeignKeyField (Course , backref = 'assessments' )
506+
507+ @classmethod
508+ def get_assessments (cls , course : Course ):
509+ return cls .select ().where (cls .course == course ).order_by (cls .order )
510+
511+ def __str__ (self ):
512+ return self .name
513+
514+
515+ @pre_save (sender = SolutionAssessment )
516+ def assessment_on_save_handler (_model_class , instance , created ):
517+ """Change colors to hex."""
518+ try :
519+ instance .color = get_hex_color (instance .color )
520+ except ValueError :
521+ instance .color = DEFAULT_ASSESSMENT_BUTTON_COLOR
522+
523+ try :
524+ instance .active_color = get_hex_color (instance .active_color )
525+ except ValueError :
526+ instance .active_color = DEFAULT_ASSESSMENT_BUTTON_ACTIVE_COLOR
527+
528+
485529class Solution (BaseModel ):
486530 STATES = SolutionState
487531 STATUS_VIEW = SolutionStatusView
@@ -506,6 +550,9 @@ class Solution(BaseModel):
506550 index = True ,
507551 )
508552 last_time_view = DateTimeField (default = datetime .now , null = True , index = True )
553+ assessment = ForeignKeyField (
554+ SolutionAssessment , backref = 'solutions' , null = True ,
555+ )
509556
510557 @property
511558 def solution_files (
@@ -564,13 +611,18 @@ def view_solution(self) -> None:
564611 def start_checking (self ) -> bool :
565612 return self .set_state (Solution .STATES .IN_CHECKING )
566613
567- def set_state (self , new_state : SolutionState , ** kwargs ) -> bool :
614+ def set_state (
615+ self , new_state : SolutionState ,
616+ assessment : Optional [SolutionAssessment ] = None , ** kwargs ,
617+ ) -> bool :
568618 # Optional: filter the old state of the object
569619 # to make sure that no two processes set the state together
570620 requested_solution = (Solution .id == self .id )
621+ updates_dict = {Solution .state .name : new_state .name }
622+ if assessment is not None :
623+ updates_dict [Solution .assessment .name ] = assessment
571624 changes = Solution .update (
572- ** {Solution .state .name : new_state .name },
573- ** kwargs ,
625+ ** updates_dict , ** kwargs ,
574626 ).where (requested_solution )
575627 return changes .execute () == 1
576628
@@ -595,7 +647,9 @@ def of_user(
595647 exercises = Exercise .as_dicts (db_exercises )
596648 solutions = (
597649 cls
598- .select (cls .exercise , cls .id , cls .state , cls .checker )
650+ .select (
651+ cls .exercise , cls .id , cls .state , cls .checker , cls .assessment ,
652+ )
599653 .where (cls .exercise .in_ (db_exercises ), cls .solver == user_id )
600654 .order_by (cls .submission_timestamp .desc ())
601655 )
@@ -607,6 +661,8 @@ def of_user(
607661 exercise ['comments_num' ] = len (solution .staff_comments )
608662 if solution .is_checked and solution .checker :
609663 exercise ['checker' ] = solution .checker .fullname
664+ if solution .assessment :
665+ exercise ['assessment' ] = solution .assessment .name
610666 return tuple (exercises .values ())
611667
612668 @property
@@ -709,10 +765,15 @@ def _base_next_unchecked(cls):
709765
710766 def mark_as_checked (
711767 self ,
768+ assessment_id : Optional [int ] = None ,
712769 by : Optional [Union [User , int ]] = None ,
713770 ) -> bool :
771+ assessment = SolutionAssessment .get_or_none (
772+ SolutionAssessment .id == assessment_id ,
773+ )
714774 return self .set_state (
715775 Solution .STATES .DONE ,
776+ assessment = assessment ,
716777 checker = by ,
717778 )
718779
@@ -1089,6 +1150,24 @@ def create_basic_roles() -> None:
10891150 Role .create (name = role .value )
10901151
10911152
1153+ def create_basic_assessments () -> None :
1154+ assessments_dict = {
1155+ 'Excellent' : {'color' : 'green' , 'icon' : 'star' , 'order' : 1 },
1156+ 'Nice' : {'color' : 'blue' , 'icon' : 'check' , 'order' : 2 },
1157+ 'Try again' : {'color' : 'red' , 'icon' : 'exclamation' , 'order' : 3 },
1158+ 'Plagiarism' : {
1159+ 'color' : 'black' , 'icon' : 'exclamation-triangle' , 'order' : 4 ,
1160+ },
1161+ }
1162+ courses = Course .select ()
1163+ for course in courses :
1164+ for name , values in assessments_dict .items ():
1165+ SolutionAssessment .create (
1166+ name = name , icon = values .get ('icon' ), color = values .get ('color' ),
1167+ active_color = 'white' , order = values .get ('order' ), course = course ,
1168+ )
1169+
1170+
10921171def create_basic_course () -> Course :
10931172 return Course .create (name = 'Python Course' , date = datetime .now ())
10941173
0 commit comments