diff --git a/dmoj/urls.py b/dmoj/urls.py index 6dd5a306d..dc5698347 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -286,6 +286,8 @@ def paged_list_view(view, name): path('/users/', organization.OrganizationUsers.as_view(), name='organization_users'), path('/join', organization.JoinOrganization.as_view(), name='join_organization'), path('/leave', organization.LeaveOrganization.as_view(), name='leave_organization'), + path('/get-submissions-data', organization.GetSubmissionsData.as_view(), + name='get_submissions_data'), path('/edit', organization.EditOrganization.as_view(), name='edit_organization'), path('/kick', organization.KickUserWidgetView.as_view(), name='organization_user_kick'), path('/problems/', organization.ProblemListOrganization.as_view(), name='problem_list_organization'), diff --git a/judge/forms.py b/judge/forms.py index bbd769da5..16f2beece 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -458,6 +458,17 @@ class Meta: }) +class OrganizationGetSubmissionsDataForm(Form): + start_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label=_('From')) + end_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label=_('To')) + + def get_data(self): + start_time = self.cleaned_data['start_time'] + end_time = self.cleaned_data['end_time'] + + return start_time, end_time + + class SocialAuthMixin: def _has_social_auth(self, key): return (getattr(settings, 'SOCIAL_AUTH_%s_KEY' % key, None) and diff --git a/judge/views/organization.py b/judge/views/organization.py index 1bd68d70b..7060608cc 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -1,3 +1,4 @@ +import csv from functools import cached_property from django import forms @@ -10,7 +11,7 @@ from django.db.models.expressions import F, Value from django.db.models.functions import Coalesce from django.forms import Form, modelformset_factory -from django.http import Http404, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone @@ -20,9 +21,10 @@ from django.views.generic.detail import SingleObjectMixin, SingleObjectTemplateResponseMixin from reversion import revisions -from judge.forms import OrganizationForm +from judge.forms import OrganizationForm, OrganizationGetSubmissionsDataForm from judge.models import BlogPost, Comment, Contest, Language, Organization, OrganizationRequest, \ Problem, Profile +from judge.models.submission import Submission from judge.tasks import on_new_problem from judge.utils.infinite_paginator import InfinitePaginationMixin from judge.utils.ranker import ranker @@ -32,8 +34,9 @@ from judge.views.problem import ProblemCreate, ProblemList from judge.views.submission import SubmissionsListBase + __all__ = ['OrganizationList', 'OrganizationHome', 'OrganizationUsers', 'OrganizationMembershipChange', - 'JoinOrganization', 'LeaveOrganization', 'EditOrganization', 'RequestJoinOrganization', + 'JoinOrganization', 'LeaveOrganization', 'GetSubmissionsData', 'EditOrganization', 'RequestJoinOrganization', 'OrganizationRequestDetail', 'OrganizationRequestView', 'OrganizationRequestLog', 'KickUserWidgetView'] @@ -217,6 +220,40 @@ def handle(self, request, org, profile): profile.organizations.remove(org) +class GetSubmissionsData(LoginRequiredMixin, PublicOrganizationMixin, TitleMixin, SingleObjectMixin, FormView): + form_class = OrganizationGetSubmissionsDataForm + template_name = 'organization/get-submissions-data.html' + title = gettext_lazy('Get submissions data') + success_url = '.' + + def form_valid(self, form): + if not self.organization.is_admin(self.request.profile): + raise PermissionDenied() + if form.cleaned_data['start_time'] > form.cleaned_data['end_time']: + form.add_error('start_time', _('Start time must be before end time.')) + return self.form_invalid(form) + return self.download(form) + + def download(self, form): + start_time, end_time = form.get_data() + org = self.organization + submissions = Submission.objects.filter(judged_date__gte=start_time, + judged_date__lte=end_time, + problem__organizations=org).order_by('judged_date').only( + 'judged_date', 'problem', 'user', 'result') + respone = HttpResponse(content_type='text/csv') + respone['Content-Disposition'] = 'attachment; filename="submissions.csv"' + respone.write(u'\ufeff'.encode('utf8')) + + writer = csv.writer(respone) + writer.writerow(['judged_date', 'problem_id', 'user', 'result']) + for submission in submissions: + writer.writerow([submission.judged_date.ctime(), submission.problem.code, + submission.user.user.username, submission.result]) + + return respone + + class OrganizationRequestForm(Form): reason = forms.CharField(widget=forms.Textarea) @@ -485,6 +522,7 @@ def get_context_data(self, **kwargs): context['title'] = self.object.name context['can_edit'] = self.can_edit_organization() context['is_member'] = self.request.profile in self.object + context['form'] = OrganizationGetSubmissionsDataForm() context['post_comment_counts'] = { int(page[2:]): count for page, count in diff --git a/templates/organization/get-submissions-data.html b/templates/organization/get-submissions-data.html new file mode 100644 index 000000000..c7c5b5931 --- /dev/null +++ b/templates/organization/get-submissions-data.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} + +{% block media %} + {{ form.media.css }} + +{% endblock %} + +{% block body %} +
+
+ {% csrf_token %} +
+
+
{{ form.start_time.label }}
+
{{ form.start_time }}
+
+
+
{{ form.end_time.label }}
+
{{ form.end_time }}
+
+
+ + {% if form.has_error('start_time') %} + {{ form.errors['start_time'] }} + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/organization/home.html b/templates/organization/home.html index 48d9c757f..b38333aaa 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -127,6 +127,11 @@

{{ _('Controls') }}

{{ _('Admin organization') }} {% endif %} + {% if can_edit %} +
+ {{ _('Download submissions data') }} +
+ {% endif %}