1818from judge import contest_format , event_poster as event
1919from judge .models .problem import Problem
2020from judge .models .profile import Organization , Profile
21+ from judge .models .role import ContestRole , ROLE_AUTHOR , ROLE_CURATOR , ROLE_TESTER
2122from judge .models .submission import Submission
2223from judge .ratings import rate_contest
2324from judge .utils .unicode import utf8bytes
2425
2526__all__ = ['Contest' , 'ContestTag' , 'ContestAnnouncement' , 'ContestParticipation' , 'ContestProblem' ,
26- 'ContestSubmission' , 'Rating' ]
27+ 'ContestSubmission' , 'Rating' , 'ContestRole' ]
2728
2829
2930class MinValueOrNoneValidator (MinValueValidator ):
@@ -60,6 +61,32 @@ class Meta:
6061 verbose_name_plural = _ ('contest tags' )
6162
6263
64+ class RoleQuerySetAdapter :
65+ def __init__ (self , queryset ):
66+ self .queryset = queryset
67+
68+ def all (self ):
69+ return self .queryset .all ()
70+
71+ def filter (self , * args , ** kwargs ):
72+ return self .queryset .filter (* args , ** kwargs )
73+
74+ def exists (self ):
75+ return self .queryset .exists ()
76+
77+ def first (self ):
78+ return self .queryset .first ()
79+
80+ def values_list (self , * args , ** kwargs ):
81+ return self .queryset .values_list (* args , ** kwargs )
82+
83+ def __iter__ (self ):
84+ return iter (self .queryset )
85+
86+ def __contains__ (self , item ):
87+ return self .queryset .filter (pk = getattr (item , 'pk' , item )).exists ()
88+
89+
6390class Contest (models .Model ):
6491 SCOREBOARD_VISIBLE = 'V'
6592 SCOREBOARD_HIDDEN = 'H'
@@ -74,14 +101,6 @@ class Contest(models.Model):
74101 key = models .CharField (max_length = 32 , verbose_name = _ ('contest id' ), unique = True ,
75102 validators = [RegexValidator ('^[a-z0-9_]+$' , _ ('Contest id must be ^[a-z0-9_]+$' ))])
76103 name = models .CharField (max_length = 100 , verbose_name = _ ('contest name' ), db_index = True )
77- authors = models .ManyToManyField (Profile , help_text = _ ('These users will be able to edit the contest.' ),
78- related_name = 'authors+' )
79- curators = models .ManyToManyField (Profile , help_text = _ ('These users will be able to edit the contest, '
80- 'but will not be listed as authors.' ),
81- related_name = 'curators+' , blank = True )
82- testers = models .ManyToManyField (Profile , help_text = _ ('These users will be able to view the contest, '
83- 'but not edit it.' ),
84- blank = True , related_name = 'testers+' )
85104 description = models .TextField (verbose_name = _ ('description' ), blank = True )
86105 problems = models .ManyToManyField (Problem , verbose_name = _ ('problems' ), through = 'ContestProblem' )
87106 start_time = models .DateTimeField (verbose_name = _ ('start time' ), db_index = True )
@@ -318,6 +337,27 @@ def show_scoreboard(self):
318337 return False
319338 return True
320339
340+ @property
341+ def authors (self ):
342+ return RoleQuerySetAdapter (Profile .objects .filter (
343+ contest_roles__contest = self ,
344+ contest_roles__role = ROLE_AUTHOR ,
345+ ))
346+
347+ @property
348+ def curators (self ):
349+ return RoleQuerySetAdapter (Profile .objects .filter (
350+ contest_roles__contest = self ,
351+ contest_roles__role = ROLE_CURATOR ,
352+ ))
353+
354+ @property
355+ def testers (self ):
356+ return RoleQuerySetAdapter (Profile .objects .filter (
357+ contest_roles__contest = self ,
358+ contest_roles__role = ROLE_TESTER ,
359+ ))
360+
321361 @property
322362 def contest_window_length (self ):
323363 return self .end_time - self .start_time
@@ -377,16 +417,27 @@ def ended(self):
377417
378418 @cached_property
379419 def author_ids (self ):
380- return Contest .authors .through .objects .filter (contest = self ).values_list ('profile_id' , flat = True )
420+ return ContestRole .objects .filter (
421+ contest = self , role = ROLE_AUTHOR ,
422+ ).values_list ('user_id' , flat = True )
381423
382424 @cached_property
383425 def editor_ids (self ):
384- return self .author_ids .union (
385- Contest .curators .through .objects .filter (contest = self ).values_list ('profile_id' , flat = True ))
426+ return ContestRole .objects .filter (
427+ contest = self ,
428+ role = ROLE_AUTHOR ,
429+ ).values_list ('user_id' , flat = True ).union (
430+ ContestRole .objects .filter (
431+ contest = self ,
432+ role = ROLE_CURATOR ,
433+ ).values_list ('user_id' , flat = True ),
434+ )
386435
387436 @cached_property
388437 def tester_ids (self ):
389- return Contest .testers .through .objects .filter (contest = self ).values_list ('profile_id' , flat = True )
438+ return ContestRole .objects .filter (
439+ contest = self , role = ROLE_TESTER ,
440+ ).values_list ('user_id' , flat = True )
390441
391442 @classmethod
392443 def get_id_secret (cls , contest_id ):
@@ -530,17 +581,20 @@ def get_visible_contests(cls, user):
530581 )
531582 )
532583
533- authors_exists = Contest . authors . through .objects .filter (
584+ authors_exists = ContestRole .objects .filter (
534585 contest_id = OuterRef ('pk' ),
535- profile_id = user .profile .id ,
586+ user_id = user .profile .id ,
587+ role = ROLE_AUTHOR ,
536588 )
537- curators_exists = Contest . curators . through .objects .filter (
589+ curators_exists = ContestRole .objects .filter (
538590 contest_id = OuterRef ('pk' ),
539- profile_id = user .profile .id ,
591+ user_id = user .profile .id ,
592+ role = ROLE_CURATOR ,
540593 )
541- testers_exists = Contest . testers . through .objects .filter (
594+ testers_exists = ContestRole .objects .filter (
542595 contest_id = OuterRef ('pk' ),
543- profile_id = user .profile .id ,
596+ user_id = user .profile .id ,
597+ role = ROLE_TESTER ,
544598 )
545599
546600 queryset = queryset .annotate (
0 commit comments