def filter_visible_reports(self, request, submission, queryset): if is_contest_basicadmin(request) or is_contest_observer(request): return queryset return queryset.filter( status='ACTIVE', kind__in=self.get_visible_reports_kinds(request, submission), )
def has_change_permission(self, request, obj=None): if obj: return False # is_contest_observer() is required in here, because otherwise # observers get a 403 response. Any actions that modify submissions # will be blocked in get_actions() return is_contest_admin(request) or is_contest_observer(request)
def can_generate_user_out(self, request, submission_report): """Determines if the current user is allowed to generate outs from ``submission_report``. Default implementations delegates to ``report_actions_config`` associated with the problem, :meth:`~ContestController.can_see_problem`, :meth:`~ContestController.filter_my_visible_submissions`, except for admins and observers, which get full access. """ submission = submission_report.submission if is_contest_basicadmin(request) or is_contest_observer(request): return True if not has_report_actions_config(submission.problem_instance.problem): return False config = submission.problem_instance.problem.report_actions_config return (config.can_user_generate_outs and submission.user == request.user and self.can_see_problem(request, submission.problem_instance) and self.filter_visible_reports( request, submission, SubmissionReport.objects.filter(id=submission_report.id), ).exists())
def test_utils(self): ofactory = partial(self.factory, self.observer) cfactory = partial(self.factory, self.cadmin) ufactory = partial(self.factory, User.objects.get(username='******')) self.assertFalse(can_enter_contest(ufactory(self.during))) self.assertTrue(is_contest_admin(cfactory(self.during))) self.assertTrue(can_enter_contest(cfactory(self.during))) self.assertTrue(is_contest_observer(ofactory(self.during))) self.assertTrue(can_enter_contest(ofactory(self.during)))
def get_default_submission_kind(self, request): """Returns default kind of newly created submission by the current user. The default implementation returns ``'IGNORED'`` for non-contestants. In other cases it returns ``'NORMAL'``. """ if is_contest_admin(request) or is_contest_observer(request): return 'IGNORED' return 'NORMAL'
def points_to_source_length_problem(request, problem): submissions = ProgramSubmission.objects.filter( problem_instance=problem, submissionreport__userresultforproblem__isnull=False) contest = request.contest controller = contest.controller if is_contest_admin(request) or is_contest_observer(request): visible_submissions = submissions else: visible_submissions = controller.filter_my_visible_submissions( request, submissions) data = [] for s in submissions: record = {'x': s.source_length, 'y': int_score(s.score), 'url': ''} kwargs = {'submission_id': s.id, 'contest_id': contest.id} if visible_submissions.filter(id=s.id).exists(): record['url'] = reverse('submission', kwargs=kwargs) elif controller.can_see_source(request, s.submission_ptr): record['url'] = reverse('show_submission_source', kwargs=kwargs) data.append(record) data = sorted(data) # Assumes that max_score is exactly the same for each submission max_score = None if submissions: score_reports = ScoreReport.objects.filter( submission_report__submission__in=submissions, submission_report__status='ACTIVE', submission_report__kind__in=('NORMAL', 'FULL'), ) if score_reports: max_score = score_reports.latest('id').max_score max_score = int_score(max_score, 0) return { 'plot_name': _("Points vs source length scatter"), 'data': [data], 'x_min': 0, 'y_min': 0, 'y_max': max_score, 'titles': { 'xAxis': _("Source length (bytes)"), 'yAxis': _("Points"), }, 'series': [problem.short_name], 'series_extra_options': [{ 'color': 'rgba(47, 126, 216, 0.5)' }], }
def points_to_source_length_problem(request, problem): submissions = ProgramSubmission.objects.filter( problem_instance=problem, submissionreport__userresultforproblem__isnull=False) contest = request.contest controller = contest.controller if is_contest_admin(request) or is_contest_observer(request): visible_submissions = submissions else: visible_submissions = \ controller.filter_my_visible_submissions(request, submissions) data = [] for s in submissions: record = {'x': s.source_length, 'y': int_score(s.score), 'url': ''} kwargs = {'submission_id': s.id, 'contest_id': contest.id} if visible_submissions.filter(id=s.id).exists(): record['url'] = reverse('submission', kwargs=kwargs) elif controller.can_see_source(request, s.submission_ptr): record['url'] = reverse('show_submission_source', kwargs=kwargs) data.append(record) data = sorted(data) # Assumes that max_score is exactly the same for each submission max_score = None if submissions: score_reports = ScoreReport.objects.filter( submission_report__submission__in=submissions, submission_report__status='ACTIVE', submission_report__kind__in=('NORMAL', 'FULL')) if score_reports: max_score = score_reports.latest('id').max_score max_score = int_score(max_score, 0) return { 'plot_name': _("Points vs source length scatter"), 'data': [data], 'x_min': 0, 'y_min': 0, 'y_max': max_score, 'titles': { 'xAxis': _("Source length (bytes)"), 'yAxis': _("Points"), }, 'series': [problem.short_name], 'series_extra_options': [{'color': 'rgba(47, 126, 216, 0.5)'}], }
def livedata_events_view(request, round_id): user_is_participant = \ Q(submission__user__participant__contest_id=request.contest.id, submission__user__participant__status='ACTIVE') submission_ignored = Q(submission__kind='IGNORED') reports = SubmissionReport.objects \ .filter(user_is_participant) \ .exclude(submission_ignored) \ .select_related('submission') \ .prefetch_related('scorereport_set') if (is_contest_admin(request) or is_contest_observer(request)) and \ 'from' in request.GET: # Only admin/observer is allowed to specify 'from' parameter. start_time = datetime.datetime.utcfromtimestamp( int(request.GET['from'])).replace(tzinfo=utc) reports = reports.filter(creation_date__gte=start_time) round = get_object_or_404(request.contest.round_set.all(), pk=round_id) contest_start = round.start_date reports = reports.filter(submission__problem_instance__round=round) if is_contest_admin(request): freeze_time = None else: freeze_time = request.contest.controller.get_round_freeze_time(round) return [ { 'submissionId': 'START', 'reportId': 'START', 'teamId': 'START', 'taskId': 'START', 'submissionTimestamp': int( dateformat.format(request.timestamp, 'U')), 'judgingTimestamp': int(dateformat.format(contest_start, 'U')), 'result': 'CTRL', } ] + [{ 'submissionId': report.submission_id, 'reportId': report.pk, 'teamId': report.submission.user_id, 'taskId': report.submission.problem_instance_id, 'submissionTimestamp': int(dateformat.format(report.submission.date, 'U')), 'judgingTimestamp': int(dateformat.format(report.creation_date, 'U')), 'result': report.score_report.status if freeze_time is None or report.submission.date < freeze_time else RESULT_FOR_FROZEN_SUBMISSION, } for report in reports.order_by('creation_date') if report.score_report is not None]
def results_visible(self, request, submission): normally = super(DisqualificationContestControllerMixin, self) \ .results_visible(request, submission) if is_contest_admin(request) or is_contest_observer(request): return normally return normally and \ not self.is_any_submission_to_problem_disqualified( submission.user, submission.problem_instance)
def can_see_stats(self, request): """Determines if statistics should be shown""" if is_contest_admin(request) or is_contest_observer(request): return True try: sconfig = request.contest.statistics_config return (sconfig.visible_to_users and request.timestamp >= sconfig.visibility_date) except StatisticsConfig.DoesNotExist: return False
def _rounds_for_ranking(self, request, key=CONTEST_RANKING_KEY): can_see_all = is_contest_admin(request) or is_contest_observer(request) ccontroller = self.contest.controller queryset = self.contest.round_set.all() if key != CONTEST_RANKING_KEY: queryset = queryset.filter(id=key) for round in queryset: times = ccontroller.get_round_times(request, round) if can_see_all or times.results_visible(request.timestamp): yield round
def can_see_stats(self, request): """Determines if statistics should be shown""" if is_contest_admin(request) or is_contest_observer(request): return True try: sconfig = request.contest.statistics_config return sconfig.visible_to_users and \ request.timestamp >= sconfig.visibility_date except StatisticsConfig.DoesNotExist: return False
def _get_users_results(self, request, pis, results, rounds, users): contest = request.contest controller = contest.controller by_user = defaultdict(dict) for r in results: by_user[r.user_id][r.problem_instance_id] = r users = users.filter(id__in=by_user.keys()) data = [] all_rounds_trial = all(r.is_trial for r in rounds) for user in users.order_by('last_name', 'first_name', 'username'): by_user_row = by_user[user.id] user_results = [] user_data = { 'user': user, 'results': user_results, 'sum': None } submissions = Submission.objects.filter( problem_instance__contest=contest) if is_contest_admin(request) or is_contest_observer(request): my_visible = submissions else: my_visible = controller.filter_my_visible_submissions(request, submissions) for pi in pis: result = by_user_row.get(pi.id) if result and hasattr(result, 'submission_report') and \ hasattr(result.submission_report, 'submission'): submission = result.submission_report.submission kwargs = {'contest_id': contest.id, 'submission_id': submission.id} if my_visible.filter(id=submission.id).exists(): result.url = reverse('submission', kwargs=kwargs) elif controller.can_see_source(request, submission): result.url = reverse('show_submission_source', kwargs=kwargs) user_results.append(result) if result and result.score and \ (not pi.round.is_trial or all_rounds_trial): if user_data['sum'] is None: user_data['sum'] = result.score else: user_data['sum'] += result.score if user_data['sum'] is not None: # This rare corner case with sum being None may happen if all # user's submissions do not have scores (for example the # problems do not support scoring, or all the evaluations # failed with System Errors). if self._allow_zero_score() or user_data['sum'] != 0: data.append(user_data) return data
def test_utils_archived(self): user = User.objects.get(pk=1001) contest = Contest.objects.get(pk="uc") request = RequestFactory().request() request.contest = contest request.user = user self.assertFalse(is_contest_admin(request)) self.assertFalse(is_contest_basicadmin(request)) self.assertTrue(is_contest_observer(request))
def can_see_source(self, request, submission): """Check if submission's source should be visible. :type submission: oioioi.contest.Submission Consider using filter_visible_sources instead, especially for batch queries. """ qs = Submission.objects.filter(id=submission.id) if not (is_contest_admin(request) or is_contest_observer(request)) \ and is_model_submission(submission): return False return self.filter_visible_sources(request, qs).exists()
def get_contest_participant_info_list(self, request, user): """Returns a list of tuples (priority, info). Each entry represents a fragment of HTML with information about the user's participation in the contest. This information will be visible for contest admins. It can be any information an application wants to add. The fragments are sorted by priority (descending) and rendered in that order. The default implementation returns basic info about the contestant: his/her full name, e-mail, the user id, his/her submissions and round time extensions. To add additional info from another application, override this method. For integrity, include the result of the parent implementation in your output. """ res = [(100, render_to_string('contests/basic_user_info.html', { 'request': request, 'target_user_name': self.get_user_public_name(request, user), 'target_user': user, 'user': request.user}))] exts = RoundTimeExtension.objects.filter(user=user, round__contest=request.contest) if exts.exists(): res.append((99, render_to_string('contests/roundtimeextension_info.html', { 'request': request, 'extensions': exts, 'user': request.user}))) if is_contest_basicadmin(request) or is_contest_observer(request): submissions = Submission.objects.filter( problem_instance__contest=request.contest, user=user) \ .order_by('-date').select_related() if submissions.exists(): submission_records = [submission_template_context(request, s) for s in submissions] context = { 'submissions': submission_records, 'show_scores': True } rendered_submissions = render_to_string( 'contests/user_submissions_table.html', context=context, request=request) res.append((50, rendered_submissions)) return res
def get_contest_participant_info_list(self, request, user): """Returns a list of tuples (priority, info). Each entry represents a fragment of HTML with information about the user's participation in the contest. This information will be visible for contest admins. It can be any information an application wants to add. The fragments are sorted by priority (descending) and rendered in that order. The default implementation returns basic info about the contestant: his/her full name, e-mail, the user id, his/her submissions and round time extensions. To add additional info from another application, override this method. For integrity, include the result of the parent implementation in your output. """ res = [(100, render_to_string('contests/basic_user_info.html', { 'request': request, 'target_user_name': self.get_user_public_name(request, user), 'target_user': user, 'user': request.user}))] exts = RoundTimeExtension.objects.filter(user=user, round__contest=request.contest) if exts.exists(): res.append((99, render_to_string('contests/roundtimeextension_info.html', { 'request': request, 'extensions': exts, 'user': request.user}))) if is_contest_admin(request) or is_contest_observer(request): submissions = Submission.objects.filter( problem_instance__contest=request.contest, user=user) \ .order_by('-date').select_related() if submissions.exists(): submission_records = [submission_template_context(request, s) for s in submissions] context = { 'submissions': submission_records, 'show_scores': True } rendered_submissions = render_to_string( 'contests/user_submissions_table.html', context_instance=RequestContext(request, context)) res.append((50, rendered_submissions)) return res
def can_see_problem_statistics(self, request, pi): controller = pi.controller rtimes = rounds_times(request) can_see_problem = controller.can_see_problem(request, pi) if pi.round: can_see_round_results = rtimes[pi.round].public_results_visible( request.timestamp) else: can_see_round_results = False can_observe = is_contest_admin(request) or is_contest_observer(request) return can_see_problem and (can_see_round_results or can_observe)
def filter_visible_sources(self, request, queryset): """Determines which sources the user could see. This usually involves cross-user privileges, like publicizing sources. Default implementations delegates to :meth:`~ContestController.filter_my_visible_submissions`, except for admins and observers, which get full access. Queryset's model should be oioioi.contest.Submission """ if is_contest_admin(request) or is_contest_observer(request): return queryset return self.filter_my_visible_submissions(request, queryset)
def can_see_problem_statistics(self, request, pi): controller = pi.controller rtimes = rounds_times(request, self.contest) can_see_problem = controller.can_see_problem(request, pi) if pi.round: can_see_round_results = rtimes[pi.round].public_results_visible( request.timestamp) else: can_see_round_results = False can_observe = is_contest_admin(request) or is_contest_observer(request) return can_see_problem and (can_see_round_results or can_observe)
def livedata_events_view(request, round_id): user_is_participant = Q( submission__user__participant__contest_id=request.contest.id, submission__user__participant__status="ACTIVE" ) submission_ignored = Q(submission__kind="IGNORED") reports = ( SubmissionReport.objects.filter(user_is_participant) .exclude(submission_ignored) .select_related("submission") .prefetch_related("scorereport_set") ) if (is_contest_admin(request) or is_contest_observer(request)) and "from" in request.GET: # Only admin/observer is allowed to specify 'from' parameter. start_time = datetime.datetime.utcfromtimestamp(int(request.GET["from"])).replace(tzinfo=utc) reports = reports.filter(creation_date__gte=start_time) round = get_object_or_404(request.contest.round_set.all(), pk=round_id) contest_start = round.start_date reports = reports.filter(submission__problem_instance__round=round) if is_contest_admin(request): freeze_time = None else: freeze_time = request.contest.controller.get_round_freeze_time(round) return [ { "submissionId": "START", "reportId": "START", "teamId": "START", "taskId": "START", "submissionTimestamp": int(dateformat.format(request.timestamp, "U")), "judgingTimestamp": int(dateformat.format(contest_start, "U")), "result": "CTRL", } ] + [ { "submissionId": report.submission_id, "reportId": report.pk, "teamId": report.submission.user_id, "taskId": report.submission.problem_instance_id, "submissionTimestamp": int(dateformat.format(report.submission.date, "U")), "judgingTimestamp": int(dateformat.format(report.creation_date, "U")), "result": report.score_report.status if freeze_time is None or report.submission.date < freeze_time else RESULT_FOR_FROZEN_SUBMISSION, } for report in reports.order_by("creation_date") if report.score_report is not None ]
def can_see_source(self, request, submission): """Determines if the current user is allowed to see source of ``submission``. This usually involves cross-user privileges, like publicizing sources. Default implementations delegates to :meth:`~ContestController.filter_my_visible_submissions`, except for admins and observers, which get full access. """ if is_contest_admin(request) or is_contest_observer(request): return True queryset = Submission.objects.filter(id=submission.id) return self.filter_my_visible_submissions(request, queryset).exists()
def livedata_events_view(request, round_id): user_is_participant = \ Q(submission__user__participant__contest_id=request.contest.id, submission__user__participant__status='ACTIVE') submission_ignored = Q(submission__kind='IGNORED') reports = SubmissionReport.objects \ .filter(user_is_participant) \ .exclude(submission_ignored) \ .select_related('submission') \ .prefetch_related('scorereport_set') if (is_contest_admin(request) or is_contest_observer(request)) and \ 'from' in request.GET: # Only admin/observer is allowed to specify 'from' parameter. start_time = datetime.datetime.utcfromtimestamp( int(request.GET['from'])).replace(tzinfo=utc) reports = reports.filter(creation_date__gte=start_time) round = get_object_or_404(request.contest.round_set.all(), pk=round_id) contest_start = round.start_date reports = reports.filter(submission__problem_instance__round=round) if is_contest_admin(request): freeze_time = None else: freeze_time = request.contest.controller.get_round_freeze_time(round) return [{ 'submissionId': 'START', 'reportId': 'START', 'teamId': 'START', 'taskId': 'START', 'submissionTimestamp': int(dateformat.format(request.timestamp, 'U')), 'judgingTimestamp': int(dateformat.format(contest_start, 'U')), 'result': 'CTRL', }] + [{ 'submissionId': report.submission_id, 'reportId': report.pk, 'teamId': report.submission.user_id, 'taskId': report.submission.problem_instance_id, 'submissionTimestamp': int(dateformat.format(report.submission.date, 'U')), 'judgingTimestamp': int(dateformat.format(report.creation_date, 'U')), 'result': report.score_report.status if freeze_time is None or report.submission.date < freeze_time else RESULT_FOR_FROZEN_SUBMISSION, } for report in reports.order_by('creation_date') if report.score_report is not None]
def _rounds_for_ranking(self, request, key=CONTEST_RANKING_KEY): can_see_all = is_contest_admin(request) or is_contest_observer(request) ccontroller = self.contest.controller if not ccontroller.can_see_ranking(request): return queryset = self.contest.round_set.all() if key != CONTEST_RANKING_KEY: queryset = queryset.filter(id=key) if can_see_all: for round in queryset: yield round else: for round in queryset: rtimes = ccontroller.get_round_times(request, round) if not rtimes.is_future(request.timestamp): yield round
def results_visible(self, request, submission): """Determines whether it is a good time to show the submission's results. This method is not used directly in any code outside of the controllers. It's a helper method used in a number of other controller methods, as described. The default implementations uses the round's :attr:`~oioioi.contests.models.Round.results_date`. If it's ``None``, results are not available. Admins are always shown the results. """ if is_contest_admin(request) or is_contest_observer(request): return True round = submission.problem_instance.round rtimes = self.get_round_times(request, round) return rtimes.results_visible(request.timestamp)
def results_visible(self, request, submission): """Determines whether it is a good time to show the submission's results. This method is not used directly in any code outside of the controllers. It's a helper method used in a number of other controller methods, as described. The default implementations uses the round's :attr:`~oioioi.contests.models.Round.results_date`. If it's ``None``, results are not available. Admins are always shown the results. """ if is_contest_basicadmin(request) or is_contest_observer(request): return True round = submission.problem_instance.round rtimes = self.get_round_times(request, round) return rtimes.results_visible(request.timestamp)
def inner(request, round_id): should_cache = not is_contest_admin(request) and not is_contest_observer(request) if not should_cache: return view(request, round_id) cache = get_cache("default") cache_key = "%s/%s/%s" % (view.__name__, request.contest.id, round_id) result = cache.get(cache_key) if result is None: result = view(request, round_id) assert isinstance(result, HttpResponse) cache.set( cache_key, {"content": unicode(result.content), "content_type": result["Content-Type"]}, settings.LIVEDATA_CACHE_TIMEOUT, ) else: result = HttpResponse(result["content"], content_type=result["content_type"]) return result
def inner(request, round_id): should_cache = not is_contest_admin(request) and \ not is_contest_observer(request) if not should_cache: return view(request, round_id) cache_key = '%s/%s/%s' % (view.__name__, request.contest.id, round_id) result = cache.get(cache_key) if result is None: result = view(request, round_id) assert isinstance(result, HttpResponse) cache.set(cache_key, {'content': six.text_type(result.content), 'content_type': result['Content-Type']}, settings.LIVEDATA_CACHE_TIMEOUT) else: result = HttpResponse(result['content'], content_type=result['content_type']) return result
def inner(request, round_id): should_cache = not is_contest_admin(request) and \ not is_contest_observer(request) if not should_cache: return view(request, round_id) cache_key = '%s/%s/%s' % (view.__name__, request.contest.id, round_id) result = cache.get(cache_key) if result is None: result = view(request, round_id) assert isinstance(result, HttpResponse) cache.set( cache_key, { 'content': six.text_type(result.content), 'content_type': result['Content-Type'] }, settings.LIVEDATA_CACHE_TIMEOUT) else: result = HttpResponse(result['content'], content_type=result['content_type']) return result
def filter_visible_reports(self, request, submission, queryset): """Determines which reports the user should be able to see. It need not check whether the submission is visible to the user. The default implementation uses :meth:`~ContestController.results_visible`. :param request: Django request :param submission: instance of :class:`~oioioi.contests.models.Submission` :param queryset: a queryset, initially filtered at least to select only given submission's reports :returns: updated queryset """ if is_contest_basicadmin(request) or is_contest_observer(request): return queryset if self.results_visible(request, submission): return queryset.filter(status='ACTIVE', kind='NORMAL') return queryset.none()
def filter_visible_reports(self, request, submission, queryset): """Determines which reports the user should be able to see. It need not check whether the submission is visible to the user. The default implementation uses :meth:`~ContestController.results_visible`. :param request: Django request :param submission: instance of :class:`~oioioi.contests.models.Submission` :param queryset: a queryset, initially filtered at least to select only given submission's reports :returns: updated queryset """ if is_contest_admin(request) or is_contest_observer(request): return queryset if self.results_visible(request, submission): return queryset.filter(status='ACTIVE', kind='NORMAL') return queryset.none()
def get_cache_key(self, request, key, page=None): """Returns a cache key to be used with group_cache. When caching is enabled, every ranking is cached under a group corresponding to its contest. All cached rankings in a contest are invalidated when a submission is judged. The cache key for each ranking should be constructed in a way that allows to distinguish between its different versions. For example, you may want to display a live version of the ranking to the admins. Users on the other hand should see results only from the finished rounds. Default implementation takes into account a few basic permissions. It is suitable for the above example and many more typical scenarios. However, if the way you generate and display the ranking differs significantly from the default implementation, you should carefully inspect the code below and modify it as needed. The html code of each page of the ranking is cached separately. Also, the whole ranking is cached as an object. :param page specifies the page to be cached. If page=None then this method returns the cache_key for whole ranking. """ if page is not None: try: page = int(page) except ValueError: page = 1 cache_key = ':'.join([key, str(request.user.is_superuser), str(request.user.is_authenticated()), str(is_contest_admin(request)), str(is_contest_observer(request)), str(page)]) return cache_key
def can_generate_user_out(self, request, submission_report): """Determines if the current user is allowed to generate outs from ``submission_report``. Default implementations delegates to ``report_actions_config`` associated with the problem, :meth:`~ContestController.can_see_problem`, :meth:`~ContestController.filter_my_visible_submissions`, except for admins and observers, which get full access. """ submission = submission_report.submission if is_contest_admin(request) or is_contest_observer(request): return True if not has_report_actions_config(submission.problem_instance.problem): return False config = submission.problem_instance.problem.report_actions_config return (config.can_user_generate_outs and submission.user == request.user and self.can_see_problem(request, submission.problem_instance) and self.filter_visible_reports(request, submission, SubmissionReport.objects.filter(id=submission_report.id)) .exists())
def can_see_ranking(self, request): return is_contest_admin(request) or is_contest_observer(request)
def filter_visible_reports(self, request, submission, queryset): if is_contest_admin(request) or is_contest_observer(request): return queryset return queryset.filter(status='ACTIVE', kind__in=self.get_visible_reports_kinds(request, submission))
def has_change_permission(self, request, obj=None): if obj: return False return is_contest_admin(request) or is_contest_observer(request)
def can_see_stats(self, request): return is_contest_admin(request) or is_contest_observer(request)
def pi_results_visible(pi): return rtimes[pi.round].public_results_visible(request.timestamp) \ or is_contest_admin(request) or is_contest_observer(request)
def _rounds_for_ranking(self, request, partial_key=CONTEST_RANKING_KEY): can_see_all = is_contest_admin(request) or is_contest_observer(request) return self._iter_rounds(can_see_all, request.timestamp, partial_key, request)
class RankingController(RegisteredSubclassesBase, ObjectWithMixins): """Ranking system uses two types of keys: "partial key"s and "full key"s. Please note that full keys are abbreviated in the code as "key"s. A pair (request, partial_key) should allow to build a full key, while a partial_key can always be extracted from the full key. partial keys identify the rounds to display and are used everywhere outside controllers and rankingsd (e.g. in views and urls). However, the actual ranking contents can depend on many other factors, like user permissions. This was the reason for introduction of full keys, which are always sufficient to choose the right data for serialization and display. """ modules_with_subclasses = ['controllers'] abstract = True PERMISSION_CHECKERS = [ lambda request: 'admin' if is_contest_admin(request) else None, lambda request: 'observer' if is_contest_observer(request) else None, lambda request: 'regular' ] def get_partial_key(self, key): """Extracts partial key from a full key.""" return key.split('#')[1] def replace_partial_key(self, key, new_partial): """Replaces partial key in a full key""" return key.split('#')[0] + '#' + new_partial def get_full_key(self, request, partial_key): """Returns a full key associated with request and partial_key""" for checker in self.PERMISSION_CHECKERS: res = checker(request) if res is not None: return res + '#' + partial_key def _key_permission(self, key): """Returns a permission level associated with given full key""" return key.split('#')[0] def is_admin_key(self, key): """Returns true if a given full key corresponds to users with administrative permissions. """ return self._key_permission(key) == 'admin' def __init__(self, contest): self.contest = contest def available_rankings(self, request): """Returns a list of available rankings. Each ranking is a pair ``(key, description)``. """ raise NotImplementedError def can_search_for_users(self): """Determines if in this ranking, searching for users is enabled.""" return False def find_user_position(self, request, partial_key, user): """Returns user's position in the ranking. User should be an object of class User, not a string with username. If user is not in the ranking, None is returned. """ raise NotImplementedError def get_rendered_ranking(self, request, partial_key): """Retrieves ranking generated by rankingsd. You should never override this function. It will be responsible for communication with rankingsd and use render_ranking for actual HTML generation. Feel free to override render_ranking to customize its logic. If the ranking is still being generated, or the user requested an invalid page, displays an appropriate message. """ page_nr = request.GET.get('page', 1) key = self.get_full_key(request, partial_key) # Let's pretend the ranking is always up-to-date during tests. if getattr(settings, 'MOCK_RANKINGSD', False): data = self.serialize_ranking(key) html = self._render_ranking_page(key, data, page_nr) print(data) return mark_safe(html) ranking = Ranking.objects.get_or_create(contest=self.contest, key=key)[0] try: page = ranking.pages.get(nr=page_nr) except RankingPage.DoesNotExist: # The ranking hasn't been yet generated if page_nr == 1: return mark_safe( render_to_string("rankings/generating_ranking.html")) return mark_safe(render_to_string("rankings/no_page.html")) context = { 'ranking_html': mark_safe(page.data), 'is_up_to_date': ranking.is_up_to_date(), } return mark_safe( render_to_string("rankings/rendered_ranking.html", context)) def get_serialized_ranking(self, key): return self.serialize_ranking(key) def build_ranking(self, key): """Serializes data and renders html for given key. Results are processed using serialize_ranking, and then as many pages as needed are rendered. Returns a tuple containing serialized data and a list of strings, that are html code of ranking pages. """ data = self.serialize_ranking(key) pages = [] num_participants = len(data['rows']) on_page = data['participants_on_page'] num_pages = (num_participants + on_page - 1) / on_page num_pages = max(num_pages, 1) # Render at least a single page for i in range(1, num_pages + 1): pages.append(self._render_ranking_page(key, data, i)) return data, pages def _fake_request(self, page): """Creates a fake request used to render ranking. Pagination engine requires access to request object, so it can extract page number from GET parameters. """ fake_req = RequestFactory().get('/?page=' + str(page)) fake_req.user = AnonymousUser() fake_req.contest = self.contest # This is required by dj-pagination # Normally they monkey patch this function in their middleware fake_req.page = lambda _: page return fake_req def _render_ranking_page(self, key, data, page): raise NotImplementedError def render_ranking_to_csv(self, request, partial_key): raise NotImplementedError def serialize_ranking(self, key): """Returns some data (representing ranking). This data will be used by :meth:`render_ranking` to generate the html code. """ raise NotImplementedError
def pi_results_visible(pi): return (rtimes[pi.round].public_results_visible(request.timestamp) or is_contest_admin(request) or is_contest_observer(request))