26
26
from lms .lmsdb import database_config
27
27
from lms .models .errors import AlreadyExists
28
28
from 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
+ )
29
33
from lms .utils .log import log
30
34
31
35
@@ -393,12 +397,23 @@ def open_for_new_solutions(self) -> bool:
393
397
return datetime .now () < self .due_date and not self .is_archived
394
398
395
399
@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
+ )
398
408
399
409
@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
+ )
402
417
403
418
@classmethod
404
419
def get_objects (
@@ -446,9 +461,8 @@ def __str__(self):
446
461
@pre_save (sender = Exercise )
447
462
def exercise_number_save_handler (model_class , instance , created ):
448
463
"""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
452
466
453
467
454
468
class SolutionState (enum .Enum ):
@@ -482,6 +496,36 @@ def to_choices(cls: enum.EnumMeta) -> Tuple[Tuple[str, str], ...]:
482
496
return tuple ((choice .name , choice .value ) for choice in choices )
483
497
484
498
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
+
485
529
class Solution (BaseModel ):
486
530
STATES = SolutionState
487
531
STATUS_VIEW = SolutionStatusView
@@ -506,6 +550,9 @@ class Solution(BaseModel):
506
550
index = True ,
507
551
)
508
552
last_time_view = DateTimeField (default = datetime .now , null = True , index = True )
553
+ assessment = ForeignKeyField (
554
+ SolutionAssessment , backref = 'solutions' , null = True ,
555
+ )
509
556
510
557
@property
511
558
def solution_files (
@@ -564,13 +611,18 @@ def view_solution(self) -> None:
564
611
def start_checking (self ) -> bool :
565
612
return self .set_state (Solution .STATES .IN_CHECKING )
566
613
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 :
568
618
# Optional: filter the old state of the object
569
619
# to make sure that no two processes set the state together
570
620
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
571
624
changes = Solution .update (
572
- ** {Solution .state .name : new_state .name },
573
- ** kwargs ,
625
+ ** updates_dict , ** kwargs ,
574
626
).where (requested_solution )
575
627
return changes .execute () == 1
576
628
@@ -595,7 +647,9 @@ def of_user(
595
647
exercises = Exercise .as_dicts (db_exercises )
596
648
solutions = (
597
649
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
+ )
599
653
.where (cls .exercise .in_ (db_exercises ), cls .solver == user_id )
600
654
.order_by (cls .submission_timestamp .desc ())
601
655
)
@@ -607,6 +661,8 @@ def of_user(
607
661
exercise ['comments_num' ] = len (solution .staff_comments )
608
662
if solution .is_checked and solution .checker :
609
663
exercise ['checker' ] = solution .checker .fullname
664
+ if solution .assessment :
665
+ exercise ['assessment' ] = solution .assessment .name
610
666
return tuple (exercises .values ())
611
667
612
668
@property
@@ -709,10 +765,15 @@ def _base_next_unchecked(cls):
709
765
710
766
def mark_as_checked (
711
767
self ,
768
+ assessment_id : Optional [int ] = None ,
712
769
by : Optional [Union [User , int ]] = None ,
713
770
) -> bool :
771
+ assessment = SolutionAssessment .get_or_none (
772
+ SolutionAssessment .id == assessment_id ,
773
+ )
714
774
return self .set_state (
715
775
Solution .STATES .DONE ,
776
+ assessment = assessment ,
716
777
checker = by ,
717
778
)
718
779
@@ -1089,6 +1150,24 @@ def create_basic_roles() -> None:
1089
1150
Role .create (name = role .value )
1090
1151
1091
1152
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
+
1092
1171
def create_basic_course () -> Course :
1093
1172
return Course .create (name = 'Python Course' , date = datetime .now ())
1094
1173
0 commit comments