def has_add_permission(self, request): return is_contest_basicadmin(request)
def has_change_permission(self, request, obj=None): if obj: return False return is_contest_basicadmin(request)
def has_add_permission(self, request): # Correct object contest ensured by form. return is_contest_basicadmin(request)
def has_change_permission(self, request, obj=None): if obj: return is_superuser(request) or \ (is_contest_basicadmin(request) and obj.contest == request.contest) return self.has_add_permission(request)
def __init__(self, request, *args, **kwargs): problem_instance = kwargs.pop('problem_instance', None) if problem_instance is None: # if problem_instance does not exist any from the current # contest is chosen. To change in future. # ALSO in mailsubmit.forms contest = request.contest assert contest is not None problem_instances = ProblemInstance.objects \ .filter(contest=contest) problem_instance = problem_instances[0] else: problem_instances = [problem_instance] contest = None self.all_problem_instances = problem_instances # Default kind is selected based on # the first problem_instance assigned to this form. # This is an arbitrary choice. self.kind = kwargs.pop( 'kind', problem_instance.controller.get_default_submission_kind( request, problem_instance=problem_instance)) problem_filter = kwargs.pop('problem_filter', None) self.request = request # taking the available problems pis = self.get_problem_instances() if problem_filter: pis = problem_filter(pis) pi_choices = [(pi.id, six.text_type(pi)) for pi in pis] # pylint: disable=non-parent-init-called # init form with previously sent data forms.Form.__init__(self, *args, **kwargs) # prepare problem instance selector pi_field = self.fields['problem_instance_id'] pi_field.widget.attrs['class'] = 'input-xlarge' self._set_field_show_always('problem_instance_id') if len(pi_choices) > 1: pi_field.choices = [('', '')] + pi_choices else: pi_field.choices = pi_choices narrow_input_field(pi_field) # if contest admin, add kind and 'as other user' field if contest and is_contest_basicadmin(request): self.fields['user'] = UserSelectionField( label=_("User"), hints_url=reverse('contest_user_hints', kwargs={'contest_id': request.contest.id}), initial=request.user) self._set_field_show_always('user') def clean_user(): try: user = self.cleaned_data['user'] if user == request.user: return user if not request.user.is_superuser: contest.controller.registration_controller() \ .filter_participants( User.objects.filter(pk=user.pk)).get() return user except User.DoesNotExist: raise forms.ValidationError( _("User does not exist or " "you do not have enough privileges")) self.clean_user = clean_user self.fields['kind'] = forms.ChoiceField(choices=[ ('NORMAL', _("Normal")), ('IGNORED', _("Ignored")) ], initial=self.kind, label=_("Kind")) self._set_field_show_always('kind') narrow_input_fields([self.fields['kind'], self.fields['user']]) # adding additional fields, etc for pi in pis: pi.controller.adjust_submission_form(request, self, pi) self._set_default_fields_attributes() # fix field order (put kind and user at the end) self._move_field_to_end('user') self._move_field_to_end('kind')
def has_delete_permission(self, request, obj=None): return is_contest_basicadmin(request)
def get_contest_permissions(request, response): response['is_contest_admin'] = is_contest_admin(request) response['is_contest_basicadmin'] = is_contest_basicadmin(request) return response
def filter_my_visible_submissions(self, request, queryset): if not is_contest_basicadmin(request): queryset = queryset.exclude(kind='USER_OUTS') return super(ProgrammingContestController, self). \ filter_my_visible_submissions(request, queryset)
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 can_add_problems(request): return request.user.has_perm('problems.problems_db_admin') \ or is_contest_basicadmin(request)
def ranking_view(request, key=None): rcontroller = request.contest.controller.ranking_controller() choices = rcontroller.available_rankings(request) if key is None: key = choices[0][0] if key not in next(zip(*choices)): raise Http404 context = dict() ranking = None if rcontroller.can_search_for_users(): form = FilterUsersInRankingForm(request, request.GET) context['form'] = form if form.is_valid(): user = form.cleaned_data.get('user') # Everybody can search for themselves. # Contest admins can search for anyone. if user and (is_contest_basicadmin(request) \ or user == request.user): found_pos = rcontroller.find_user_position(request, key, user) if found_pos: users_per_page = getattr(settings, 'PARTICIPANTS_ON_PAGE', 100) found_page = ((found_pos - 1) // users_per_page) + 1 get_dict = request.GET.copy() get_dict.pop('user') get_dict['page'] = found_page return redirect(request.path + '?' + get_dict.urlencode() + '#' + str(user.id)) else: msg = _("User is not in the ranking.") # Admin should receive error in form, # whereas user should see it as an error message, # because there is no form then. if is_contest_basicadmin(request): form._errors['user'] = form.error_class([msg]) else: messages.error(request, msg) if ranking is None: # Changing request.GET is necessary! # The pagination library not only truncates the list of objects, # but also generates the links to other pages. # It simply copies the current url and replaces 'page' # with another number. If there is a different GET parameter, # it is included (without any change) in the url to another page. # If a user requests a page, he can provide any number of useless # GET parameters, which will be shown in urls to other pages. # The ranking page could be cached and those urls could be # visible to other users, giving them links to the ranking # with strange arguments (e.g. arguments, which perform a search). # Below, only 'user' key is deleted, because that's the only argument # performing an action, and I'd like to change request.GET # as less as possible. # This solution does not prevent users from sending "messages" # between each other (using these GET parameters). request.GET = request.GET.copy() try: request.GET.pop('user') except KeyError: pass ranking = rcontroller.get_rendered_ranking(request, key) context['choices'] = choices context['ranking'] = ranking context['key'] = key return TemplateResponse(request, 'rankings/ranking_view.html', context)
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_basicadmin(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. """ try: page_nr = int(request.GET.get('page', 1)) except ValueError: return HttpResponseBadRequest("Page number must be integer") 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 _rounds_for_ranking(self, request, partial_key=CONTEST_RANKING_KEY): can_see_all = is_contest_basicadmin(request) or is_contest_observer( request) return self._iter_rounds(can_see_all, request.timestamp, partial_key, request)
def check_repeated_submission(self, request, problem_instance, form): return not is_contest_basicadmin(request) and \ form.kind == 'NORMAL' and \ getattr(settings, 'WARN_ABOUT_REPEATED_SUBMISSION', False)
def make_context(self, request_or_context): if isinstance(request_or_context, ContestControllerContext): return request_or_context return ContestControllerContext(request_or_context.contest, request_or_context.timestamp, is_contest_basicadmin(request_or_context))
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 get_submissions_limit(self, request, problem_instance, kind='NORMAL'): if is_contest_basicadmin(request): return None return problem_instance.problem.controller \ .get_submissions_limit(request, problem_instance, kind)
def is_admin(self, request, report): return is_contest_basicadmin(request)
def contest_files_view(request): additional_files = attachment_registry.to_list(request=request) contest_files = ContestAttachment.objects.filter(contest=request.contest) \ .filter(Q(round__isnull=True) | Q(round__in=visible_rounds(request))) \ .select_related('round') if not is_contest_basicadmin(request): contest_files = contest_files.filter( Q(pub_date__isnull=True) | Q(pub_date__lte=request.timestamp)) round_file_exists = contest_files.filter(round__isnull=False).exists() problem_instances = visible_problem_instances(request) problem_ids = [pi.problem_id for pi in problem_instances] problem_files = ProblemAttachment.objects \ .filter(problem_id__in=problem_ids) \ .select_related('problem') add_category_field = round_file_exists or problem_files.exists() rows = [{ 'category': cf.round if cf.round else '', 'name': cf.download_name, 'description': cf.description, 'link': reverse('contest_attachment', kwargs={ 'contest_id': request.contest.id, 'attachment_id': cf.id }), 'pub_date': cf.pub_date } for cf in contest_files] rows += [{ 'category': pf.problem, 'name': pf.download_name, 'description': pf.description, 'link': reverse('problem_attachment', kwargs={ 'contest_id': request.contest.id, 'attachment_id': pf.id }), 'pub_date': None } for pf in problem_files] rows += [{ 'category': af.get('category'), 'name': af.get('name'), 'description': af.get('description'), 'link': af.get('link'), 'pub_date': af.get('pub_date') } for af in additional_files] rows.sort(key=itemgetter('name')) return TemplateResponse( request, 'contests/files.html', { 'files': rows, 'files_on_page': getattr(settings, 'FILES_ON_PAGE', 100), 'add_category_field': add_category_field, 'show_pub_dates': True })