From d79706ba2cf3d80ca5be23e35e87d2283147515c Mon Sep 17 00:00:00 2001 From: winprn Date: Tue, 19 Dec 2023 21:57:19 +0700 Subject: [PATCH 1/4] allow org admin to download submissions during specified time --- dmoj/urls.py | 2 ++ judge/forms.py | 5 ++++ judge/views/organization.py | 42 +++++++++++++++++++++++++++++--- templates/organization/home.html | 14 +++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/dmoj/urls.py b/dmoj/urls.py index 19e86d5e5..70f9f0d19 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -282,6 +282,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 6ee354c18..09bec7f9a 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -432,6 +432,11 @@ class Meta: }) +class OrganizationGetSubmissionsDataForm(Form): + start_time = DateTimeInput(format='%Y-%m-%d %H:%M:%S', attrs={'class': 'datetimefield'}) + end_time = DateTimeInput(format='%Y-%m-%d %H:%M:%S', attrs={'class': 'datetimefield'}) + + 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 54fb59390..377b55b43 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,39 @@ def handle(self, request, org, profile): profile.organizations.remove(org) +class GetSubmissionsData(LoginRequiredMixin, PublicOrganizationMixin, SingleObjectMixin, View, Form): + form = OrganizationGetSubmissionsDataForm + + def get(self, request, *args, **kwargs): + org = self.get_object() + profile = request.profile + if not org.is_admin(profile): + raise PermissionDenied() + return self.handle(request, org, profile) + + def handle(self, request, org, profile): + star_time = request.GET.get('start_time', None) + end_time = request.GET.get('end_time', None) + print(star_time, end_time) + if star_time == '' or end_time == '': + return generic_message(request, _('Get submissions data'), _('Please input start time and end time.')) + submissions = Submission.objects.filter(judged_date__gte=star_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) diff --git a/templates/organization/home.html b/templates/organization/home.html index bfb394305..93bf0eec2 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -127,6 +127,20 @@

{{ _('Controls') }}

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

Download submission data

+
+ {% csrf_token %} +
+ + - + +
+ +
+
+ {% endif %} From 75eecb4fd6740e0064eb78eae6d018bf5f5860bb Mon Sep 17 00:00:00 2001 From: winprn Date: Tue, 19 Dec 2023 22:12:26 +0700 Subject: [PATCH 2/4] remove printing --- judge/views/organization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index 377b55b43..ba81b3427 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -233,7 +233,6 @@ def get(self, request, *args, **kwargs): def handle(self, request, org, profile): star_time = request.GET.get('start_time', None) end_time = request.GET.get('end_time', None) - print(star_time, end_time) if star_time == '' or end_time == '': return generic_message(request, _('Get submissions data'), _('Please input start time and end time.')) submissions = Submission.objects.filter(judged_date__gte=star_time, From 50373cd6ba765e3525a79dac8b92e390a3b39082 Mon Sep 17 00:00:00 2001 From: winprn Date: Thu, 21 Dec 2023 20:45:44 +0700 Subject: [PATCH 3/4] using django form instead of HTML elements --- judge/forms.py | 4 ++-- judge/views/organization.py | 10 +++++----- templates/organization/home.html | 6 +----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/judge/forms.py b/judge/forms.py index 09bec7f9a..c8603f2b3 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -433,8 +433,8 @@ class Meta: class OrganizationGetSubmissionsDataForm(Form): - start_time = DateTimeInput(format='%Y-%m-%d %H:%M:%S', attrs={'class': 'datetimefield'}) - end_time = DateTimeInput(format='%Y-%m-%d %H:%M:%S', attrs={'class': 'datetimefield'}) + start_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label='') + end_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label='') class SocialAuthMixin: diff --git a/judge/views/organization.py b/judge/views/organization.py index ba81b3427..bdce49a41 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -220,19 +220,18 @@ def handle(self, request, org, profile): profile.organizations.remove(org) -class GetSubmissionsData(LoginRequiredMixin, PublicOrganizationMixin, SingleObjectMixin, View, Form): - form = OrganizationGetSubmissionsDataForm - +class GetSubmissionsData(LoginRequiredMixin, PublicOrganizationMixin, SingleObjectMixin, View): def get(self, request, *args, **kwargs): org = self.get_object() profile = request.profile if not org.is_admin(profile): raise PermissionDenied() - return self.handle(request, org, profile) + return self.handle(request, org) - def handle(self, request, org, profile): + def handle(self, request, org): star_time = request.GET.get('start_time', None) end_time = request.GET.get('end_time', None) + if star_time == '' or end_time == '': return generic_message(request, _('Get submissions data'), _('Please input start time and end time.')) submissions = Submission.objects.filter(judged_date__gte=star_time, @@ -519,6 +518,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/home.html b/templates/organization/home.html index 93bf0eec2..2b4485dd8 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -132,11 +132,7 @@

{{ _('Controls') }}

Download submission data

{% csrf_token %} -
- - - - -
+ {{ form.start_time }} - {{ form.end_time }}
From 2255b17047044c6c8e1c12b5343dbe6a887e2604 Mon Sep 17 00:00:00 2001 From: winprn Date: Wed, 27 Dec 2023 22:21:41 +0700 Subject: [PATCH 4/4] use FormView --- judge/forms.py | 10 ++- judge/views/organization.py | 31 ++++---- .../organization/get-submissions-data.html | 72 +++++++++++++++++++ templates/organization/home.html | 7 +- 4 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 templates/organization/get-submissions-data.html diff --git a/judge/forms.py b/judge/forms.py index c8603f2b3..bf9a6903a 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -433,8 +433,14 @@ class Meta: class OrganizationGetSubmissionsDataForm(Form): - start_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label='') - end_time = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'required': True}), label='') + 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: diff --git a/judge/views/organization.py b/judge/views/organization.py index bdce49a41..32d16ac12 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -220,21 +220,24 @@ def handle(self, request, org, profile): profile.organizations.remove(org) -class GetSubmissionsData(LoginRequiredMixin, PublicOrganizationMixin, SingleObjectMixin, View): - def get(self, request, *args, **kwargs): - org = self.get_object() - profile = request.profile - if not org.is_admin(profile): - raise PermissionDenied() - return self.handle(request, 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 handle(self, request, org): - star_time = request.GET.get('start_time', None) - end_time = request.GET.get('end_time', None) - - if star_time == '' or end_time == '': - return generic_message(request, _('Get submissions data'), _('Please input start time and end time.')) - submissions = Submission.objects.filter(judged_date__gte=star_time, + 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') 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 2b4485dd8..733aacab3 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -129,12 +129,7 @@

{{ _('Controls') }}

{% endif %} {% if can_edit %}
-

Download submission data

-
- {% csrf_token %} - {{ form.start_time }} - {{ form.end_time }} - -
+ {{ _('Download submissions data') }}
{% endif %}