|
18 | 18 | from judge import contest_format, event_poster as event |
19 | 19 | from judge.models.problem import Problem |
20 | 20 | from judge.models.profile import Organization, Profile |
| 21 | +from judge.models.role import ContestRole, RoleQuerySetAdapter, ROLE_AUTHOR, ROLE_CURATOR, ROLE_TESTER |
21 | 22 | from judge.models.submission import Submission |
22 | 23 | from judge.ratings import rate_contest |
23 | 24 | from judge.utils.unicode import utf8bytes |
24 | 25 |
|
25 | 26 | __all__ = ['Contest', 'ContestTag', 'ContestAnnouncement', 'ContestParticipation', 'ContestProblem', |
26 | | - 'ContestSubmission', 'Rating'] |
| 27 | + 'ContestSubmission', 'Rating', 'ContestRole'] |
27 | 28 |
|
28 | 29 |
|
29 | 30 | class MinValueOrNoneValidator(MinValueValidator): |
@@ -74,14 +75,6 @@ class Contest(models.Model): |
74 | 75 | key = models.CharField(max_length=32, verbose_name=_('contest id'), unique=True, |
75 | 76 | validators=[RegexValidator('^[a-z0-9_]+$', _('Contest id must be ^[a-z0-9_]+$'))]) |
76 | 77 | 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+') |
85 | 78 | description = models.TextField(verbose_name=_('description'), blank=True) |
86 | 79 | problems = models.ManyToManyField(Problem, verbose_name=_('problems'), through='ContestProblem') |
87 | 80 | start_time = models.DateTimeField(verbose_name=_('start time'), db_index=True) |
@@ -318,6 +311,23 @@ def show_scoreboard(self): |
318 | 311 | return False |
319 | 312 | return True |
320 | 313 |
|
| 314 | + def _role_users(self, role): |
| 315 | + return RoleQuerySetAdapter(Profile.objects.filter( |
| 316 | + contest_roles__contest=self, contest_roles__role=role, |
| 317 | + )) |
| 318 | + |
| 319 | + @property |
| 320 | + def authors(self): |
| 321 | + return self._role_users(ROLE_AUTHOR) |
| 322 | + |
| 323 | + @property |
| 324 | + def curators(self): |
| 325 | + return self._role_users(ROLE_CURATOR) |
| 326 | + |
| 327 | + @property |
| 328 | + def testers(self): |
| 329 | + return self._role_users(ROLE_TESTER) |
| 330 | + |
321 | 331 | @property |
322 | 332 | def contest_window_length(self): |
323 | 333 | return self.end_time - self.start_time |
@@ -375,18 +385,22 @@ def time_before_end(self): |
375 | 385 | def ended(self): |
376 | 386 | return self.end_time < self._now |
377 | 387 |
|
| 388 | + def _role_ids(self, roles): |
| 389 | + return ContestRole.objects.filter( |
| 390 | + contest=self, role__in=roles, |
| 391 | + ).values_list('user_id', flat=True) |
| 392 | + |
378 | 393 | @cached_property |
379 | 394 | def author_ids(self): |
380 | | - return Contest.authors.through.objects.filter(contest=self).values_list('profile_id', flat=True) |
| 395 | + return self._role_ids([ROLE_AUTHOR]) |
381 | 396 |
|
382 | 397 | @cached_property |
383 | 398 | def editor_ids(self): |
384 | | - return self.author_ids.union( |
385 | | - Contest.curators.through.objects.filter(contest=self).values_list('profile_id', flat=True)) |
| 399 | + return self._role_ids([ROLE_AUTHOR, ROLE_CURATOR]) |
386 | 400 |
|
387 | 401 | @cached_property |
388 | 402 | def tester_ids(self): |
389 | | - return Contest.testers.through.objects.filter(contest=self).values_list('profile_id', flat=True) |
| 403 | + return self._role_ids([ROLE_TESTER]) |
390 | 404 |
|
391 | 405 | @classmethod |
392 | 406 | def get_id_secret(cls, contest_id): |
@@ -530,31 +544,9 @@ def get_visible_contests(cls, user): |
530 | 544 | ) |
531 | 545 | ) |
532 | 546 |
|
533 | | - authors_exists = Contest.authors.through.objects.filter( |
534 | | - contest_id=OuterRef('pk'), |
535 | | - profile_id=user.profile.id, |
536 | | - ) |
537 | | - curators_exists = Contest.curators.through.objects.filter( |
538 | | - contest_id=OuterRef('pk'), |
539 | | - profile_id=user.profile.id, |
540 | | - ) |
541 | | - testers_exists = Contest.testers.through.objects.filter( |
542 | | - contest_id=OuterRef('pk'), |
543 | | - profile_id=user.profile.id, |
544 | | - ) |
545 | | - |
546 | 547 | queryset = queryset.annotate( |
547 | | - has_author=Exists(authors_exists), |
548 | | - has_curator=Exists(curators_exists), |
549 | | - has_tester=Exists(testers_exists), |
550 | | - ) |
551 | | - |
552 | | - queryset = queryset.filter( |
553 | | - q | |
554 | | - Q(has_author=True) | |
555 | | - Q(has_curator=True) | |
556 | | - Q(has_tester=True), |
557 | | - ) |
| 548 | + has_role=ContestRole.exists_for(user.profile), |
| 549 | + ).filter(q | Q(has_role=True)) |
558 | 550 |
|
559 | 551 | return queryset |
560 | 552 |
|
|
0 commit comments