From 2942a79ab299f04505f6aca4dc98e2afa4eeff50 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 10 Jan 2024 17:49:40 +0100 Subject: [PATCH 01/34] Fix adding new tags from problem admin (#304) --- oioioi/problems/forms.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/oioioi/problems/forms.py b/oioioi/problems/forms.py index 8b1216c78..66d6ea5fb 100644 --- a/oioioi/problems/forms.py +++ b/oioioi/problems/forms.py @@ -165,9 +165,10 @@ def __init__(self, *args, **kwargs): class LocalizationFormset(forms.models.BaseInlineFormSet): def __init__(self, *args, **kwargs): - kwargs['initial'] = _localized_formset_get_initial( - kwargs['instance'].localizations - ) + if kwargs['instance'].pk: + kwargs['initial'] = _localized_formset_get_initial( + kwargs['instance'].localizations + ) super(LocalizationFormset, self).__init__(*args, **kwargs) self.min_num = self.max_num = len(settings.LANGUAGES) for form in self.forms: From f82adf01e824d87b3442118bcf3449e5bbfda240 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Sat, 9 Mar 2024 11:40:34 +0100 Subject: [PATCH 02/34] Create monitoring site for contests --- oioioi/statistics/urls.py | 1 + oioioi/statistics/views.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/oioioi/statistics/urls.py b/oioioi/statistics/urls.py index e26596c42..b75e1abb2 100644 --- a/oioioi/statistics/urls.py +++ b/oioioi/statistics/urls.py @@ -16,4 +16,5 @@ name='statistics_view_without_object', ), re_path(r'^stat/$', views.statistics_view, name='statistics_main'), + re_path(r'^monitoring/$', views.monitoring_view, name='monitoring'), ] diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 584738aa8..43097a06b 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -116,3 +116,13 @@ def statistics_view( 'links': links(request), }, ) +@contest_admin_menu_registry.register_decorator( + _("Monitoring"), + lambda request: reverse( + 'monitoring', kwargs={'contest_id': request.contest.id} + ), + condition=(is_contest_admin | is_contest_observer), + order=110, +) +def monitoring_view(request): + raise NotImplementedError From 9e9b399d038dcab3368c50f93a026844fc88d23b Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Sat, 9 Mar 2024 11:46:43 +0100 Subject: [PATCH 03/34] Add monitoring template --- .../statistics/templates/statistics/monitoring.html | 12 ++++++++++++ oioioi/statistics/views.py | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 oioioi/statistics/templates/statistics/monitoring.html diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html new file mode 100644 index 000000000..cacbb92ac --- /dev/null +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -0,0 +1,12 @@ +{% extends "base-with-menu.html" %} +{% load i18n pagination_tags %} + +{% block title %}{% trans "Monitoring" %}{% endblock %} + +{% block main-content %} + {{ head|safe }} + +
+ {% trans "No plots are available at the moment." %} +
+{% endblock %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 43097a06b..cd9f2bef0 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -125,4 +125,11 @@ def statistics_view( order=110, ) def monitoring_view(request): - raise NotImplementedError + return TemplateResponse( + request, + 'statistics/monitoring.html', + { + 'title': 'Monitoring', + 'links': links(request), + }, + ) From 5d23a7ed25e2d602afa5455b29920575e71b7b2f Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Sat, 9 Mar 2024 12:24:29 +0100 Subject: [PATCH 04/34] Implement round times monitoring --- .../templates/statistics/_rounds_info.html | 24 +++++++++++++++++++ .../templates/statistics/monitoring.html | 6 +++-- oioioi/statistics/views.py | 12 ++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 oioioi/statistics/templates/statistics/_rounds_info.html diff --git a/oioioi/statistics/templates/statistics/_rounds_info.html b/oioioi/statistics/templates/statistics/_rounds_info.html new file mode 100644 index 000000000..c00ec0173 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_rounds_info.html @@ -0,0 +1,24 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "Rounds" %}

+ + + + + + + + + + {% for round, time in rounds_times %} + + + + + + {% endfor %} + +
{% trans "Round name" %} {% trans "Start time" %} {% trans "End time" %}
{{ round }}{{ time.start }}{{ time.end }}
+
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index cacbb92ac..e35e635a2 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -6,7 +6,9 @@ {% block main-content %} {{ head|safe }} -
- {% trans "No plots are available at the moment." %} +
+
+ {% include "statistics/_rounds_info.html" %} +
{% endblock %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index cd9f2bef0..40672e2db 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -1,3 +1,6 @@ +import inspect +from pprint import pprint + from django.core.exceptions import PermissionDenied from django.http import Http404 from django.template.response import TemplateResponse @@ -12,7 +15,7 @@ can_enter_contest, contest_exists, is_contest_admin, - is_contest_observer, + is_contest_observer, rounds_times, ) from oioioi.statistics.controllers import statistics_categories from oioioi.statistics.utils import any_statistics_avaiable, can_see_stats, render_head @@ -116,6 +119,8 @@ def statistics_view( 'links': links(request), }, ) + + @contest_admin_menu_registry.register_decorator( _("Monitoring"), lambda request: reverse( @@ -125,11 +130,14 @@ def statistics_view( order=110, ) def monitoring_view(request): + r_times = rounds_times(request, request.contest).items() + return TemplateResponse( request, 'statistics/monitoring.html', { - 'title': 'Monitoring', + 'title': _("Monitoring"), + 'rounds_times': r_times, 'links': links(request), }, ) From c7bd8de00b89fd49036690021e8a4d8d88f008fc Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Sun, 10 Mar 2024 10:15:59 +0100 Subject: [PATCH 05/34] Add contest permissions info to monitoring page --- .../statistics/_permissions_info.html | 20 +++++++++++++++++++ .../templates/statistics/monitoring.html | 3 +++ oioioi/statistics/views.py | 12 ++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 oioioi/statistics/templates/statistics/_permissions_info.html diff --git a/oioioi/statistics/templates/statistics/_permissions_info.html b/oioioi/statistics/templates/statistics/_permissions_info.html new file mode 100644 index 000000000..2d9214082 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_permissions_info.html @@ -0,0 +1,20 @@ +{% load i18n pagination_tags %} +
+

{% trans "Permissions" %}

+ + + + + + + + + {% for permission, count in permissions_count.items %} + + + + + {% endfor %} + +
{% trans "Permission" %} {% trans "Count" %}
{{ permission }}{{ count }}
+
\ No newline at end of file diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index e35e635a2..416ad0721 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -7,6 +7,9 @@ {{ head|safe }}
+
+ {% include "statistics/_permissions_info.html" %} +
{% include "statistics/_rounds_info.html" %}
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 40672e2db..84cc3d364 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -10,13 +10,14 @@ from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry -from oioioi.contests.models import ProblemInstance +from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions from oioioi.contests.utils import ( can_enter_contest, contest_exists, is_contest_admin, is_contest_observer, rounds_times, ) +from oioioi.participants.models import Participant from oioioi.statistics.controllers import statistics_categories from oioioi.statistics.utils import any_statistics_avaiable, can_see_stats, render_head @@ -131,6 +132,14 @@ def statistics_view( ) def monitoring_view(request): r_times = rounds_times(request, request.contest).items() + permissions_count = { + permission_name: (ContestPermission + .objects + .filter(contest_id=request.contest.id, permission=permission_cls) + .count()) + for permission_cls, permission_name in contest_permissions + } + permissions_count['Participant'] = Participant.objects.filter(contest_id=request.contest.id).count() return TemplateResponse( request, @@ -138,6 +147,7 @@ def monitoring_view(request): { 'title': _("Monitoring"), 'rounds_times': r_times, + 'permissions_count': permissions_count, 'links': links(request), }, ) From effe9b1810458fc7fc3e2d06ff53d30428688c96 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 17:34:37 +0100 Subject: [PATCH 06/34] Added General info tab. -Added q_size and q_size_global to General info. --- .../templates/statistics/_general_info.html | 24 +++++++++++++++++++ .../templates/statistics/monitoring.html | 3 +++ oioioi/statistics/views.py | 10 ++++++++ 3 files changed, 37 insertions(+) create mode 100644 oioioi/statistics/templates/statistics/_general_info.html diff --git a/oioioi/statistics/templates/statistics/_general_info.html b/oioioi/statistics/templates/statistics/_general_info.html new file mode 100644 index 000000000..477a6e699 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_general_info.html @@ -0,0 +1,24 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "General info" %}

+ + + + + + + + + + + + + + + + + +
{% trans "Name" %} {% trans "Value" %}
Queued jobs in this contest{{ q_size }}
Queued jobs on this server{{ q_size_global }}
+
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index e35e635a2..6c95828f2 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -10,5 +10,8 @@
{% include "statistics/_rounds_info.html" %}
+
+ {% include "statistics/_general_info.html" %} +
{% endblock %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 40672e2db..e3254db68 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -18,6 +18,7 @@ is_contest_observer, rounds_times, ) from oioioi.statistics.controllers import statistics_categories +from oioioi.evalmgr.models import QueuedJob from oioioi.statistics.utils import any_statistics_avaiable, can_see_stats, render_head @@ -131,6 +132,13 @@ def statistics_view( ) def monitoring_view(request): r_times = rounds_times(request, request.contest).items() + q_size = (QueuedJob.objects + .filter(submission__problem_instance__contest=request.contest) + .count()) + q_size_global = (QueuedJob.objects + .count()) + + return TemplateResponse( request, @@ -139,5 +147,7 @@ def monitoring_view(request): 'title': _("Monitoring"), 'rounds_times': r_times, 'links': links(request), + 'q_size': q_size, + 'q_size_global': q_size_global, }, ) From bf686f6416a9ad2e743974010415edecae8bdb78 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 20 Mar 2024 13:06:28 +0100 Subject: [PATCH 07/34] Add relative start/end times to monitoring --- .../templates/statistics/_rounds_info.html | 12 +++++++---- oioioi/statistics/views.py | 21 +++++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/oioioi/statistics/templates/statistics/_rounds_info.html b/oioioi/statistics/templates/statistics/_rounds_info.html index c00ec0173..6a026ada8 100644 --- a/oioioi/statistics/templates/statistics/_rounds_info.html +++ b/oioioi/statistics/templates/statistics/_rounds_info.html @@ -7,15 +7,19 @@

{% trans "Rounds" %}

{% trans "Round name" %} {% trans "Start time" %} + {% trans "Time left to start" %} {% trans "End time" %} + {% trans "Time left to end" %} - {% for round, time in rounds_times %} + {% for round_info in rounds_times%} - {{ round }} - {{ time.start }} - {{ time.end }} + {{ round_info.name }} + {{ round_info.start }} + {{ round_info.start_relative }} + {{ round_info.end }} + {{ round_info.end_relative }} {% endfor %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 84cc3d364..34178b50d 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -1,4 +1,4 @@ -import inspect +from datetime import datetime from pprint import pprint from django.core.exceptions import PermissionDenied @@ -6,6 +6,7 @@ from django.template.response import TemplateResponse from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from pytz import UTC from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition @@ -131,7 +132,23 @@ def statistics_view( order=110, ) def monitoring_view(request): - r_times = rounds_times(request, request.contest).items() + cur_time = UTC.localize(datetime.now()) + r_times = [] + for round_, rt in rounds_times(request, request.contest).items(): + round_time_info = {'name': str(round_)} + round_time_info['start'] = rt.start or _("Not set") + if rt.start: + round_time_info['start_relative'] = str(rt.start - cur_time)[:-7] if rt.is_future(cur_time) else _("Started") + else: + round_time_info['start_relative'] = _("Not set") + round_time_info['end'] = rt.end or _("Not set") + if rt.end: + round_time_info['end_relative'] = str(rt.end - cur_time)[:-7] if not rt.is_past(cur_time) else _("Finished") + else: + round_time_info['end_relative'] = _("Not set") + r_times.append(round_time_info) + + print(f'TUTAJ: {r_times}') permissions_count = { permission_name: (ContestPermission .objects From 215fbb341bb55e6e6c6f9508830885d020b73bce Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 20 Mar 2024 13:10:56 +0100 Subject: [PATCH 08/34] Cleanup --- oioioi/statistics/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index eb5c52b72..7f9401b32 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -149,7 +149,6 @@ def monitoring_view(request): round_time_info['end_relative'] = _("Not set") r_times.append(round_time_info) - print(f'TUTAJ: {r_times}') permissions_count = { permission_name: (ContestPermission .objects From b08adc3b9c8ec0853f8e69bffb613710b0270bf8 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 20 Mar 2024 13:16:34 +0100 Subject: [PATCH 09/34] Change monitoring template --- .../statistics/templates/statistics/monitoring.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index 1f9f60066..5028f6a9c 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -7,14 +7,16 @@ {{ head|safe }}
-
+
+ {% include "statistics/_general_info.html" %} +
+
+
+
{% include "statistics/_permissions_info.html" %}
-
+
{% include "statistics/_rounds_info.html" %}
-
- {% include "statistics/_general_info.html" %} -
{% endblock %} From 76a41f44124f3c1631d57009cd795d3a73c2e53b Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 3 Apr 2024 17:26:51 +0200 Subject: [PATCH 10/34] Added count of unanswered questions and date of oldest unanswered question to general info --- oioioi/statistics/templates/statistics/_general_info.html | 8 ++++++++ oioioi/statistics/views.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/oioioi/statistics/templates/statistics/_general_info.html b/oioioi/statistics/templates/statistics/_general_info.html index 477a6e699..6c38b3b18 100644 --- a/oioioi/statistics/templates/statistics/_general_info.html +++ b/oioioi/statistics/templates/statistics/_general_info.html @@ -18,6 +18,14 @@

{% trans "General info" %}

Queued jobs on this server {{ q_size_global }} + + Unanswered questions + {{ unanswered_questions }} + + + Oldest unanswered question + {{ oldest_unanswered_question }} +
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 7f9401b32..89e689c2f 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -19,6 +19,7 @@ is_contest_observer, rounds_times, ) from oioioi.participants.models import Participant +from oioioi.questions.models import Message from oioioi.statistics.controllers import statistics_categories from oioioi.evalmgr.models import QueuedJob from oioioi.statistics.utils import any_statistics_avaiable, can_see_stats, render_head @@ -163,6 +164,10 @@ def monitoring_view(request): q_size_global = (QueuedJob.objects .count()) + unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) + oldest_unanswered_question = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest) + .order_by('pub_date').first()).date + return TemplateResponse( request, 'statistics/monitoring.html', @@ -173,5 +178,7 @@ def monitoring_view(request): 'links': links(request), 'q_size': q_size, 'q_size_global': q_size_global, + 'unanswered_questions': unanswered_questions, + 'oldest_unanswered_question': oldest_unanswered_question, }, ) From 3b7cbacd57801a5396b2db9f942c097c8757e209 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 3 Apr 2024 17:41:19 +0200 Subject: [PATCH 11/34] Add attachments info to monitoring page --- .../statistics/_attachments_info.html | 24 +++++++++++++++++++ .../templates/statistics/_rounds_info.html | 2 +- .../templates/statistics/monitoring.html | 3 +++ oioioi/statistics/views.py | 12 +++++++++- 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 oioioi/statistics/templates/statistics/_attachments_info.html diff --git a/oioioi/statistics/templates/statistics/_attachments_info.html b/oioioi/statistics/templates/statistics/_attachments_info.html new file mode 100644 index 000000000..706a0a260 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_attachments_info.html @@ -0,0 +1,24 @@ +{% load i18n pagination_tags %} +
+

{% trans "Permissions" %}

+ + + + + + + + + + + {% for attachment in attachments %} + + + + + + + {% endfor %} + +
{% trans "Description" %} {% trans "Round" %} {% trans "Publication date" %} {% trans "Time left to publication" %}
{{ attachment.description }}{{ attachment.round }}{{ attachment.pub_date }}{{ attachment.pub_date_relative }}
+
\ No newline at end of file diff --git a/oioioi/statistics/templates/statistics/_rounds_info.html b/oioioi/statistics/templates/statistics/_rounds_info.html index 6a026ada8..a8f8c5a83 100644 --- a/oioioi/statistics/templates/statistics/_rounds_info.html +++ b/oioioi/statistics/templates/statistics/_rounds_info.html @@ -5,7 +5,7 @@

{% trans "Rounds" %}

- + diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index 5028f6a9c..eea7c4ffe 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -10,6 +10,9 @@
{% include "statistics/_general_info.html" %}
+
+ {% include "statistics/_attachments_info.html" %} +
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 7f9401b32..20ba89b9c 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -11,7 +11,7 @@ from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry -from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions +from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment from oioioi.contests.utils import ( can_enter_contest, contest_exists, @@ -132,6 +132,9 @@ def statistics_view( condition=(is_contest_admin | is_contest_observer), order=110, ) +@enforce_condition( + contest_exists & can_enter_contest & can_see_stats +) def monitoring_view(request): cur_time = UTC.localize(datetime.now()) r_times = [] @@ -163,6 +166,12 @@ def monitoring_view(request): q_size_global = (QueuedJob.objects .count()) + attachments = ContestAttachment.objects.filter(contest_id=request.contest.id).order_by('id') + for attachment in attachments: + print(attachment.pub_date, cur_time) + pub_date_relative = str(attachment.pub_date - cur_time)[:-7] if attachment.pub_date > cur_time else _("Published") + setattr(attachment, 'pub_date_relative', pub_date_relative) + return TemplateResponse( request, 'statistics/monitoring.html', @@ -173,5 +182,6 @@ def monitoring_view(request): 'links': links(request), 'q_size': q_size, 'q_size_global': q_size_global, + 'attachments': attachments, }, ) From 6077a741fa7f1d5d5949653d5669172064ff2605 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 3 Apr 2024 17:50:34 +0200 Subject: [PATCH 12/34] Quickfix --- oioioi/statistics/templates/statistics/_attachments_info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oioioi/statistics/templates/statistics/_attachments_info.html b/oioioi/statistics/templates/statistics/_attachments_info.html index 706a0a260..f07a5ae71 100644 --- a/oioioi/statistics/templates/statistics/_attachments_info.html +++ b/oioioi/statistics/templates/statistics/_attachments_info.html @@ -1,6 +1,6 @@ {% load i18n pagination_tags %}
-

{% trans "Permissions" %}

+

{% trans "Attachments" %}

{% trans "Round name" %} {% trans "Round" %} {% trans "Start time" %} {% trans "Time left to start" %} {% trans "End time" %}
From 979449f1b73069819efd87e361ee55a25e48d78e Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Sun, 7 Apr 2024 19:39:20 +0200 Subject: [PATCH 13/34] Add submissions info to monitoring page * Number of submission of every kind --- .../statistics/_submissions_info.html | 22 +++++++++++++++++++ .../templates/statistics/monitoring.html | 3 +++ oioioi/statistics/views.py | 12 +++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 oioioi/statistics/templates/statistics/_submissions_info.html diff --git a/oioioi/statistics/templates/statistics/_submissions_info.html b/oioioi/statistics/templates/statistics/_submissions_info.html new file mode 100644 index 000000000..c5a80d712 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_submissions_info.html @@ -0,0 +1,22 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "Submission types" %}

+
+ + + + + + + + {% for submission_info in submissions_info %} + + + + + {% endfor %} + +
{% trans "Kind" %} {% trans "Count" %}
{{ submission_info.kind }}{{ submission_info.total }}
+
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index eea7c4ffe..d82dbfaeb 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -10,6 +10,9 @@
{% include "statistics/_general_info.html" %}
+
+ {% include "statistics/_submissions_info.html" %} +
{% include "statistics/_attachments_info.html" %}
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 916445992..7c55033e9 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -2,6 +2,7 @@ from pprint import pprint from django.core.exceptions import PermissionDenied +from django.db.models import Count from django.http import Http404 from django.template.response import TemplateResponse from django.urls import reverse @@ -11,7 +12,8 @@ from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry -from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment +from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment, \ + Submission from oioioi.contests.utils import ( can_enter_contest, contest_exists, @@ -169,14 +171,17 @@ def monitoring_view(request): attachments = ContestAttachment.objects.filter(contest_id=request.contest.id).order_by('id') for attachment in attachments: - print(attachment.pub_date, cur_time) - pub_date_relative = str(attachment.pub_date - cur_time)[:-7] if attachment.pub_date > cur_time else _("Published") + pub_date_relative = None + if attachment.pub_date: + pub_date_relative = str(attachment.pub_date - cur_time)[:-7] if attachment.pub_date > cur_time else _("Published") setattr(attachment, 'pub_date_relative', pub_date_relative) unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) oldest_unanswered_question = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest) .order_by('pub_date').first()) oldest_unanswered_question_date = oldest_unanswered_question.date if oldest_unanswered_question else None + submissions_info = Submission.objects.filter(problem_instance__contest=request.contest).values('kind').annotate(total=Count('kind')).order_by() + return TemplateResponse( request, 'statistics/monitoring.html', @@ -190,5 +195,6 @@ def monitoring_view(request): 'attachments': attachments, 'unanswered_questions': unanswered_questions, 'oldest_unanswered_question': oldest_unanswered_question_date, + 'submissions_info': submissions_info }, ) From d501172a973f29b0a3c1e0e9b59e59b92b55af3a Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 17 Apr 2024 17:31:29 +0200 Subject: [PATCH 14/34] Added count of submissions with system errors --- .../templates/statistics/_general_info.html | 4 ++++ oioioi/statistics/views.py | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/oioioi/statistics/templates/statistics/_general_info.html b/oioioi/statistics/templates/statistics/_general_info.html index 6c38b3b18..42a25bbbd 100644 --- a/oioioi/statistics/templates/statistics/_general_info.html +++ b/oioioi/statistics/templates/statistics/_general_info.html @@ -26,6 +26,10 @@

{% trans "General info" %}

Oldest unanswered question {{ oldest_unanswered_question }} + + Submissions with system errors + {{ sys_error_count }} +
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 7c55033e9..cc3e9e4af 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -13,7 +13,7 @@ from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment, \ - Submission + Submission, SubmissionReport from oioioi.contests.utils import ( can_enter_contest, contest_exists, @@ -169,6 +169,12 @@ def monitoring_view(request): q_size_global = (QueuedJob.objects .count()) + sys_error_count = ( + SubmissionReport.objects.filter( status='ACTIVE', failurereport__isnull=False).count() + + + SubmissionReport.objects.filter( status='ACTIVE', testreport__status='SE').count() + ) + attachments = ContestAttachment.objects.filter(contest_id=request.contest.id).order_by('id') for attachment in attachments: pub_date_relative = None @@ -195,6 +201,7 @@ def monitoring_view(request): 'attachments': attachments, 'unanswered_questions': unanswered_questions, 'oldest_unanswered_question': oldest_unanswered_question_date, - 'submissions_info': submissions_info + 'submissions_info': submissions_info, + 'sys_error_count': sys_error_count, }, ) From 71e422f2cc8d8df4af8c2b33a75c3a46cdb88a11 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 17 Apr 2024 17:42:21 +0200 Subject: [PATCH 15/34] Add info about test limits to contest monitoring --- .../templates/statistics/_tests_info.html | 30 +++++++++++++++++++ .../templates/statistics/monitoring.html | 5 ++++ oioioi/statistics/views.py | 15 ++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 oioioi/statistics/templates/statistics/_tests_info.html diff --git a/oioioi/statistics/templates/statistics/_tests_info.html b/oioioi/statistics/templates/statistics/_tests_info.html new file mode 100644 index 000000000..0373ef25f --- /dev/null +++ b/oioioi/statistics/templates/statistics/_tests_info.html @@ -0,0 +1,30 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "Test limits" %}

+ + + + + + + + + + + + {% for round, test_info in tests_info.items %} + {% for t_info in test_info %} + + + + + + + + {% endfor %} + {% endfor %} + +
{% trans "Round" %} {% trans "Problem" %} {% trans "Memory limit" %} {% trans "Time limit" %} {% trans "#tests" %}
{{ round }}{{ t_info.problem_instance__short_name }}{{ t_info.memory_limit }}{{ t_info.time_limit }}{{ t_info.count }}
+
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index d82dbfaeb..d63f8e3ea 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -25,4 +25,9 @@ {% include "statistics/_rounds_info.html" %} +
+
+ {% include "statistics/_tests_info.html" %} +
+
{% endblock %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 7c55033e9..fd3771cb8 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -1,3 +1,4 @@ +from collections import defaultdict from datetime import datetime from pprint import pprint @@ -21,6 +22,7 @@ is_contest_observer, rounds_times, ) from oioioi.participants.models import Participant +from oioioi.programs.models import Test from oioioi.questions.models import Message from oioioi.statistics.controllers import statistics_categories from oioioi.evalmgr.models import QueuedJob @@ -139,7 +141,7 @@ def statistics_view( contest_exists & can_enter_contest & can_see_stats ) def monitoring_view(request): - cur_time = UTC.localize(datetime.now()) + cur_time = request.timestamp r_times = [] for round_, rt in rounds_times(request, request.contest).items(): round_time_info = {'name': str(round_)} @@ -182,6 +184,14 @@ def monitoring_view(request): submissions_info = Submission.objects.filter(problem_instance__contest=request.contest).values('kind').annotate(total=Count('kind')).order_by() + tests_info = defaultdict(list) + tests_qs = Test.objects.filter(problem_instance__contest=request.contest) + tests_limits = (tests_qs.values('memory_limit', 'time_limit', 'problem_instance', 'problem_instance__round__name', 'problem_instance__short_name') + .annotate(count=Count('problem_instance')) + .order_by('problem_instance', 'memory_limit', 'time_limit')) + for t_info in tests_limits: + tests_info[t_info['problem_instance__round__name']].append(t_info) + return TemplateResponse( request, 'statistics/monitoring.html', @@ -195,6 +205,7 @@ def monitoring_view(request): 'attachments': attachments, 'unanswered_questions': unanswered_questions, 'oldest_unanswered_question': oldest_unanswered_question_date, - 'submissions_info': submissions_info + 'submissions_info': submissions_info, + 'tests_info': dict(tests_info) }, ) From 2ebbf0a626f6949e5bedaf42a65fb3be91b10ac9 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 24 Apr 2024 18:07:16 +0200 Subject: [PATCH 16/34] Extend problems info in monitoring page --- .../statistics/_problems_and_tests_info.html | 65 +++++++++++++++++++ .../templates/statistics/_tests_info.html | 30 --------- .../templates/statistics/monitoring.html | 2 +- oioioi/statistics/views.py | 27 ++++++-- 4 files changed, 89 insertions(+), 35 deletions(-) create mode 100644 oioioi/statistics/templates/statistics/_problems_and_tests_info.html delete mode 100644 oioioi/statistics/templates/statistics/_tests_info.html diff --git a/oioioi/statistics/templates/statistics/_problems_and_tests_info.html b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html new file mode 100644 index 000000000..b168ebe07 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html @@ -0,0 +1,65 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "Problem and tests info" %}

+ {% for round, problems in tests_info.items %} +

{{ round }}

+ {% for problem_id, problem_info in problems.items %} + + + + + + + + + + + + + + + {% if problem_info.testrun_config %} + + {% endif %} + + +
Problem Sumbissions limit Testrun
{{ problem_info.problem_name }}{{ problem_info.submissions_limit }}{% if problem_info.testrun_config %}{% trans "Enabled" %}{% else %}{% trans "Disabled" %}{% endif %}
+

Test Run Config

+ + + + + + + + + + + + + + +
{% trans "Test run limit" %} {% trans "Memory limit" %} {% trans "Time limit" %}
{{ problem_info.testrun_config.test_runs_limit }}{{ problem_info.testrun_config.memory_limit }}{{ problem_info.testrun_config.time_limit }}
+

Tests

+ + + + + + + + + {% for t_info in problem_info.tests %} + + + + + + {% endfor %} + +
{% trans "Memory limit" %} {% trans "Time limit" %} {% trans "#tests" %}
{{ t_info.memory_limit }}{{ t_info.time_limit }}{{ t_info.count }}
+ {% endfor %} + {% endfor %} +
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/_tests_info.html b/oioioi/statistics/templates/statistics/_tests_info.html deleted file mode 100644 index 0373ef25f..000000000 --- a/oioioi/statistics/templates/statistics/_tests_info.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load i18n pagination_tags %} -{% if rounds_times %} -
-

{% trans "Test limits" %}

- - - - - - - - - - - - {% for round, test_info in tests_info.items %} - {% for t_info in test_info %} - - - - - - - - {% endfor %} - {% endfor %} - -
{% trans "Round" %} {% trans "Problem" %} {% trans "Memory limit" %} {% trans "Time limit" %} {% trans "#tests" %}
{{ round }}{{ t_info.problem_instance__short_name }}{{ t_info.memory_limit }}{{ t_info.time_limit }}{{ t_info.count }}
-
-{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index d63f8e3ea..ea6c77fe1 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -27,7 +27,7 @@
- {% include "statistics/_tests_info.html" %} + {% include "statistics/_problems_and_tests_info.html" %}
{% endblock %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index badfe5ad1..f1589ad76 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -27,6 +27,7 @@ from oioioi.statistics.controllers import statistics_categories from oioioi.evalmgr.models import QueuedJob from oioioi.statistics.utils import any_statistics_avaiable, can_see_stats, render_head +from oioioi.testrun.models import TestRunConfig def links(request): @@ -190,13 +191,31 @@ def monitoring_view(request): submissions_info = Submission.objects.filter(problem_instance__contest=request.contest).values('kind').annotate(total=Count('kind')).order_by() - tests_info = defaultdict(list) + tests_info = defaultdict(lambda: defaultdict(lambda: { + 'problem_name': None, + 'testrun_config': None, + 'tests': list(), + 'submissions_limit': None, + })) tests_qs = Test.objects.filter(problem_instance__contest=request.contest) - tests_limits = (tests_qs.values('memory_limit', 'time_limit', 'problem_instance', 'problem_instance__round__name', 'problem_instance__short_name') + tests_limits = (tests_qs.values('memory_limit', 'time_limit', 'problem_instance', 'problem_instance__round__name', + 'problem_instance__short_name', 'problem_instance', + 'problem_instance__submissions_limit') .annotate(count=Count('problem_instance')) .order_by('problem_instance', 'memory_limit', 'time_limit')) + for t_info in tests_limits: - tests_info[t_info['problem_instance__round__name']].append(t_info) + tests_info[t_info['problem_instance__round__name']][t_info['problem_instance']]['tests'].append(t_info) + tests_info[t_info['problem_instance__round__name']][t_info['problem_instance']]['problem_name'] = \ + t_info['problem_instance__short_name'] + tests_info[t_info['problem_instance__round__name']][t_info['problem_instance']]['submissions_limit'] = \ + t_info['problem_instance__submissions_limit'] + + testrunconfig_qs = TestRunConfig.objects.filter(problem_instance__contest=request.contest) + for trc in testrunconfig_qs: + tests_info[trc.problem_instance.round][trc.problem_instance.id]['testrun_config'] = trc + + tests_info = dict({r: dict(t) for r, t in tests_info.items()}) return TemplateResponse( request, @@ -212,7 +231,7 @@ def monitoring_view(request): 'unanswered_questions': unanswered_questions, 'oldest_unanswered_question': oldest_unanswered_question_date, 'submissions_info': submissions_info, - 'tests_info': dict(tests_info), + 'tests_info': tests_info, 'sys_error_count': sys_error_count, }, ) From a04c578a982c66bf31affd47a61e19162bb3a186 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Tue, 7 May 2024 14:12:16 +0200 Subject: [PATCH 17/34] Refactor contest monitoring view --- .../statistics/_permissions_info.html | 2 +- oioioi/statistics/views.py | 118 ++++++++++-------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/oioioi/statistics/templates/statistics/_permissions_info.html b/oioioi/statistics/templates/statistics/_permissions_info.html index 2d9214082..18dcb679a 100644 --- a/oioioi/statistics/templates/statistics/_permissions_info.html +++ b/oioioi/statistics/templates/statistics/_permissions_info.html @@ -9,7 +9,7 @@

{% trans "Permissions" %}

- {% for permission, count in permissions_count.items %} + {% for permission, count in permissions_info.items %} {{ permission }} {{ count }} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index f1589ad76..e24bf0da9 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -1,6 +1,4 @@ from collections import defaultdict -from datetime import datetime -from pprint import pprint from django.core.exceptions import PermissionDenied from django.db.models import Count @@ -8,7 +6,6 @@ from django.template.response import TemplateResponse from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from pytz import UTC from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition @@ -142,55 +139,86 @@ def statistics_view( contest_exists & can_enter_contest & can_see_stats ) def monitoring_view(request): - cur_time = request.timestamp - r_times = [] + q_size = QueuedJob.objects.filter(submission__problem_instance__contest=request.contest).count() + q_size_global = QueuedJob.objects.count() + sys_error_count = ( + SubmissionReport.objects.filter(status='ACTIVE', failurereport__isnull=False).count() + + SubmissionReport.objects.filter(status='ACTIVE', testreport__status='SE').count() + ) + + unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) + oldest_unanswered_question = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest) + .order_by('pub_date').first()) + oldest_unanswered_question_date = oldest_unanswered_question.date if oldest_unanswered_question else None + + submissions_info = (Submission.objects.filter(problem_instance__contest=request.contest).values('kind') + .annotate(total=Count('kind')).order_by()) + rounds_info = get_rounds_info(request) + permissions_info = get_permissions_info(request) + attachments_info = get_attachments_info(request) + tests_info = get_tests_info(request) + return TemplateResponse( + request, + 'statistics/monitoring.html', + { + 'title': _("Monitoring"), + 'rounds_times': rounds_info, + 'permissions_info': permissions_info, + 'q_size': q_size, + 'q_size_global': q_size_global, + 'attachments': attachments_info, + 'unanswered_questions': unanswered_questions, + 'oldest_unanswered_question': oldest_unanswered_question_date, + 'submissions_info': submissions_info, + 'tests_info': tests_info, + 'sys_error_count': sys_error_count, + }, + ) + + +def get_rounds_info(request): + rounds_info = [] for round_, rt in rounds_times(request, request.contest).items(): - round_time_info = {'name': str(round_)} - round_time_info['start'] = rt.start or _("Not set") + round_time_info = {'name': str(round_), 'start': rt.start or _("Not set")} if rt.start: - round_time_info['start_relative'] = str(rt.start - cur_time)[:-7] if rt.is_future(cur_time) else _("Started") + round_time_info['start_relative'] = str(rt.start - request.timestamp)[:-7] if rt.is_future( + request.timestamp) else _("Started") else: round_time_info['start_relative'] = _("Not set") round_time_info['end'] = rt.end or _("Not set") if rt.end: - round_time_info['end_relative'] = str(rt.end - cur_time)[:-7] if not rt.is_past(cur_time) else _("Finished") + round_time_info['end_relative'] = str(rt.end - request.timestamp)[:-7] if not rt.is_past( + request.timestamp) else _("Finished") else: round_time_info['end_relative'] = _("Not set") - r_times.append(round_time_info) + rounds_info.append(round_time_info) + return rounds_info - permissions_count = { - permission_name: (ContestPermission - .objects - .filter(contest_id=request.contest.id, permission=permission_cls) - .count()) - for permission_cls, permission_name in contest_permissions - } - permissions_count['Participant'] = Participant.objects.filter(contest_id=request.contest.id).count() - q_size = (QueuedJob.objects - .filter(submission__problem_instance__contest=request.contest) - .count()) - q_size_global = (QueuedJob.objects - .count()) - - sys_error_count = ( - SubmissionReport.objects.filter( status='ACTIVE', failurereport__isnull=False).count() - + - SubmissionReport.objects.filter( status='ACTIVE', testreport__status='SE').count() - ) +def get_attachments_info(request): attachments = ContestAttachment.objects.filter(contest_id=request.contest.id).order_by('id') for attachment in attachments: pub_date_relative = None if attachment.pub_date: - pub_date_relative = str(attachment.pub_date - cur_time)[:-7] if attachment.pub_date > cur_time else _("Published") + pub_date_relative = str(attachment.pub_date - request.timestamp)[:-7] \ + if attachment.pub_date > request.timestamp else _("Published") setattr(attachment, 'pub_date_relative', pub_date_relative) - unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) - oldest_unanswered_question = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest) - .order_by('pub_date').first()) - oldest_unanswered_question_date = oldest_unanswered_question.date if oldest_unanswered_question else None + return attachments - submissions_info = Submission.objects.filter(problem_instance__contest=request.contest).values('kind').annotate(total=Count('kind')).order_by() +def get_permissions_info(request): + permissions_info = { + permission_name: (ContestPermission + .objects + .filter(contest_id=request.contest.id, permission=permission_cls) + .count()) + for permission_cls, permission_name in contest_permissions + } + permissions_info['Participant'] = Participant.objects.filter(contest_id=request.contest.id).count() + return permissions_info + + +def get_tests_info(request): tests_info = defaultdict(lambda: defaultdict(lambda: { 'problem_name': None, 'testrun_config': None, @@ -216,22 +244,4 @@ def monitoring_view(request): tests_info[trc.problem_instance.round][trc.problem_instance.id]['testrun_config'] = trc tests_info = dict({r: dict(t) for r, t in tests_info.items()}) - - return TemplateResponse( - request, - 'statistics/monitoring.html', - { - 'title': _("Monitoring"), - 'rounds_times': r_times, - 'permissions_count': permissions_count, - 'links': links(request), - 'q_size': q_size, - 'q_size_global': q_size_global, - 'attachments': attachments, - 'unanswered_questions': unanswered_questions, - 'oldest_unanswered_question': oldest_unanswered_question_date, - 'submissions_info': submissions_info, - 'tests_info': tests_info, - 'sys_error_count': sys_error_count, - }, - ) + return tests_info From 6977291b411342f3a513dbbacdf75faceb3e0f1d Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 8 May 2024 17:03:17 +0200 Subject: [PATCH 18/34] Added round extensions --- .../statistics/_round_time_extensions.html | 24 +++++++++++++++++++ .../templates/statistics/monitoring.html | 2 ++ oioioi/statistics/views.py | 20 +++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 oioioi/statistics/templates/statistics/_round_time_extensions.html diff --git a/oioioi/statistics/templates/statistics/_round_time_extensions.html b/oioioi/statistics/templates/statistics/_round_time_extensions.html new file mode 100644 index 000000000..f19dda554 --- /dev/null +++ b/oioioi/statistics/templates/statistics/_round_time_extensions.html @@ -0,0 +1,24 @@ +{% load i18n pagination_tags %} +{% if rounds_times %} +
+

{% trans "Round time extensions" %}

+ + + + + + + + + + {% for rte in round_time_extensions%} + + + + + + {% endfor %} + +
{% trans "Round" %} {% trans "Extra time" %} {% trans "Count" %}
{{ rte.round__name }}{{ rte.extra_time }}{{ rte.count }}
+
+{% endif %} diff --git a/oioioi/statistics/templates/statistics/monitoring.html b/oioioi/statistics/templates/statistics/monitoring.html index ea6c77fe1..eb625b48e 100644 --- a/oioioi/statistics/templates/statistics/monitoring.html +++ b/oioioi/statistics/templates/statistics/monitoring.html @@ -23,7 +23,9 @@
{% include "statistics/_rounds_info.html" %} + {% include "statistics/_round_time_extensions.html" %}
+
diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index e24bf0da9..04e87ad01 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -1,4 +1,6 @@ from collections import defaultdict +from datetime import datetime, timedelta +from pprint import pprint from django.core.exceptions import PermissionDenied from django.db.models import Count @@ -11,7 +13,7 @@ from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment, \ - Submission, SubmissionReport + Submission, SubmissionReport, RoundTimeExtension from oioioi.contests.utils import ( can_enter_contest, contest_exists, @@ -157,6 +159,18 @@ def monitoring_view(request): permissions_info = get_permissions_info(request) attachments_info = get_attachments_info(request) tests_info = get_tests_info(request) + + def is_rte_active(rte): + print(rte) + return rte['round__end_date'] + timedelta(minutes=rte['extra_time']) >= request.timestamp + + round_time_extensions = (RoundTimeExtension.objects.filter(round__contest=request.contest.id) + .values("round__name", "round__end_date", "extra_time") + .annotate(count=Count('extra_time')) + .order_by('extra_time')) + + active_rtes = list(filter(is_rte_active, round_time_extensions)) + return TemplateResponse( request, 'statistics/monitoring.html', @@ -172,6 +186,7 @@ def monitoring_view(request): 'submissions_info': submissions_info, 'tests_info': tests_info, 'sys_error_count': sys_error_count, + 'round_time_extensions': active_rtes, }, ) @@ -218,6 +233,9 @@ def get_permissions_info(request): return permissions_info + + + def get_tests_info(request): tests_info = defaultdict(lambda: defaultdict(lambda: { 'problem_name': None, From 1b665d78c96a7e7726d6a1448a9c12b8ed16e566 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 15 May 2024 18:11:43 +0200 Subject: [PATCH 19/34] Added bool, that shows if someone solved a problem --- .../statistics/_problems_and_tests_info.html | 4 +++- oioioi/statistics/views.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/oioioi/statistics/templates/statistics/_problems_and_tests_info.html b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html index b168ebe07..2888321cf 100644 --- a/oioioi/statistics/templates/statistics/_problems_and_tests_info.html +++ b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html @@ -10,7 +10,8 @@

{{ round }}

Problem Sumbissions limit - Testrun + Testrun + Solved @@ -18,6 +19,7 @@

{{ round }}

{{ problem_info.problem_name }} {{ problem_info.submissions_limit }} {% if problem_info.testrun_config %}{% trans "Enabled" %}{% else %}{% trans "Disabled" %}{% endif %} + {{ problem_info.solved }} {% if problem_info.testrun_config %} diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 04e87ad01..6e9bcef12 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -8,12 +8,13 @@ from django.template.response import TemplateResponse from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from django.db.models import F from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition from oioioi.contests.menu import contest_admin_menu_registry from oioioi.contests.models import ProblemInstance, ContestPermission, contest_permissions, ContestAttachment, \ - Submission, SubmissionReport, RoundTimeExtension + Submission, SubmissionReport, RoundTimeExtension, ScoreReport from oioioi.contests.utils import ( can_enter_contest, contest_exists, @@ -161,7 +162,6 @@ def monitoring_view(request): tests_info = get_tests_info(request) def is_rte_active(rte): - print(rte) return rte['round__end_date'] + timedelta(minutes=rte['extra_time']) >= request.timestamp round_time_extensions = (RoundTimeExtension.objects.filter(round__contest=request.contest.id) @@ -242,6 +242,7 @@ def get_tests_info(request): 'testrun_config': None, 'tests': list(), 'submissions_limit': None, + 'solved': False, })) tests_qs = Test.objects.filter(problem_instance__contest=request.contest) tests_limits = (tests_qs.values('memory_limit', 'time_limit', 'problem_instance', 'problem_instance__round__name', @@ -261,5 +262,14 @@ def get_tests_info(request): for trc in testrunconfig_qs: tests_info[trc.problem_instance.round][trc.problem_instance.id]['testrun_config'] = trc + solved_qs = ScoreReport.objects.filter(submission_report__submission__problem_instance__contest=request.contest, + submission_report__kind='NORMAL', + submission_report__submission__kind='NORMAL', + score = F('max_score')) + + for s in solved_qs: + tests_info[s.submission_report.submission.problem_instance.round.name][s.submission_report.submission.problem_instance.id]['solved'] = True + tests_info = dict({r: dict(t) for r, t in tests_info.items()}) + return tests_info From 2ef9ff9fcb39b8b5f8ab8b8bc1cf87043a53cf0b Mon Sep 17 00:00:00 2001 From: Zonkil Date: Sun, 19 May 2024 17:06:00 +0200 Subject: [PATCH 20/34] Bugfix: Changed round to round-name --- oioioi/statistics/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 6e9bcef12..17f5ecbf9 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -260,7 +260,7 @@ def get_tests_info(request): testrunconfig_qs = TestRunConfig.objects.filter(problem_instance__contest=request.contest) for trc in testrunconfig_qs: - tests_info[trc.problem_instance.round][trc.problem_instance.id]['testrun_config'] = trc + tests_info[trc.problem_instance.round.name][trc.problem_instance.id]['testrun_config'] = trc solved_qs = ScoreReport.objects.filter(submission_report__submission__problem_instance__contest=request.contest, submission_report__kind='NORMAL', From beca5c87ec251b577424b45b9c4bf9a486fe31dc Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 22 May 2024 17:11:15 +0200 Subject: [PATCH 21/34] Bugfix: Added contest filter to SE detection --- oioioi/statistics/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 17f5ecbf9..16a3936c8 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -145,8 +145,10 @@ def monitoring_view(request): q_size = QueuedJob.objects.filter(submission__problem_instance__contest=request.contest).count() q_size_global = QueuedJob.objects.count() sys_error_count = ( - SubmissionReport.objects.filter(status='ACTIVE', failurereport__isnull=False).count() - + SubmissionReport.objects.filter(status='ACTIVE', testreport__status='SE').count() + SubmissionReport.objects.filter(status='ACTIVE', failurereport__isnull=False, + submission__problem_instance__contest=request.contest).count() + + SubmissionReport.objects.filter(status='ACTIVE', testreport__status='SE', + submission__problem_instance__contest=request.contest).count() ) unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) From 1354ab6ed31336317859e4adc052d908dd8041d0 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 22 May 2024 17:13:02 +0200 Subject: [PATCH 22/34] Create tests_monitoring file --- oioioi/statistics/tests_monitoring.py | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 oioioi/statistics/tests_monitoring.py diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py new file mode 100644 index 000000000..afbf33768 --- /dev/null +++ b/oioioi/statistics/tests_monitoring.py @@ -0,0 +1,42 @@ +from datetime import datetime, timezone # pylint: disable=E0611 + +from django.contrib.auth.models import User +from django.test import RequestFactory +from django.urls import reverse + +from oioioi.base.tests import TestCase, fake_time +from oioioi.contests.models import Contest, ProblemInstance + + +class TestContestMonitoringViews(TestCase): + fixtures = [ + 'test_users', + 'test_contest', + 'test_full_package', + 'test_problem_instance', + 'test_submission', + 'test_submission_another_user_for_statistics', + 'test_extra_rounds', + 'test_extra_problem', + 'test_permissions', + ] + + def setUp(self): + self.request = RequestFactory().request() + self.request.user = User.objects.get(username='test_user') + self.request.contest = Contest.objects.get() + self.request.timestamp = datetime.now().replace(tzinfo=timezone.utc) + + def test_submission_types(self): + contest = Contest.objects.get() + url = reverse('monitoring', kwargs={'contest_id': contest.id}) + + # Without StatisticsConfig model + self.assertTrue(self.client.login(username='test_admin')) + with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): + response = self.client.get(url) + + f = open("monitoring_page.html", "a") + f.write(str(response.content)) + f.close() + From 0741eddf963fe1c0b072cb1cc91259bd6cec5bda Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 22 May 2024 17:56:26 +0200 Subject: [PATCH 23/34] Add tests round times --- oioioi/statistics/tests_monitoring.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py index afbf33768..12bbb320d 100644 --- a/oioioi/statistics/tests_monitoring.py +++ b/oioioi/statistics/tests_monitoring.py @@ -19,6 +19,7 @@ class TestContestMonitoringViews(TestCase): 'test_extra_rounds', 'test_extra_problem', 'test_permissions', + 'test_messages', ] def setUp(self): @@ -27,16 +28,29 @@ def setUp(self): self.request.contest = Contest.objects.get() self.request.timestamp = datetime.now().replace(tzinfo=timezone.utc) - def test_submission_types(self): + def test_permissions_info(self): contest = Contest.objects.get() url = reverse('monitoring', kwargs={'contest_id': contest.id}) - # Without StatisticsConfig model self.assertTrue(self.client.login(username='test_admin')) with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): response = self.client.get(url) - - f = open("monitoring_page.html", "a") + self.assertRegex(str(response.content), r"Admin... *... *... *... *... *.{0,50}.{0,50}... *.{0,50}.{0,50}... *... *... *.{0,50}.{0,50}... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... *... * {% if problem_info.testrun_config %} -
1") + self.assertRegex(str(response.content), r"Basic Admin1") + self.assertRegex(str(response.content), r"Observer1") + self.assertRegex(str(response.content), r"Personal Data1") + self.assertRegex(str(response.content), r"Participant0") + f = open("monitoring_page.html", "w") f.write(str(response.content)) f.close() + def test_round_times(self): + contest = Contest.objects.get() + url = reverse('monitoring', kwargs={'contest_id': contest.id}) + + self.assertTrue(self.client.login(username='test_admin')) + with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): + response = self.client.get(url) + self.assertRegex(str(response.content), r"Past round(.{0,50}){2}Started") + self.assertRegex(str(response.content), r"Past round(.{0,50}){4}Finished") + From 6f9562a1cd7297d52f6f3bf288c9b8f63ef4840b Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 29 May 2024 16:55:32 +0200 Subject: [PATCH 24/34] Bugfix: changed pub-date to date --- oioioi/statistics/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 16a3936c8..443ac3c14 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -153,7 +153,7 @@ def monitoring_view(request): unanswered_questions = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest).count()) oldest_unanswered_question = (Message.objects.filter(kind='QUESTION', message=None, contest=request.contest) - .order_by('pub_date').first()) + .order_by('date').first()) oldest_unanswered_question_date = oldest_unanswered_question.date if oldest_unanswered_question else None submissions_info = (Submission.objects.filter(problem_instance__contest=request.contest).values('kind') From 227503a6a2e4a3bbc3b8a1b9efff527384c5f796 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 29 May 2024 17:12:45 +0200 Subject: [PATCH 25/34] Added tests for general info --- .../test_submission_list_with_syserr.json | 519 ++++++++++++++++++ oioioi/statistics/tests_monitoring.py | 15 +- 2 files changed, 526 insertions(+), 8 deletions(-) create mode 100644 oioioi/contests/fixtures/test_submission_list_with_syserr.json diff --git a/oioioi/contests/fixtures/test_submission_list_with_syserr.json b/oioioi/contests/fixtures/test_submission_list_with_syserr.json new file mode 100644 index 000000000..513ffa3b1 --- /dev/null +++ b/oioioi/contests/fixtures/test_submission_list_with_syserr.json @@ -0,0 +1,519 @@ +[ + { + "pk": 1, + "model": "contests.submission", + "fields": { + "status": "INI_OK", + "problem_instance": 1, + "kind": "NORMAL", + "comment": "", + "score": "int:0000000000000000034", + "user": 1001, + "date": "2012-06-03T22:07:07.516Z" + } + }, + { + "pk": 2, + "model": "contests.submission", + "fields": { + "status": "INI_ERR", + "problem_instance": 1, + "kind": "NORMAL", + "comment": "", + "score": "int:0000000000000000000", + "user": 1001, + "date": "2012-06-03T22:08:07.516Z" + } + }, + { + "pk": 1, + "model": "contests.submissionreport", + "fields": { + "status": "ACTIVE", + "kind": "INITIAL", + "submission": 1, + "creation_date": "2012-08-03T22:07:29.547Z" + } + }, + { + "pk": 2, + "model": "contests.submissionreport", + "fields": { + "status": "ACTIVE", + "kind": "NORMAL", + "submission": 1, + "creation_date": "2012-08-03T22:07:39.662Z" + } + }, + { + "pk": 3, + "model": "contests.submissionreport", + "fields": { + "status": "ACTIVE", + "kind": "INITIAL", + "submission": 2, + "creation_date": "2012-08-03T22:08:29.547Z" + } + }, + { + "pk": 4, + "model": "contests.submissionreport", + "fields": { + "status": "ACTIVE", + "kind": "NORMAL", + "submission": 2, + "creation_date": "2012-08-03T22:08:39.662Z" + } + }, + { + "pk": 1, + "model": "contests.scorereport", + "fields": { + "status": "OK", + "comment": null, + "score": null, + "submission_report": 1, + "max_score": "int:0000000000000000100" + } + }, + { + "pk": 2, + "model": "contests.scorereport", + "fields": { + "status": "RE", + "comment": null, + "score": "int:0000000000000000034", + "submission_report": 2, + "max_score": "int:0000000000000000100" + } + }, + { + "pk": 3, + "model": "contests.scorereport", + "fields": { + "status": "OK", + "comment": null, + "score": null, + "submission_report": 3, + "max_score": "int:0000000000000000100" + } + }, + { + "pk": 4, + "model": "contests.scorereport", + "fields": { + "status": "RE", + "comment": null, + "score": "int:0000000000000000034", + "submission_report": 4, + "max_score": "int:0000000000000000100" + } + }, + { + "pk": 1, + "model": "contests.userresultforproblem", + "fields": { + "status": "OK", + "problem_instance": 1, + "score": "int:0000000000000000034", + "user": 1001, + "submission_report": 2 + } + }, + { + "pk": 1, + "model": "contests.userresultforround", + "fields": { + "score": "int:0000000000000000034", + "user": 1001, + "round": 1 + } + }, + { + "pk": 1, + "model": "contests.userresultforcontest", + "fields": { + "score": "int:0000000000000000034", + "user": 1001, + "contest": "c" + } + }, + { + "pk": 1, + "model": "programs.programsubmission", + "fields": { + "source_file": "data:submissions/c/1.cpp:I2luY2x1ZGUgPGlvc3RyZWFtPgoKdXNpbmcgbmFtZXNwYWNlIHN0ZDsKCmludCBtYWluKCkgewogICAgaW50IGEsIGI7CiAgICBjaW4gPj4gYSA+PiBiOwogICAgaWYgKGEgPT0gMTAwKSB7CiAgICAgICAgLy8gc3VtMWIuaW4KICAgICAgICByZXR1cm4gMTsKICAgIH0gZWxzZSBpZiAoYSA9PSA0OTcyKSB7CiAgICAgICAgLy8gc3VtMi5pbgogICAgICAgIGNvdXQgPDwgMzQ1Njc4OSA8PCBlbmRsOwogICAgfSBlbHNlIHsKICAgICAgICBjb3V0IDw8IGEgKyBiIDw8IGVuZGw7CiAgICB9Cn0K", + "source_length": 279 + } + }, + { + "pk": 1, + "model": "programs.compilationreport", + "fields": { + "status": "OK", + "compiler_output": "ld: warning: option -s is obsolete and being ignored\n", + "submission_report": 1 + } + }, + { + "pk": 2, + "model": "programs.compilationreport", + "fields": { + "status": "OK", + "compiler_output": "ld: warning: option -s is obsolete and being ignored\n", + "submission_report": 2 + } + }, + { + "pk": 3, + "model": "programs.compilationreport", + "fields": { + "status": "OK", + "compiler_output": "ld: warning: option -s is obsolete and being ignored\n", + "submission_report": 3 + } + }, + { + "pk": 4, + "model": "programs.compilationreport", + "fields": { + "status": "OK", + "compiler_output": "ld: warning: option -s is obsolete and being ignored\n", + "submission_report": 4 + } + }, + { + "pk": 1, + "model": "programs.testreport", + "fields": { + "status": "OK", + "comment": "", + "test_time_limit": 10000, + "submission_report": 1, + "time_used": 1, + "test_group": "0", + "score": "int:0000000000000000000", + "test_name": "0", + "test": 1 + } + }, + { + "pk": 2, + "model": "programs.testreport", + "fields": { + "status": "SE", + "comment": "", + "test_time_limit": 10000, + "submission_report": 1, + "time_used": 1, + "test_group": "1ocen", + "score": "int:0000000000000000000", + "test_name": "1ocen", + "test": 4 + } + }, + { + "pk": 3, + "model": "programs.testreport", + "fields": { + "status": "OK", + "comment": "", + "test_time_limit": 10000, + "submission_report": 2, + "time_used": 1, + "test_group": "1", + "score": "int:0000000000000000005", + "test_name": "1a", + "test": 2 + } + }, + { + "pk": 4, + "model": "programs.testreport", + "fields": { + "status": "SE", + "comment": "", + "test_time_limit": 10000, + "submission_report": 2, + "time_used": 1, + "test_group": "3", + "score": "int:0000000000000000000", + "test_name": "3", + "test": 6 + } + }, + { + "pk": 5, + "model": "programs.testreport", + "fields": { + "status": "SE", + "comment": "", + "test_time_limit": 10000, + "submission_report": 2, + "time_used": 0, + "test_group": "2", + "score": "int:0000000000000000000", + "test_name": "2", + "test": 5 + } + }, + { + "pk": 6, + "model": "programs.testreport", + "fields": { + "status": "RE", + "comment": "program exited with code 1", + "test_time_limit": 100, + "submission_report": 2, + "time_used": 0, + "test_group": "1", + "score": "int:0000000000000000000", + "test_name": "1b", + "test": 3 + } + }, + { + "pk": 7, + "model": "programs.testreport", + "fields": { + "status": "WA", + "comment": "", + "test_time_limit": 10000, + "submission_report": 3, + "time_used": 1, + "test_group": "0", + "score": "int:0000000000000000000", + "test_name": "0", + "test": 1 + } + }, + { + "pk": 8, + "model": "programs.testreport", + "fields": { + "status": "OK", + "comment": "", + "test_time_limit": 10000, + "submission_report": 3, + "time_used": 1, + "test_group": "1ocen", + "score": "int:0000000000000000000", + "test_name": "1ocen", + "test": 4 + } + }, + { + "pk": 9, + "model": "programs.testreport", + "fields": { + "status": "OK", + "comment": "", + "test_time_limit": 10000, + "submission_report": 4, + "time_used": 1, + "test_group": "1", + "score": "int:0000000000000000005", + "test_name": "1a", + "test": 2 + } + }, + { + "pk": 10, + "model": "programs.testreport", + "fields": { + "status": "OK", + "comment": "", + "test_time_limit": 10000, + "submission_report": 4, + "time_used": 1, + "test_group": "3", + "score": "int:0000000000000000034", + "test_name": "3", + "test": 6 + } + }, + { + "pk": 11, + "model": "programs.testreport", + "fields": { + "status": "WA", + "comment": "", + "test_time_limit": 10000, + "submission_report": 4, + "time_used": 0, + "test_group": "2", + "score": "int:0000000000000000000", + "test_name": "2", + "test": 5 + } + }, + { + "pk": 12, + "model": "programs.testreport", + "fields": { + "status": "RE", + "comment": "program exited with code 1", + "test_time_limit": 100, + "submission_report": 4, + "time_used": 0, + "test_group": "1", + "score": "int:0000000000000000000", + "test_name": "1b", + "test": 3 + } + }, + { + "pk": 1, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "0", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 1 + } + }, + { + "pk": 2, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "1ocen", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 1 + } + }, + { + "pk": 3, + "model": "programs.groupreport", + "fields": { + "status": "RE", + "group": "1", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000033", + "submission_report": 2 + } + }, + { + "pk": 4, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "0", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 2 + } + }, + { + "pk": 5, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "3", + "score": "int:0000000000000000034", + "max_score": "int:0000000000000000034", + "submission_report": 2 + } + }, + { + "pk": 6, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "1ocen", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 2 + } + }, + { + "pk": 7, + "model": "programs.groupreport", + "fields": { + "status": "WA", + "group": "2", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000033", + "submission_report": 2 + } + }, + { + "pk": 8, + "model": "programs.groupreport", + "fields": { + "status": "WA", + "group": "0", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 3 + } + }, + { + "pk": 9, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "1ocen", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 3 + } + }, + { + "pk": 10, + "model": "programs.groupreport", + "fields": { + "status": "RE", + "group": "1", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000033", + "submission_report": 4 + } + }, + { + "pk": 11, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "0", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 4 + } + }, + { + "pk": 12, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "3", + "score": "int:0000000000000000034", + "max_score": "int:0000000000000000034", + "submission_report": 4 + } + }, + { + "pk": 13, + "model": "programs.groupreport", + "fields": { + "status": "OK", + "group": "1ocen", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000000", + "submission_report": 4 + } + }, + { + "pk": 14, + "model": "programs.groupreport", + "fields": { + "status": "WA", + "group": "2", + "score": "int:0000000000000000000", + "max_score": "int:0000000000000000033", + "submission_report": 4 + } + } +] diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py index 12bbb320d..2b6fd67c5 100644 --- a/oioioi/statistics/tests_monitoring.py +++ b/oioioi/statistics/tests_monitoring.py @@ -31,8 +31,8 @@ def setUp(self): def test_permissions_info(self): contest = Contest.objects.get() url = reverse('monitoring', kwargs={'contest_id': contest.id}) - self.assertTrue(self.client.login(username='test_admin')) + with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): response = self.client.get(url) self.assertRegex(str(response.content), r"Admin1") @@ -43,14 +43,13 @@ def test_permissions_info(self): f = open("monitoring_page.html", "w") f.write(str(response.content)) f.close() - - def test_round_times(self): + def test_questions_info(self): contest = Contest.objects.get() url = reverse('monitoring', kwargs={'contest_id': contest.id}) - self.assertTrue(self.client.login(username='test_admin')) - with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): - response = self.client.get(url) - self.assertRegex(str(response.content), r"Past round(.{0,50}){2}Started") - self.assertRegex(str(response.content), r"Past round(.{0,50}){4}Finished") + with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): + response = self.client.get(url) + self.assertRegex(str(response.content), r"Unanswered questions2") + self.assertRegex(str(response.content), r"Oldest unanswered question2012-09-07 13:14:24") + self.assertRegex(str(response.content), r"Submissions with system errors2") \ No newline at end of file From b909df44ba293df9e0d149eed3f4652e94469adc Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 29 May 2024 17:13:18 +0200 Subject: [PATCH 26/34] WIP --- oioioi/statistics/tests_monitoring.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py index 12bbb320d..8d0103d2b 100644 --- a/oioioi/statistics/tests_monitoring.py +++ b/oioioi/statistics/tests_monitoring.py @@ -6,6 +6,7 @@ from oioioi.base.tests import TestCase, fake_time from oioioi.contests.models import Contest, ProblemInstance +from oioioi.statistics.views import get_permissions_info, get_rounds_info class TestContestMonitoringViews(TestCase): @@ -20,13 +21,14 @@ class TestContestMonitoringViews(TestCase): 'test_extra_problem', 'test_permissions', 'test_messages', + 'test_second_user_messages', ] def setUp(self): self.request = RequestFactory().request() self.request.user = User.objects.get(username='test_user') self.request.contest = Contest.objects.get() - self.request.timestamp = datetime.now().replace(tzinfo=timezone.utc) + self.request.timestamp = datetime(2014, 8, 5, tzinfo=timezone.utc) def test_permissions_info(self): contest = Contest.objects.get() @@ -44,13 +46,16 @@ def test_permissions_info(self): f.write(str(response.content)) f.close() - def test_round_times(self): + def test_round_info(self): contest = Contest.objects.get() - url = reverse('monitoring', kwargs={'contest_id': contest.id}) - - self.assertTrue(self.client.login(username='test_admin')) with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): - response = self.client.get(url) - self.assertRegex(str(response.content), r"Past round(.{0,50}){2}Started") - self.assertRegex(str(response.content), r"Past round(.{0,50}){4}Finished") - + self.assertTrue(self.client.login(username='test_admin')) + rounds_info = get_rounds_info(self.request) + for ri in rounds_info: + if ri['name'] == 'Past round': + self.assertTrue(ri['start_relative'] == 'Started') + self.assertTrue(ri['end_relative'] == 'Finished') + if ri['name'] == 'Future round': + self.assertTrue(ri['start_relative'] == '360 days, 20:27:58') + + def test_attachments_info(self): From b1593c085bfb363089bd88844c27ad19f3b797d7 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 29 May 2024 17:28:40 +0200 Subject: [PATCH 27/34] Changed fixture and fixed system error counter --- .../test_submission_list_with_syserr.json | 16 ++++++++-------- oioioi/statistics/views.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/oioioi/contests/fixtures/test_submission_list_with_syserr.json b/oioioi/contests/fixtures/test_submission_list_with_syserr.json index 513ffa3b1..8dfee63ab 100644 --- a/oioioi/contests/fixtures/test_submission_list_with_syserr.json +++ b/oioioi/contests/fixtures/test_submission_list_with_syserr.json @@ -69,11 +69,11 @@ "pk": 1, "model": "contests.scorereport", "fields": { - "status": "OK", + "status": "SE", "comment": null, "score": null, "submission_report": 1, - "max_score": "int:0000000000000000100" + "max_score": "int:0000000000000000000" } }, { @@ -94,8 +94,8 @@ "status": "OK", "comment": null, "score": null, - "submission_report": 3, - "max_score": "int:0000000000000000100" + "submission_report": null, + "max_score": "int:0000000000000000000" } }, { @@ -201,7 +201,7 @@ "pk": 2, "model": "programs.testreport", "fields": { - "status": "SE", + "status": "OK", "comment": "", "test_time_limit": 10000, "submission_report": 1, @@ -231,13 +231,13 @@ "pk": 4, "model": "programs.testreport", "fields": { - "status": "SE", + "status": "OK", "comment": "", "test_time_limit": 10000, "submission_report": 2, "time_used": 1, "test_group": "3", - "score": "int:0000000000000000000", + "score": "int:0000000000000000034", "test_name": "3", "test": 6 } @@ -246,7 +246,7 @@ "pk": 5, "model": "programs.testreport", "fields": { - "status": "SE", + "status": "WA", "comment": "", "test_time_limit": 10000, "submission_report": 2, diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 443ac3c14..8b39cd5d8 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -147,7 +147,7 @@ def monitoring_view(request): sys_error_count = ( SubmissionReport.objects.filter(status='ACTIVE', failurereport__isnull=False, submission__problem_instance__contest=request.contest).count() - + SubmissionReport.objects.filter(status='ACTIVE', testreport__status='SE', + + SubmissionReport.objects.filter(status='ACTIVE', scorereport__status='SE', submission__problem_instance__contest=request.contest).count() ) From 3e2f07e7a4e1236315783fd7ea292fa364f1097f Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 29 May 2024 18:02:25 +0200 Subject: [PATCH 28/34] WIP --- .../fixtures/test_contest_attachment.json | 22 ++++++++++++++----- oioioi/statistics/tests_monitoring.py | 19 ++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/oioioi/contests/fixtures/test_contest_attachment.json b/oioioi/contests/fixtures/test_contest_attachment.json index ea1b55488..5fa36ae6e 100644 --- a/oioioi/contests/fixtures/test_contest_attachment.json +++ b/oioioi/contests/fixtures/test_contest_attachment.json @@ -4,11 +4,21 @@ "model": "contests.contestattachment", "fields": { "contest": "c", - "description": "test attachment", - "content": "", - "Round": 1, - "score": "int:0000000000000000034", - "pub_date": "2014-01-31T17:18:19.768Z" + "description": "published attachment", + "content": "data:problems/1/sum_7.pdf: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OCA1MDAKNTAwIDUwMCAwIDAgMCAwIDAgMCAwIDAgMjc4IDAgMCAwIDAgMAowIDAgMCAwIDc2NCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgNTU2IDcyMiAwIDAgMTAyOCAwIDAgMCAwIDAgMCAwIDAKMCA1MDAgNTU2IDQ0NCA1NTYgNDQ0IDAgNTAwIDU1NiAyNzggMzA2IDUyOCAyNzggODMzIDU1NiA1MDAKNTU2IDAgMzkyIDM5NCAzODkgNTU2IDUyOCA3MjIgMCA1MjggNDQ0IDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCA1MDAgNDQ0IDAgMCAwIDQ0NCAwIDAgMCAzMzYgMCAwIDAgMCAwCjAgMzk0IDAgMCAwIDAgMCAwIDAgNDQ0IDAgNDQ0IDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgNTAwXQovRW5jb2RpbmcgMzkgMCBSL1N1YnR5cGUvVHlwZTE+PgplbmRvYmoKMzkgMCBvYmoKPDwvVHlwZS9FbmNvZGluZy9CYXNlRW5jb2RpbmcvV2luQW5zaUVuY29kaW5nL0RpZmZlcmVuY2VzWwoxNjEvYW9nb25lay9jYWN1dGUKMTY2L2VvZ29uZWsKMTcwL2xzbGFzaAoxNzcvc2FjdXRlCjE4NS96YWN1dGUKMTg3L3pkb3RhY2NlbnRdPj4KZW5kb2JqCjE0IDAgb2JqCjw8L0Jhc2VGb250L1pJRVFNVitQTFJvbWFuMTAtSXRhbGljL0ZvbnREZXNjcmlwdG9yIDE1IDAgUi9UeXBlL0ZvbnQKL0ZpcnN0Q2hhciA0Ni9MYXN0Q2hhciAxMjMvV2lkdGhzWyAzMDcgMAo1MTEgNTExIDUxMSAwIDUxMSAwIDAgNTExIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTExXQovRW5jb2RpbmcgNDAgMCBSL1N1YnR5cGUvVHlwZTE+PgplbmRvYmoKNDAgMCBvYmoKPDwvVHlwZS9FbmNvZGluZy9CYXNlRW5jb2RpbmcvV2luQW5zaUVuY29kaW5nL0RpZmZlcmVuY2VzWwoxMjMvZW5kYXNoXT4+CmVuZG9iagoxMiAwIG9iago8PC9CYXNlRm9udC9LSktWR0srUExUeXBld3JpdGVyMTAtUmVndWxhci9Gb250RGVzY3JpcHRvciAxMyAwIFIvVHlwZS9Gb250Ci9GaXJzdENoYXIgNDIvTGFzdENoYXIgMTE3L1dpZHRoc1sgNTI1IDAgMCAwIDUyNSAwCjAgNTI1IDUyNSA1MjUgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTI1IDAgMAowIDAgMCA1MjUgMCA1MjVdCi9FbmNvZGluZy9XaW5BbnNpRW5jb2RpbmcvU3VidHlwZS9UeXBlMT4+CmVuZG9iago0MSAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDMwOT4+c3RyZWFtCnicXZI9bsMwDIV3n0I3sOQfKQEELumSoUXR9gKyTBceIhuOM/T2fWSSDh0eoc/ioyjR9en8ci7zbur3bcmfvJtpLuPG1+W2ZTYDf8+lco0Z57w/SGO+pLWqT69p/fpZ2SCBpzu/pQvXH53TL+7uycvI1zVl3lL55ipaS3GaqOIy/ttq2rtjmB6pTSaVtYhAJhWQga2j2DaCWAEPpAIeBBOpgAnYIbHTZERgRypgJ3gkFfAoOJIKOApOFHuriL5jj0N6PaiXgzzaEFnrpQ2Pqr5XlMoeVb1W9lLZDxSDlsKqigEdBW0jSFchkAoYBOEL6g3iTUgWWWdlN3lSWed0F3cVWacXHOATWRfk+lNLKmsR5emfbyxTkHE+p2fybdu47DpznanMci7891usyyouA1W/jsGcaQplbmRzdHJlYW0KZW5kb2JqCjEwIDAgb2JqCjw8L0Jhc2VGb250L0xNSldOVStQTFJvbWFuMTAtQm9sZC9Gb250RGVzY3JpcHRvciAxMSAwIFIvVG9Vbmljb2RlIDQxIDAgUi9UeXBlL0ZvbnQKL0ZpcnN0Q2hhciA0NC9MYXN0Q2hhciAyNDMvV2lkdGhzWyAzMTkgMCAzMTkgMAowIDU3NSA1NzUgMCAwIDAgMCAwIDU3NSAwIDMxOSAwIDAgMCAwIDAKMCAwIDgxOCAwIDg4MiAwIDAgMCAwIDQzNiAwIDAgMCAxMDkyIDAgODY0Cjc4NiAwIDAgMCAwIDAgMCAwIDg2OSAwIDAgMCAwIDAgMCAwCjAgNTU5IDAgMCA2MzkgNTI3IDAgMCAwIDMxOSAwIDYwNyAzMTkgOTU4IDYzOSA1NzUKNjM5IDAgNDc0IDQ1NCA0NDcgMCAwIDgzMSAwIDYwNyAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDUxMSAwIDAgMCA1MjcgMCAwIDAgMzg3IDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCA1MTEgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgNTc1XQovRW5jb2RpbmcgNDIgMCBSL1N1YnR5cGUvVHlwZTE+PgplbmRvYmoKNDIgMCBvYmoKPDwvVHlwZS9FbmNvZGluZy9CYXNlRW5jb2RpbmcvV2luQW5zaUVuY29kaW5nL0RpZmZlcmVuY2VzWwoxNjIvY2FjdXRlCjE2Ni9lb2dvbmVrCjE3MC9sc2xhc2gKMTg1L3phY3V0ZV0+PgplbmRvYmoKNDMgMCBvYmoKPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCAyMzc+PnN0cmVhbQp4nF1QMW4DIRDseQU/uL2cAVk60TiNi1hRkg9wsFgU5hA+F/59dtd2ihQz0sAMy85wOL4fa9n08NnX+I2bzqWmjtf11iPqBc+lqvFNpxK3pxKOl9DUcPgI7efeUJMB80OfwgWHr90kJ+MjE9eE1xYi9lDPqGYAP+fsFdb074pGSWLJT+sUvACAWM275AUAxGo2kxcAEJM0XkDSsKSckazhrB29AICYJOWsmC1n7d4LSO5Z0hCLInmQI6MTs+OXHRmdmB2bl8ULYHRRtnp9nxfkpl7F6HjrHesmdUpdXFOp+Nd4WxunNEH9ApDXeEAKZW5kc3RyZWFtCmVuZG9iago4IDAgb2JqCjw8L0Jhc2VGb250L0NOV05GUitQTFNhbnMxMC1Cb2xkL0ZvbnREZXNjcmlwdG9yIDkgMCBSL1RvVW5pY29kZSA0MyAwIFIvVHlwZS9Gb250Ci9GaXJzdENoYXIgNTgvTGFzdENoYXIgMTg3L1dpZHRoc1sgMzA2IDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDk3OCAwIDAKMCAwIDAgNjExIDAgNzY0IDAgMCAwIDAgNjcyIDAgMCAwIDAgMAowIDUyNSAwIDQ4OSA1NjEgNTExIDAgMCAwIDI1NiAwIDAgMCA4NjcgNTYxIDAKMCAwIDAgMCAwIDU2MSAwIDAgMCA1MDAgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDQ3Nl0KL0VuY29kaW5nIDQ0IDAgUi9TdWJ0eXBlL1R5cGUxPj4KZW5kb2JqCjQ0IDAgb2JqCjw8L1R5cGUvRW5jb2RpbmcvQmFzZUVuY29kaW5nL1dpbkFuc2lFbmNvZGluZy9EaWZmZXJlbmNlc1sKMTg3L3pkb3RhY2NlbnRdPj4KZW5kb2JqCjIzIDAgb2JqCjw8L1R5cGUvRm9udERlc2NyaXB0b3IvRm9udE5hbWUvRVNVSFNUK1BMTWF0aFN5bWJvbHMxMC1JdGFsaWMvRm9udEJCb3hbMCAtMTAxIDY5NSA2MzZdL0ZsYWdzIDQKL0FzY2VudCA2MzYKL0NhcEhlaWdodCA2MzYKL0Rlc2NlbnQgLTEwMQovSXRhbGljQW5nbGUgMAovU3RlbVYgMTA0Ci9NaXNzaW5nV2lkdGggMTEzMQovQ2hhclNldCgveGxlc3NlcXVhbCkvRm9udEZpbGUzIDI2IDAgUj4+CmVuZG9iagoyNiAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUKL1N1YnR5cGUvVHlwZTFDL0xlbmd0aCAzNDU+PnN0cmVhbQp4nGNkYGFiYGRkFA/w8U0syQiuzE3Kzyk2NND1LEnMyUwGSan8kGb8IcP0Q5a5W+2n908B1m4e5m4eluXfXwl9LxP8Xsz/vUCAgYWRMba4qcc5v6CyKDM9o0RBw1lTwdDS0lzBMTe1KDM5MU8BZH5qbmIJkJOjEJyfnJlaUqmn4JiToxAE0lGsEJRanFpUlpoCFExJLChJTM5KVAjwUfDy9QObhN2FaKIVOanFxamFpYk5DAwMjGsYGLsYmBgZWfJ//ef7z8x5guGnxY/nolP7J/d3z+CY1jSloaOzu7VB7s+B37Ma25rau+sku+untk1p/7H/9yyJaa293f3dHNOmTJk2vaWvfqL8n4Xfe1h/tP1xE53RNqd58twpc6sm1078s/n7fIm6iZWTmyubKuc0z2jl4GucunnCT6HlmxaybebazC3HxRwuYs/DuZmHZzMPLwMDAAbkjz0KZW5kc3RyZWFtCmVuZG9iagoyMSAwIG9iago8PC9UeXBlL0ZvbnREZXNjcmlwdG9yL0ZvbnROYW1lL1dSS0pDWitQTE1hdGhJdGFsaWMxMC1JdGFsaWMvRm9udEJCb3hbMCAtMTkzIDQ5OCA2OTRdL0ZsYWdzIDEzMTA3NgovQXNjZW50IDY5NAovQ2FwSGVpZ2h0IDY5NAovRGVzY2VudCAtMTkzCi9JdGFsaWNBbmdsZSAwCi9TdGVtViA3NAovTWlzc2luZ1dpZHRoIDExMzEKL1hIZWlnaHQgNDQyCi9DaGFyU2V0KC9hL2IvY29tbWEpL0ZvbnRGaWxlMyAyNyAwIFI+PgplbmRvYmoKMjcgMCBvYmoKPDwvRmlsdGVyL0ZsYXRlRGVjb2RlCi9TdWJ0eXBlL1R5cGUxQy9MZW5ndGggNjEwPj5zdHJlYW0KeJxjZGBhYmBkZBQL8PFNLMnwLEnMyUw2NNCFMEAyqj+kGX/IMP2QZe7+Hfqj7acXazcPczcPy4ofn4S+5wt+z+D/nizAwMzIGFvU4JxfUFmUmZ5RoqDhrKlgaGlpruCYm1qUmZyYpwAyPjU3sQTIyVEIzk/OTC2p1FNwzMlRCALpKFYISi1OLSpLTQEKpiQWlCQmZyUqBPgoePn6gU3C6kBUQQYGBubEJGsGBicGZwZesM8YWBhWMuoxlv/6z/efSWAaQ8PCH7oLGTdfYv7B+YNPtHJD3rK4bo7fnL8Ffqv/VtM95Hlz/t7+S8flJyeuy9rdva570aLZq+esn3Giu5djalN3R2NXe22HXJVXZUBSN0dBx9L5C3snT9suP21r79QpG+ec7p8368ik5d293f0cc+qnVhXXlJZ2yHn9ni7aVtndWVMVH5JUnd/NEVl7aNPe1d/5F+2UX3Vsx4bN3RxnZlq2tna2drXJ8/1nXKsFceT3tK2Mu45/b7jM/L35p71oT/fUuhKn5s5Cueauhq7uSo76Kd2zdve2NLY0ZbaVyVn+vlUf2V7dnSGpc8D36neu7/zfNb+rPAu7YB2blJeTK1duJzpxYXfvlGl7zuyYtbSbY/2yXO+ArN/slanyWR6x6SndHJlZy67O6543+xTIEaLHGX6HLvju8JXxu+dC5u/xPaJnizalZJUX5RfML1wzfUb/lKlyfb09PT3dHD09XbWOGf5ZWfINDd2d3R0cLRPaJ0y7feM7pxxf6cIfztOmzfieu5BtFddlbjkulpCQeB7OVTw8l3l4GRgAUyELLgplbmRzdHJlYW0KZW5kb2JqCjE5IDAgb2JqCjw8L1R5cGUvRm9udERlc2NyaXB0b3IvRm9udE5hbWUvSkdZV1dJK1BMUm9tYW4xMi1Cb2xkL0ZvbnRCQm94Wy01MyAtMjAwIDExMzkgNzA0XS9GbGFncyA0Ci9Bc2NlbnQgNzA0Ci9DYXBIZWlnaHQgNzA0Ci9EZXNjZW50IC0yMDAKL0l0YWxpY0FuZ2xlIDAKL1N0ZW1WIDE3MAovTWlzc2luZ1dpZHRoIDExMzEKL0NoYXJTZXQoL1AvVy9hL2MvZC9lL2kvai9rL2xzbGFzaC9yL3NhY3V0ZS95L3opL0ZvbnRGaWxlMyAyOCAwIFI+PgplbmRvYmoKMjggMCBvYmoKPDwvRmlsdGVyL0ZsYXRlRGVjb2RlCi9TdWJ0eXBlL1R5cGUxQy9MZW5ndGggMTcxNz4+c3RyZWFtCnicVVRtUFNnFr6Xm+ReENHaZi1C7w1u7YfairVbZbbrVqxQLVgErd/CJUQJ4TsBJBpASCDJIWgiAvIRCLoUQiyDQHBxF61O2XFtp6472+mPdXRm223XcbddnXouvtl1L+mu05376z3vuc95nnPO89KUIoKiaXpBRlpmSZFYvPq1V5JLCvPmQi9KcbQUHyE9x7xP9sUrjLPblRDNQLTCH89uW4SNT6F+Ae5eSCloer/BZN5YUlpdrj+Ub9K8tPFlzeqkpLWaDUW6cr1WLNaki6Z8XZFokg+FmqwSrV5nqn5Vs6GwUJM594dRk6kz6sordXlyME8sNYnaAlGTkabZkr41jPT/5J6cjKK2wqSjKCqmXKzWmvN0GfoCw07/KEVlUcnUHuptai+1iUqhVlNbqHepNGod5aGd1AJZMKWgsqlbtEjPRCyJgIiHTAbTrVAoepU/U1pUv3j0OOYxfd1CQVCKCtAPHtxHjsE8rFAHPLgGI3lLndUM1VxuwHz2bKB7fHrnB3vf1KeIFt6JjIqsa1DmsNc8kCeQP6TksP2uz+EDGIQ/wxkHF3ydjXkcsfQOZQtg7gC+HqDPIYO5SDNSDl5Sf518jSRkkajGteJw9cDQYN+o19pjbuMDrWfAC9xn47okQcuS18iabMIkIld144vfTEz2Cgch5zt+WtV3Enx99WAR0pqhC/o5Ql9Vv7K22rAnN/DrvyLl//sJXq6+7TJF9gWkT4K0b65ug7RA7T59/OR14IKs3tEEuVAKDsh0cTls8xTU2O02p10gkeQEeQaHlFeCKqMjFUqgGDKgTE66zmJ6aFljraW4sTa28oAhZRM0QS3UuV3NQ+0wygWMfUWFxkr9vknxd7c++tNkLx8j2W0j0uIRehSjMRWjGCkfI9XtdXDsiNNZ28hbD5e+swo4kgC48sJMMy7GxZ4ZR6PT6XQ4Bbu95iiUc+I589m+0Y6pW4R17yApmwhHWBJ/dxW+iKv9yHnmisCINC9A+6UkRkqTlqtb28AFbs57DA7z5N+qOvKWckLlvuubRAa4r1TEH2Lr62Tl1tiqVujlpXmqbtyjFFUNCRVZZBFwiaqYWaUtIC0J4I7pMHe33D/8djZZ/b95H2ArvNmDKTL3pS+sIRry03+8gbFXpryB88JOFlPcyiCbboXzgiSxhA59pRa37qo5JGcvBHz60yn4rW9U8F+dcffDOExWB7I5eVr0e5RtBLMGcb0sRa5Z/zUj7cIydb8bzg5ZP98/JYgTWR3bZJDEN5aRRURzPxFX4IoL33d3HgXbEae9xiaUbdlcuVfOWObEuKvCoMI13ny+b8Q7/KEvCA+hlUQ375ZLMZRV3njsHMXLQRqfx6dxChMYHMSP1bhehT//5s59pFbeIUlCaGXO96F7CTmqQddDGJG/hzAo73YCi0uke2pcBUmpm2EtWSUQAS+rZC+tq5tDlqa/w+dpVD5g0DjLqMcL/Hm5hXqdzq8fPx/wj/PkbbJZbuXMj6xzAwZk81wPWyddWiGbh975w/r+Pgz2PkZ9KcO1yXBjhmGt1mDQaocNY2PDw2M8eScM1w0+6HT4mqAGKrnQS+yB2vVbD9ccd9XzJ1p6u2CIGynrL9CXG/O3XSv64+1vLt3npQb2B5vInB8Fce+0/AQweAcDalz0z/ZTba03IDbIljmq4CCUQRaY5qzSfsbe0GRrcjgEsoW8oJRyWLLhyUMQ+viJmgG4Af2ymhRcwna1d3b4enA+SXzW0yBvnpM7VgUWPoc94/pUTpyG0zAhp7JHoArqmi0tTS11p+VBKZyRVH3f7Lwg/cgvPVZ7elzu23Pm3e2QbSv7MhmMYUZ/AYsTHE31AnkzlElSpULl3aDK4HgVdFAIy8NJt9lxQpHnQhRJka//FlQVO16GQ2CAlVAevsb6f3Fuc4vVB7E+aPG4uzBZEp/FjSHRfSQc9cKJ4x6vzIo2ayjX+GzcPhrj5HbF4bfqdWkDuqsTSuIIq78ZVn/zv+q/YO/eLB1N3aVE3487JeNcvCy/jtL6Inr0M7yUykjzpV+qveA5anNA/VG+yV5nbbSXdB10y8YpIsuB/GQpLqzwNwoDjQHbcRirHysHkVufCJW70rrulfC2XmezGbhKsJsEwrKHwdrpcUFXL/8r74X8j6AHYq/gU+0Ye7EsUNUj5J7Tu3ecerftvTa4yH3yJfQh495U7uKba05CO3B90NIv7EePusPT3wGD3FDJUJHJYrbaeKIk8QMFypjadimjFbN7T7WrglFIz+OjFNu3Z0dHBqOjkY6eT1H/AQYZdaoKZW5kc3RyZWFtCmVuZG9iagoxNyAwIG9iago8PC9UeXBlL0ZvbnREZXNjcmlwdG9yL0ZvbnROYW1lL0paSUJCUCtQTFJvbWFuMTAtUmVndWxhci9Gb250QkJveFstNDAgLTI1MCAxMDA5IDc1MF0vRmxhZ3MgNAovQXNjZW50IDc1MAovQ2FwSGVpZ2h0IDc1MAovRGVzY2VudCAtMjUwCi9JdGFsaWNBbmdsZSAwCi9TdGVtViAxNTEKL01pc3NpbmdXaWR0aCAxMTMxCi9DaGFyU2V0KC9EL1MvVC9XL2EvYW9nb25lay9iL2MvY2FjdXRlL2NvbG9uL2NvbW1hL2QvZS9lb2dvbmVrL2cvaC9pL2ovay9sL2xzbGFzaC9tL24vby9vYWN1dGUvb25lL3AvcGFyZW5sZWZ0L3BhcmVucmlnaHQvcGVyaW9kL3Ivcy9zYWN1dGUvc2xhc2gvdC91L3Yvdy95L3ovemFjdXRlL3pkb3RhY2NlbnQvemVybykvRm9udEZpbGUzIDI5IDAgUj4+CmVuZG9iagoyOSAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUKL1N1YnR5cGUvVHlwZTFDL0xlbmd0aCA0MDAzPj5zdHJlYW0KeJzNWAlUU+e2PiHAOQ7gePrA3ibYa+tQa6XKVdtaFedZwbnIHIKQEAiJCApEJGTYGRgDyJRAGAwIyOBcnGhtpbbWa1u1r1o72ddb7WB9/6E/73r/Izj0tr3rrfV613qLtcLKn//8e/97f/vb3z4Cyt2NEggEI9esCFLIIxL8pz4fJJGqZRFKfvVZ7kkB9yc37ilhFLb8rO3N8oChQhjq3vCnJ8aP5IaMQB3DkG44NUgg2JqQkp4NecWO+YrEVOU2aazKb8L8iX7+s2bN8Jsnlyi3RUUk+K2MUMVK5BEq8kXmF6yI2iZRpU7xmyeT+QXxTyT7BUmSJcrtkmiyGB2RqIqIiovwW7PCb9nKVfdP+pWPDxeiIqLUKkmEQqpIkMRL+v8l319L6/+MVpDToiQJKoqinktQJC5QJqvU21MiIlOj0qIl0thtwXHr4mXyjRMmTp7ywlT/Oy+5qh11B44co6jV1BpqLfUMFUyto9ZTG6iN1CYqkJpPbaEWUK9RC6lF1BJqKbWMmk4tpwKoFdRKahU1kxpEDaa8qGHUcGoENZK6TD1J5QuMAhCYBGaBRWClJpOoU+6UgbolUAiuugW6fSOcJjS7C92PeyzzOO8Z5XmGfop2MjTz1SD5oDODlYMvDlk85ObQZ4Z+7lXp1ee92Xuv9zHvn4YlDjsy/JXh6uHdI+aPHDLSOSp+1I3R636+533P7TkLBS7O1yFAbheQ5IIQuaFvWWynQzWwRCSjm01d5jqohROG/UbGRS/RwGExWkHfuXim55xtfZAIp//Ozo2gBOM+BgXTcANcspb08riqtRAIIeGKlcxPNB6r8ZDR7xdkbxF7c3qNk5vqENRfQ/uuCbl4FMei0ePvYi88YhIW4BGYvfMcGoS8vr+FRolwEg5hwyHMnnhEeQjeghZog1M1h+qOHLW3wyE4qK4Nrw2HtSABKaxXh6lDwxI2A9N/UbzBhSY7uElOwd0LqOyaEM1HX7INXccbuoC59NY07IG9Fs6YFyWp6kgUp9vBBFXMOvfcnZ3rPwIGDblzC7Fo1KTb2H12RJg6RXyexuP4S1zM14aJ+2IfxGG/6YS5Fuqgy9D8KGKv0MQD/DkJda/AJUCiG9yo74TNvXNYWQ192ayRivtqaKlB86ooWca5eaJXkAhNQGtQEH4S+eM5Yjz6f/zYEEN+k5g7QDeZ84+KvvN8Gy6ouxYcXFMYALMBC3eslm5JSQxa+yx/WYHONZDVu+fv3hCiuVdYNIW+3fnFR/lmMJpFemNKJiQxCvsuR4WzuLFR1hAeKF0Sni5CbjQWP8zMIxi0mY6a66EdjkK7nnFtp+cZbC1iYseYRGkc3F8qBU3H0M5jQhSNNrAO3Sc7YT6zKTZq9gxp96c7RPoSo3U3MBmQnSrGPnQaZNtyTabKSpHZDOaKykMRh412EmD6yLt/PSnfl1ksjmuMLogoCLYtKYE3mObajhvILddfZhaZsgrBDEwJ5FaKv6fLIV+TY4DMDJFetztLp5fUR8IuYLC3ZElwbJmiTi1uTKrX/jWdj0f255TGicZXIYULzawS3LmAxjiFaB43jkV/duE/oxVKT+Msud9EHaP+FDfQeNjXmS0X6y/2iLqV62jCSzFL4KtyUT+KyIV9Ox+UC/fkVdafRuNsHi564Z7842KEaDz+UQQX0dNfWxw4Q3f0hAh9OvDLFdC/KMaT6NkwFU0+c6j+7Qb+5GFvURo7N8YlqOTShdx0dIMtrC4/eN5EMKQyJBhVkAxbzWkmRka3Gm0q2AFZBp1O8wwu8MHuqFFbQiJj9q1tgwqRi5Ybgw0qSIQok9xMHjgHVm2JDI3DyCcv3ZxTAiVgLbQW8VVxceaA1YreeULuHvqCrXjdVdLMW1UTq2pIgFDzDt5qi9GRSr5pwZCTMRUX+kxAjbpioxlyfevaoIoYTTBuMiSDHBL6jbZCmaEoI1drk+fvCMA5PpNRVU4xcdL6YH+/kzrYYJLy+1+HYn1xPBqJ/9snN92yh3fSnJdb+j1q8fkBt1o1/JJvCVjuO87ptS4Ub6/78phD0HkZRV24RNJ54yPWGlUXcRQY+1ul/yk+owym1yq1mjjd4VwRCqGhE9qkrdFtm8teA2bWopCl8qq02vqKqtrinKatJnFd83FbIzDHu6NfFMfQm/QL9Svkc7cpN0IY89K3ynPnj7QfrRRlo5nseHry/G2hWyKbXz/deQ3NynsEi+cdKKaTlByqJNwyl/Ni0fsDHHEfDH1fO2mTqaC07uheVefmDx5xysTb2HN2eEjqTrEFl7EokoZKMNnK2s6cLqiBw9CSVCOpjbQEQSTEGFYnhG6XSeLWQyhIa5JbCU1ZoILpxw+OcFRc59QEQteF3DSSy9pzXaYyeIigVAgxq/lcuoxlaYSfdxsNOZnPYqMPFiKHrshgJcmpbYVKkpwk41JD2iMEHSMelcaiSX0Cn5yM9DhthnqdYtFyyAECxAqXtcABZUztjjJVijpDHtopff1cR3d3DQmM4LiSL7ymm61Owf5raONNIcLcE2ykXiuHDCapIqW+tqp833tz2l7FI17AFB6OR9+egBg0phENLSraDXoCcq1BlDBlWWYMMBumHUQBaMYnnScLzxmlzeL+88HJjSbNgxsk5MZwz7CFNh5kTMkeSBXhS54aHODh9Mz7obIVuZGIe3rixj6vLA0YIMc3PZ/cFV3zLEerPZSeWXjw9jDMEAYZ7PlPKSWuV54XcsNPs0tpNP5hrXN3Hk8vltEJLeG16/kDnp6IWTzq1gTk2XN4f6NTjOzuOIImCTBo0uSvharD+fTVJh9I6tB3Qwe0ms82HLa7DjSdgmbo3OUKtaURB1OZhwGU21Hg/QaZRdwYg3ayDgvYb6//ljRH5oVx5HPMT5MQjbwPI2Fl0R5zTpZBu8cgjh3nD7thM0TtU7XKj8F5aGFMdjYPTbzu3A/Mj0X4yWi+++LIahT0xvVqBNWCsx+iwA+bTwiREs1lp8N31XWmxr1OcUFxdX07MJ/BeFWiMX6XUrwnU6WIAmZCAdum6NBXAvP1hQuXW3e2KqvFLftbcitIDnLBYtTocjSQyaSVZJYVVeZXl2c2RqeEaSMjRBFNERY1MFMWLXo1zCFxbhdn7EqNg1iQlUhcKavT4sMhipl/ex3yRoPvdF1pTu/aVC/aUL8WVhHYhkO2Kd663QWNYIXq8ioGjcL32GcXdZ08cqD1kEX8pucN5A4B6xdsnih+TOHwiXzA2ltRLvu4LpkBYat3Shkj+t7z8YIt/6NkEEkke5Z3w6MTed0VIOF5IVrIrWKxH2/snUItgY/jwRGNplPmGgKE5v4jlupy28RW7gDbqLQrZOrEpERHkqveaW8cIB61q3dqveBMD3qvR4h+4HxYPNq1cm/0KXjD9/1Tb11BLzvwzNA8kUkLWttA80Rb73fPbKMxM1sUGyRviOx4EXyxcMa0sTNPBl5JFNt0XZnvp9Vl78uuj7EnFcVBHLNg8/wZCXNyj28QrT2l7zG2GIv2gP5BVw+539XzTWZbvghMFkvTSde2HukXpOAGfXwLuX239JMZVXy5+lt4kir7nEu+H4eomzdJIGL4QEzmA3G5kI+6Hx2aFbA8RwUlSaK91qpSqGNqU6sUitSUpI1nFN0fvt3zlah3intDck1CQnJyQkJNckNDTc39JkraGQnyLDua1ypocaKCL1CBU9g7pXcW24c8d2A1oYJSFLC3CT0HFSRJRQa7DjSgYoj82gbpT4uiZJ4dlh/gCPn7ETqI1Imin4bS/WKuhoYqKK2w2khyK4E8vjeuFAcwTs9+5iVG6QaUxJO/ELWjMtZozskjzGy9eLao4FxXp9nOd9QEfQYoiC5daU7nWbjSWKSBDEhJz9yTjcdibx9ORf8m+pqI+mqAfdBubDc8QN+PdM/py2M7gyHVd0eabgfZVW9602yHBmgwNvC7YkANkpMPkdcr6ByAXe8y9nesnDQ7wQnHDa0PMe59Tzhv/S8GhD9yRvi/ThN81aZJ0j+bZwv97cninrvwLKW2944l+ubnZOHPY7lP2Irug0VtfE+UG2RGolZguTmJz8a7xqo4oqq0+j0GLZ7UF+mDp3MZRqshjyicfW9ALWmKUv1iMldIINwcyT/RDXYoljoW+mBx3zwcwGmMFmM+WH5n94dgzSmOQSF9vT6WLMvuEigFc761BM3icn3QS315/Yu+/asDmhVb7D8HVwluvyFE13E2SwBlTivHI9DIeLQW0POAAg+gQDQMjSjhlZWFyddZtLv9F+NRa0SbsFsmFpIQzbZhr9PY4ywedmNZETB5Vku++Beno53k+E5kYeGKBnmtQx6r0bCAd4hg1+p0Wj0YKlPFtyZ042mAJYDnxOAFpEuPzMgkDUrPaK26vKIv30Oj3hR1IbdiJITrA4jD6+1oLmG6pQ4hWlzGmkzG9OWKVWEhYq3WaAQdk52ryy2+ehXRPckdUbGpiUlJ5YktZcV5BUWiAcjeFdy4K0TrTrK/LnTSs/p9P8P9h5Bb1hvC5pbwwWFsWYWadN3u3XoR/q+/B+ZkEIYy+GoKs2yl1qIiC/+g2tHr4xC0XUMFZNQMR5dY+Djn49gPtv5tVlkErIbApMgp8YtyZsPLMNfq3zHn4EuXUo7DKfjIfuibxgu5V+Aqg6X4EhsNa5ypf9OchZvwJvTA+YJTlWjIh4VOaILzqZUTioJhDqyAubBYs3zX5CB5KPCaldRRq+sQiXjiB0J0tHc0WwNmlWjl8mkQDwQhVeSoE7VM314CnUx/UbzMs9nyGRm62uEzaCZcFE/7622tA/PyIjVypvCKoG/mv2dk/on7O1uYX2CDYqYitSJ1l0aj1YnwUPx8earHgwwhxUCSOO9fZwlf+S2KFqx5ioKq3r+kCJAYfcOifPr4nvyFpFz4GQqfpt85Vrb9pVUeuJkOtcG7hAliTFvMSlL+GwxxPBO8a+M5I4OeG7yzyuMxgfm2EtWn8EKt/YU/VGPqkPH3w0BwKD2KXnQhzXbk9wKaruaHkaDX0JhF6GWlEF1F2b+eFpqxmt0i37QeNjJbj4b1vH6irbtMZCnelZuzO02qSCFIkG6qPtJ+wNVcIy4gotz2bxxnzOoiVdnOAZnJX+V5J8rajuRqAqyzx9CmpULkTpTSLxUjdidDSqxcFgPh//oGDQfs4s+qmz4ls8FDTSr6DU2KGj3/Wc4+cWsyEmofvWFYugM5UviXDI2b/5++ZPiWW/avyqV/TpmkRp0pgj9uTEHJ3J3/ldWSsQLU7PcHDkjoVe5ztk5VK9+WolAmVift219V7xR5qx3c/HKUYMtzeLoGXxgiGuy+bl3Y0EGuoUMvDPWiqH8ADRSyvAplbmRzdHJlYW0KZW5kb2JqCjE1IDAgb2JqCjw8L1R5cGUvRm9udERlc2NyaXB0b3IvRm9udE5hbWUvWklFUU1WK1BMUm9tYW4xMC1JdGFsaWMvRm9udEJCb3hbMCAtMTk0IDYyNSA2NjZdL0ZsYWdzIDY1NTQwCi9Bc2NlbnQgNjY2Ci9DYXBIZWlnaHQgNjY2Ci9EZXNjZW50IC0xOTQKL0l0YWxpY0FuZ2xlIDAKL1N0ZW1WIDkzCi9NaXNzaW5nV2lkdGggMTEzMQovQ2hhclNldCgvZW5kYXNoL2ZvdXIvb25lL3BlcmlvZC9zZXZlbi90d28vemVybykvRm9udEZpbGUzIDMwIDAgUj4+CmVuZG9iagozMCAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUKL1N1YnR5cGUvVHlwZTFDL0xlbmd0aCAxMDY0Pj5zdHJlYW0KeJxdUftPW3UUv5cCu8PKxmYNOL2tcSxsQQJkEVExASTZcA9kOHTZGAXKBvQ1aGEU1sfa29ve0wft7YMVClwcQTqBsXUwUEL2w0ymmDkfLBr9QRN1/gHme9klwYub/uCPn5NzPq+DY6kpGI7jWTVHanUapbao8NXDBqW6rXlrmMvvwfkXU/iXJCCcXE9bL0gDqQSkqR8//m0XUmehMztQ3U5MguNnNN2VOn1vZ9u58wZFXuV+RVFpaYmiXKPqbGtWahVHlYbzKo3SIAK14oSuuU1l6C1QlKvVitqtiy5FrapL1dmtahGHLUq9QdncrlTUHFFUHz32D9P/vf2HMQzb1ldQWFR8sATDdNgOLAvbhe3GZFg2RoixsFQsHzuOLeGv4espyxLV483MTXxzD4ZYDn/EN0j4ccTKYmGIRGzQL9/IT++3gdUahphc3CtOYPAXjtIQLkGdX8i4vjFdR7um/ULcNDGd+GSKfEJl5NazORxt/xKt3pPwK7xDdtOJnhdklW+eFtKPk6cKGoVKKCbUIMjRC1/9mUS5q2Ty0Rwqhp8IYWXDIetoHP3h5zX06cy38utrd7hpIJKzmnfLq4TZxny58fSHH9QD8UQKErwugfBRnG9Yl8k8Pi8LXoKl/BTFMP0W8u29VQ6ty8lYGTARlij4Y58FZpZIIWNDa++w28ogx55ussJFSwT887d9MC9fgEXXAhDBQRiJ2qBPbk83g8lj9hh9lAeuQMAb8vifahsfBDkU5dANMWvpFJJy6JVvJCj/e9nyL98lE3FU8Ovv9wDJCLSt7E6ZkFcqNFT3BBpv/3gLSefil2fMlNNJucHtcZMBi98MFkLb+vIxi21gcnUN1aDM5JL+dSH3YEleDdmgV3Yp66tKrFXAELYwDES93oiPZCfjs1NwDSZ7rpomehPGmyBqLaKKSNgODgvD9NHyS00thw6BDlrYPv85tiWkBitcdruZpxGEk2NIleAfjuILfIWEL0L1Mn8MvOAjJro8Lg11wUSTLptKOMBQFRNJszbcGjGBESzgBjODxpHvLnoIHmLMNGDTUga9g2wXss4K0ncE4i3hueIq2uVwdZigj6CDZjYQi7IRMsSGgp9DHEZhxjro/MjG2VuF/dl0v8hIE93DzvDIYGQ8QF5H+wIMCwHImVwdHBqgoqJpoG3yXkcnbQIH0OD0/PuGMf4qt/QAR3t5owQd4E/IqC1/YlEsBCMeT9BPrqHcP8ZmYQQGHMO6Ie2QcxiGIOa/EtwnvMG42w8XCtUXFaK+MW4dvhG6Ns2SCSS9hXbfRTtXUOri15PzoUGxFS8hFks5aXDRZH9P6/utzU2QYwRXzDVNz9FTTaCHU/b36saXxV0PEbaB08owlxxkppHjKyOoyzvEpScy7j9DZqTW1Z2Vbk9Ipfelz2LY379OBoEKZW5kc3RyZWFtCmVuZG9iagoxMyAwIG9iago8PC9UeXBlL0ZvbnREZXNjcmlwdG9yL0ZvbnROYW1lL0tKS1ZHSytQTFR5cGV3cml0ZXIxMC1SZWd1bGFyL0ZvbnRCQm94Wy00IC0xMSA1MTYgNjIyXS9GbGFncyAxMzExMDQKL0FzY2VudCA2MjIKL0NhcEhlaWdodCA2MjIKL0Rlc2NlbnQgLTExCi9JdGFsaWNBbmdsZSAwCi9TdGVtViA3NwovTWlzc2luZ1dpZHRoIDExMzEKL1hIZWlnaHQgNDQwCi9DaGFyU2V0KC9hc3Rlcmlzay9tL29uZS9wZXJpb2Qvcy90aHJlZS90d28vdSkvRm9udEZpbGUzIDMxIDAgUj4+CmVuZG9iagozMSAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUKL1N1YnR5cGUvVHlwZTFDL0xlbmd0aCAxMDgxPj5zdHJlYW0KeJyNkm1MW1UYx++leHsHdcC0mcR52i8anRF5SWbAaTLIQCZreMtMlI1UKKXv9LbQllKojBbLU17GSinlpYW1g5bqmCNOcM4pSroZFNlE57JpIglGjV80OVevibbgF6MfTM6X8zzP+f/+zz+HJFJTCJIk91dW1FpaZCZGYZQxeblPVcvkrWopk2w9yj5MsgdS2Ed4vXbW91vKfSDggSB17kDqxD5sz8K6DCzLJHgkedLwWomuxcIo5M1G8eMlT4jzCgufER/RyBhFg1QrPi41Nss0UmPiohbX6BoUMqMlR3xErRZXJ18YxNUyg4xpkzUmio3SFqO0QSkVV1aIjx2X7Cj9t8N/VgmCoA2tmoM5efkFBFFLnCAkRDqRQewjHiAeJPYkViVSCS9JkMGU3JSqlGleFs//+5972WlHnBVKyJtx/LWEx9bjLCH8wgmDqiHjoGmAGdIOlo3CW3R4PvrDvTGNfgC52/vbo0C/CcGAaJ0/DiGNodfW40L1wXowA809ayyUP/1r+7ZNdOX0nU44QTc1KXPrary3GeSc7PXqgW6GNpPoIN8KTMQz7l4YRVfk11yzQOP8+XvvinYdCSQkLiplC7gcHuuJCiN6sKFT+C4VdYwoUDFlZUCtCcOY6B1OQeUVdakOQ2we4Rn+v/se6sl1w63FxZHZBeRWBWES6FvUeAiiSRLskK6t4PTk4bG+iDDaBnKEnfwAYCpf82pnow51vKGbVIIKNDZDqytiAgvQKqpDDxp1EC6IuOt8eVsCdz6B+47yhyGSlBDhMv7W4vLizFj7McTZ+Gbm75HN/zuS9Ictazy2DiuFMNHlcbmZibKYfgwTqw+NDvuGYYoehLkGxGWW9IfdazCX7YGYUgnKbq27vF+POOKGzeOCHsg2dVo6zF6Hr0eEyUqOqOzq6XSAKbsXtJcQzrzp0veVgza7G5SxGMQ8c31rrjCKK5oCn0vPW/vhLNAB36R/Jy+cReI6nMXDt68LFzRhlYxRq5si+vmLoWgU7Qb6sYnEznUeK0lkaQArymUnqOQ3MTu6upxIduglu9ll7WtzgY5mJp3+qcUzFy+jP4py+VZDYv8Q+EV4kPKHdgNI6knI97fwN6U8Vr0oDPOnYFrRDGYd4rqp1as/br596cbK0idwh8aCxza5vVza4cJDTbP2ieDMxOyo09c9jPxLy9EPgb771cmCoy9XldeKOD0n6T7tcIItWx2CcYS3qdiwZz7Js8fx2ov4Swl5eQtvJJBGXCyEjbrV8sjKhdmP4AP6i+JPOT4nOPpKqTJkCyQhY46R1wdQNLoBHqA/e09+StPR3NIqUukZl9xV3dsBoKXV5xKYJernqy9USWoqchue9y7rRUNDw2cgQJ8zBxhja4cq/6cqnIH3f7v9Pdpr97IlXvzc6ICbiqetp6O01NraesGeuECwLrifIP4C+zAihgplbmRzdHJlYW0KZW5kb2JqCjExIDAgb2JqCjw8L1R5cGUvRm9udERlc2NyaXB0b3IvRm9udE5hbWUvTE1KV05VK1BMUm9tYW4xMC1Cb2xkL0ZvbnRCQm94WzAgLTIwNiAxMDUxIDcwNF0vRmxhZ3MgNAovQXNjZW50IDcwNAovQ2FwSGVpZ2h0IDcwNAovRGVzY2VudCAtMjA2Ci9JdGFsaWNBbmdsZSAwCi9TdGVtViAxNTcKL01pc3NpbmdXaWR0aCAxMTMxCi9DaGFyU2V0KC9CL0QvSS9NL08vUC9YL2EvY2FjdXRlL2NvbG9uL2NvbW1hL2QvZS9laWdodC9lb2dvbmVrL2kvay9sL2xzbGFzaC9tL24vby9vYWN1dGUvb25lL3AvcGVyaW9kL3Ivcy90L3R3by93L3kvemFjdXRlKS9Gb250RmlsZTMgMzIgMCBSPj4KZW5kb2JqCjMyIDAgb2JqCjw8L0ZpbHRlci9GbGF0ZURlY29kZQovU3VidHlwZS9UeXBlMUMvTGVuZ3RoIDMyMDM+PnN0cmVhbQp4nK1WaVRUV7a+l4J7r4o45UaIeVVgnBVFo89oGxUUWFGciAJKCRSDzPNYEJkUrKpdVcggQ0QmjSIIJTNecYzR+Og2Q2fQlXQnErMyvOeLbYfelz708p3CoZNOuvvPW2fdH/euffY59/v29+3NMrY2DMuyk7b7+CbGaxKWurl6JMaFWz/NkWew8os28n8ogIT+1TKy0w7sFWBv2/LipPap+LspaJiEkZMZjmX3xqZlFxzakJikTYmOjEpznrdhvvPSVatWOrvHR6REh2kSnLdo0qIi4jVp9CXO+fXEsOiINO1iZ/e4OGdf645UZ9+I1IiUjIhw+jFck5SmCYvROG/3cd60ZetYpp/f7tlbmCYsPS0iIjEyMSEiNnvshWEYl4QAj8SkjSmpaa9larRbwiO2bY+OjYtftHjpslf+vLql6eQ5htnGrGJeYrYzO5jZzOvMTmYXs4AJYDyYPcxixpPxYtyYpcwmxofZwmxlJjKTmKnMNMaJuc3MYMpYAwuskZlJcWNsmThWxVaw/2uTZDOomK1419bBdpvtX+xy7AhXwdvzWfy3wmtC1TjXcWHj/jZ+zfjg8acmvDChyn6S/Wb7IxOfn3jOwfWvjxwe2eQOMSDhg6sscnSFIKfol0Q1f8p4w9QBnXBdf8ogSF4YwSN7d+C/2hoy1ilJk9cvA6r4H3d1zl3o5xuQrESGJ+sP2Kn5m2UQriI3eYdHiogkeo58T2LbRnoUcpbsIuIaTK6sMRrbzU7HzaVwCoRmidPo9kAIhMNaiDQLav4YPaE936AvLtLriCPZ4Ehewt12f5S4UN0iCIIwWA5RNO4+jynkBgkixwrz9PpYnVOmPsOQCUKCmusxD0AvzXIbLDpB4rU0d1Sl0XS41GTGVZjoiB6kyO4VNddt/h76oQuGoJ3GzbdeedlEKzT9FqyVWJyL0/EKzlRgizxBxFrcxD84tmbv2sAtC1TIZYvI1P32BtwWvln+GVmhHPVSIzN6jzBqOZzD1V98df+/V9wiEypUK8igSHywH/24epCgLeSMBl6HfCEPyHNkltJB/n1RlzzNwrbic1iGzynkTkwQ8YUlD8hK8orbbOJEJn81F3+Da4Ye4CQlKSc+IpkGyAz1w8XaNlXd2Z6GAeiBnpzGqIZo2AmBwnIgDJmmfMw08ZPQxSJP7mKHKdmH6QGYhH8Qoc5orKnp6r5U2gjCrbe3kqnExdN/0341XAlS5VeDEeqE9+nNKc1b+d6ovoJuEHDBn75GFTq5fUdmrfeL06apBnji+YT0CNWo8R9rhAKa/OBJDVA0X5Sn41wFzh2ZJj4GagOPu3EBTsIYTCILUCR7VKMeag51o/dE+R7O4X8Hf8wa8GwPKPeGxUDYPL/YoEDvl3O2Ak3N3ql9UsbDyAyjgwKvPC8txw38ByfQ4QelwaDNgQQhtDWzubm1oevyrmZ/z7DdPmlKtOPJ6p9UKr10u7HfdA46YNBaMDTzO0eZIos8o5Xt+BDzPlRgJErivrJNlSAJN7+Fuk86EnYeVhrfMBY3gHAcDjepPuProEJ7SA9Z4crk6qjSMBDiCb3x9LD6pPpsVVdUR9Ht/ZaiT3Jhu7B+CaS7JH/Um6csrimG/SCkQlGairB8FhQeKzPBmw1KIzS+aTadi70E9SC04GLAOb2Z7VGnVOHNsaWvVluveOkVpqgLH0m4rp59SIkdLykw3qqwORKZg6+rOcPcPfPm6YTIP5CTvPNQ+oef9l/7SHlZHcB7x6Ym7oDBJlofbPeLf2eH1nkPZQY5q9Lmw1665kMoVRpylC1OzXeb78M5uu5DN5UL4ax6WWtkDtXL69XsCXwD60iMfJC4K0bSZXvxcHVJ+XWg8kvTxxtSIQnUpnQjlXfFgCFfpysy6FQzSQXh8Izd2xIXa/DXx4MGNKYcGnOdb9gNejDAIWvcGrLTcSZWWsNC9aGGUIiDYFP0WBi6jo4vyy4pOAFOJ6DkSFntMFY5DpOqn32jaNks4aylsu00ulp1Ng7DqefJ42+LcbD3z8rzXH0VHG/KhVzVViNUQ5OAsfy9jb8l832Jw6GXg9uzTra0NvSczqrVmZSnSluNVSB80q5Zp4rmN+pIWSGQ2cJCnKx9/zOpu6dRmY1LxUX8wiW5KQFh7X3I4LKaC2arGIMeMERtkd+R2EbkFbKZglRWYy4btIKUSkFKgRQIMmVYQTL1Q57hUIHBoCI8MZBp2GR3ReJi9EsNBkikQaE0aJDHbaMuaYHpgbHeG8FpP+iNB8wVJU010C6cST0enZiQFR4ghd64/c7Nayfp+bYbWp6SPe+y/DecopB70VEkB+W38k/kQzo4kRSrhd3gIN1gSNSl6vT0LCFFzXWYr1FF98ClMeJTqKFmG7WmBLOB1qkgS0YeT4zer8isKq4Ep3oof7OkAltl4mgcEzlnMX8L3XR9N+bGhKHCZiSSzKdDPuSnCE/7kayw4I6rbAd1wcNWdu5L4paD0KmSf/y7x1C5+vO7O/1LY0Agc2a+TFyI4zeLcN6754+faFHt4XFzqR3ZjMMiMfO52tCd62iYA+DED87Dlbc6Vc090pEWuAZdsY0J1nMnPWfVkM8ZXP3YfQ98r5BDqfk2HoGTLUUf7b6g2mfxO+ZFkyxfPotMIS/8sABX4LLe4dpjWVCcrS/OKVJlBPrGbqIRLgZ0uqg6Y2vqNPY0nHmrq6uqGXAcmMh0Y5TVB4OHmPyeEa6LHURX2R1dFSNd+I2IcWQ2ziQpREtUZB7ZR8LQmba7TMxGJc5GjXLUlrwrbgePU6Gfxtw88CHchUHzl33vdb9768R1alfvJ3a91rG5ZC14g+ehZRqvIA/P9F1Wc1RMnWGt+PJuHHhsvdhBz8QevCqiJ0dNXPg9lYHNvC/JatXoiqedizJ9n3bOXirxjjGueIrKgEhewAGcweECWLdhI6wlC1XUOtYlP3bfe+jGooCMAtNkP5F4WKm6/sxZW4wDph44DRf1FjoybOGPyLfEvsg2TUhUVEhIe1Rvj6Wt74k2aLaHEvqP2bkCI9BDbG4qL62vadO0ac5oKvKOZoJOKNRCrlLNnzYNGQ+DBdoMbXpByuZDQFeSe5S8ipGO+PyDmsqKslvgJPFJuhRIptazyZRmVVUTmHXlB8gOMt9R3vvTqhq9QK961iiZLHAWzo9d1Yt/+o/yvatPf3Bkk/iPu1qN501WxM7p257sUlzc9dPxSvP4+ecj1tDAzbbGzLW/PmL924Aq/mFA9yw3/917k5WZ3y4xR9DxyDco1kf4lYHM+j/ErwHdKWWf46sKjKkXDxB2k1+GtqhYp4MioaCyoPzNH++i6r3oazv2xkWFhJ2MuNhaX2u2GtgYHGNb3RRYcEfsjWoPecpjb3t7r3WUoXAVSX31LObhBAX+ZUQhNsKR5Hw9HCxUrvWdBRkgZKrruaNw1Xi9ThitISo132cehgG6hqHPWnTOvDUPTsFr6NIhsddwFnqhUiHXYIqo5nSZhZv3H8wvWg85VHVzOanzUm//GeS+GJTgewH5lz6mpjDRdcvS7effON7a1XjhkuboGyXK5uMdlRYQ7p7zXu8V4e25UUX2Ek1uHu0ymU5a2d16ZH6v/Hkne/4uXr+rwEA5VcypSKyFNuGDr8u6vrgQ6r0nOTo4QdmXIn5gaemDd4Q/ufUTjqg81ywMPuZzJ1i5cl+wL7gLZCIyASji8s+/QhtkVkp7TinJOm+xXSslXY3rSzmbQSe0la45vvN9L965c7m3vf7JCLgy0mpC8lTy/zgCfiNfFo9WNFZCq9CS2BKblpebm6dMTtjqnpJwep/jM0rR8Rmtss0veSXdtr/k+hFbWEzrfGR6AEvHZEbxKRrFq912pPiXosCP+R8+TujY6G+HdT8z81J+1TY7munRDMYKQNiY+18iUxQ4Ki8UtTn6DCgUnvbfu0Qo3UG83WlHFIjTd4twMf7naRTKavZDgdagzy1WJvsEZgXQqlABzj33NuDzKLbX6nTVqgrc/C9hsHYCEnoHHbowPgbzxojAoUUKXI4l4q86OmX+iBgTezANwoVd/f7vDViaz55QllYcqj5Qmn+k4HDRgVxtVvF+CITQOx9f/vrU6Y+gWnjWNZQ/7xp4lftnLcfxfxaim071GCPokudFsle6FfIuebZYXmkyQYXQsB+0ylEFl0c22Elc6cMWy0MQvuRI6yhTmE/ru9ApvRrqlPJk7hgG2am5wjk5W4kjCCs4/I0s/StY8ivl7RUY1HCkkpPGIzdBOd52585g+3GSvT1y9hMZ5v8A13SjcQplbmRzdHJlYW0KZW5kb2JqCjkgMCBvYmoKPDwvVHlwZS9Gb250RGVzY3JpcHRvci9Gb250TmFtZS9DTldORlIrUExTYW5zMTAtQm9sZC9Gb250QkJveFswIC0yMDUgODg2IDcxNl0vRmxhZ3MgNAovQXNjZW50IDcxNgovQ2FwSGVpZ2h0IDcxNgovRGVzY2VudCAtMjA1Ci9JdGFsaWNBbmdsZSAwCi9TdGVtViAxMzIKL01pc3NpbmdXaWR0aCAxMTMxCi9DaGFyU2V0KC9NL1MvVS9aL2EvYy9jb2xvbi9kL2UvaS9tL24vdS95L3pkb3RhY2NlbnQpL0ZvbnRGaWxlMyAzMyAwIFI+PgplbmRvYmoKMzMgMCBvYmoKPDwvRmlsdGVyL0ZsYXRlRGVjb2RlCi9TdWJ0eXBlL1R5cGUxQy9MZW5ndGggMTY4Mj4+c3RyZWFtCnicjVR9VFPnGb+XG2/uHEPtvIprvDf7qnNTGB2tiq2tgj3VlYpaLaCgAQIhhEBiCBJYSPgISZ6bEMEgH3UQGNhh0XCKHAX8mu1K15bjyo5Hp2dV5rrWrlur3d5LX07ZDQk99q/t3vP+8X49z+/3vL/nRxKyKIIkyZi0F3ar9IcSfr52S4kuL7TymPgoKSqixJUUYNWXC6cPLoBoCqJl/Qr5a48g1xKkXYQyFhMykszSGquSS0orjIUFGpPyJ8mrlQkbNqxTbi5WGwtzVXplqsqkURerTNJEp9xdkluoNlXEKTfrdMpdoRuHlLvUh9RGszpPWsxTlZpUuVqVMu0F5fbUF+cifQPa/MSSVyIdzFXrTQRBLNJnlqkqclPz1IW79xQnjRLEDmIjsZfYQmQSKUQcsZV4jthOJBJPEi8Sj5JuYrHEmZARB4lRchnZGLU4qoZaRjmoL2SbZR8uSFowTsfSzV/OxsxGJawkoEuMniTfQgr0FFJQohPdYO8fGPrxD7KfKbVy7vP0ybqOHK6QLqi25fMzMnlBS3WQe50e9Lee4pFBPvXm0MVRvymdwyfkRU2VQ/NbUuyuDQR8IVZ/So6LFynxH+ID1t8KHggwfe7u/dzMdlpVbynK7ILXp6a8MMWfovFXMz67FVxQvqI2nEc8EI52VD5qgsJfYN7liufCsG3jYmznQ7DFOHYOaS6ttls1PLbLt2l2peyub7vEoXPyyNYcCRyUK98sftD3Ptwe4Qxh1MEIajFgm0SfvUf2o2VoYyhuHrrKosfxGiFjf0ZDyfOcucbhBBvUHdkp1Ar1PkcT+MArfcNnzwfeBuZyhzaN18g1uN2FV229svmvA9f8V0f48m9kwYp/s3FrwKkqPT34KeKbTghcjOjEqtvi7zrJ0+I2StSI8awvVCwf01cKVq6MroQyqDI5HUnY4HiecdOOsZyPDn5S8oe69gxgDPSvjGBo8NZ5HDzaOZNYa3banPYV1VpL9h7QgqnN1uTwugQnMI5aZ3Xtkee8DfzR6suuDkCLYQwtbO5hYmbJj+4SthvoLzfIYIh41nkWWhrOuVoZr8tvb3CB3c5ZqvbnbADmh/DZO7d8H3z+bl9l8Wn+mM0DjeAHz1Gh0dPsvyIV5Ghdo6V5X6DYvxUYvDpuK07GSXeeRT/re0sYmOBPtAsC+KWM1B/HJfVNf7+T/BAtEs+hRZR4Ed1kzen58bUaiWPN6Zx/mi/hpehqrGWoCCqAWR+R4b2wPPrC9RTbBHlL9llM9aQhBg/GHj/YAwPS3wPHh9FSNBHr77mMFE29jDDTRs89xPzFe2FhrKONYHRZcpiItCbR3Umk6gwVAu1BKyixYfp7YXXlR9RVLs8ezmjSSfQOAH4pMwvwHpR5ZvjYuWE+v80+MN8FWHGdBQvU6MuNlSZXNTCPwd+vXYOPkezVPrs5IHUJOTuXb+0lpJiUVMeiPERRYjLSst2/Ec4idscdvBonPpWA1+D19zaihME3fK+9xwdamrxwDJqcr7qbG/rdQg00SG1js6el76grArwAqu8632GES6x3vPPaFNyE9p3epyV2xKw31O9PdP4LrSTHJWafT/+SLaqsz+Wz5EV+a5C7SI9C14g09vFDeHCec7htvgoXPfh1g+euJMoQKXbfl0ItQTVoCYW6xVS2zJfaDW8wt297Bq6ct+4b4zqqBHcjHAGf19vePhXraZaE7YVmt2BsTTmV5ksH5nEcb8Y7UoJZvy7nL7w8Yr9RccUacATK/fYuM6iZtU84teu2DJzN4KqPuSSq9eBwOquqEmNdtRJtJ5gfFPhcE+bLrl5g0E8/nkDxSLb+g1XdIZD4LlF2R/xPSGOr0AVJ2dOJ848ZsYqZdfLV+u04BjO639/nxPVhTaRHpJYoxwUoKQd9F9FoywAqluyHyumfc82JOft5Ojyo6ZGIc6a8pN1WwFV+8qRXBYdBBdlanam8qK6K+Z9m+rfxwbHRFvPekJn+n1uSghKkN0XLUTpaTk6h5RTSi8lsr1swcJtog9tt4DfJDYK7l7tO9wpCL4+X45vswwvX5Q8fluLdyiPgTyL7LHoXy0n0Z7yKEjeJj0jm7fFBB3NGBxYOj9FWDajzT0LfyVseOMkHaezEb9utLgdUrlD1wyscmqBbT0FwUAf6jL0uyOCzaPQj8X02Uty5yh+W632Hh7kL9Jkjx3/Lx5QFxOxXUOEJr0BPLkSKb3MLqZeXPhP9rcnoaKSI/g5B/Bf5fZw0CmVuZHN0cmVhbQplbmRvYmoKNDUgMCBvYmoKPDwvVHlwZS9NZXRhZGF0YQovU3VidHlwZS9YTUwvTGVuZ3RoIDEzNzg+PnN0cmVhbQo8P3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pgo8P2Fkb2JlLXhhcC1maWx0ZXJzIGVzYz0iQ1JMRiI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9J2Fkb2JlOm5zOm1ldGEvJyB4OnhtcHRrPSdYTVAgdG9vbGtpdCAyLjkuMS0xMywgZnJhbWV3b3JrIDEuNic+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIycgeG1sbnM6aVg9J2h0dHA6Ly9ucy5hZG9iZS5jb20vaVgvMS4wLyc+CjxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSd1dWlkOjE5NWJkZjg3LTFhNTItMTFlZC0wMDAwLWI2MGUxYWEzZmZkNycgeG1sbnM6cGRmPSdodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvJyBwZGY6UHJvZHVjZXI9J0dQTCBHaG9zdHNjcmlwdCA5LjA1Jy8+CjxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSd1dWlkOjE5NWJkZjg3LTFhNTItMTFlZC0wMDAwLWI2MGUxYWEzZmZkNycgeG1sbnM6eG1wPSdodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvJz48eG1wOk1vZGlmeURhdGU+MjAxMi0wOC0wOVQxNzoxOTowMiswMjowMDwveG1wOk1vZGlmeURhdGU+Cjx4bXA6Q3JlYXRlRGF0ZT4yMDEyLTA4LTA5VDE3OjE5OjAyKzAyOjAwPC94bXA6Q3JlYXRlRGF0ZT4KPHhtcDpDcmVhdG9yVG9vbD5kdmlwcyhrKSA1Ljk5MSBDb3B5cmlnaHQgMjAxMSBSYWRpY2FsIEV5ZSBTb2Z0d2FyZTwveG1wOkNyZWF0b3JUb29sPjwvcmRmOkRlc2NyaXB0aW9uPgo8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDoxOTViZGY4Ny0xYTUyLTExZWQtMDAwMC1iNjBlMWFhM2ZmZDcnIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vJyB4YXBNTTpEb2N1bWVudElEPSd1dWlkOjE5NWJkZjg3LTFhNTItMTFlZC0wMDAwLWI2MGUxYWEzZmZkNycvPgo8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDoxOTViZGY4Ny0xYTUyLTExZWQtMDAwMC1iNjBlMWFhM2ZmZDcnIHhtbG5zOmRjPSdodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLycgZGM6Zm9ybWF0PSdhcHBsaWNhdGlvbi9wZGYnPjxkYzp0aXRsZT48cmRmOkFsdD48cmRmOmxpIHhtbDpsYW5nPSd4LWRlZmF1bHQnPnN1bXphZC5kdmk8L3JkZjpsaT48L3JkZjpBbHQ+PC9kYzp0aXRsZT48L3JkZjpEZXNjcmlwdGlvbj4KPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSd3Jz8+CmVuZHN0cmVhbQplbmRvYmoKMiAwIG9iago8PC9Qcm9kdWNlcihHUEwgR2hvc3RzY3JpcHQgOS4wNSkKL0NyZWF0aW9uRGF0ZShEOjIwMTIwODA5MTcxOTAyKzAyJzAwJykKL01vZERhdGUoRDoyMDEyMDgwOTE3MTkwMiswMicwMCcpCi9DcmVhdG9yKGR2aXBzXChrXCkgNS45OTEgQ29weXJpZ2h0IDIwMTEgUmFkaWNhbCBFeWUgU29mdHdhcmUpCi9UaXRsZShzdW16YWQuZHZpKT4+ZW5kb2JqCnhyZWYKMCA0NgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDUwODggMDAwMDAgbiAKMDAwMDAyODcyOCAwMDAwMCBuIAowMDAwMDA1MDI5IDAwMDAwIG4gCjAwMDAwMDQ4NjkgMDAwMDAgbiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDA0ODQ5IDAwMDAwIG4gCjAwMDAwMDUxNTMgMDAwMDAgbiAKMDAwMDAxMDA2MSAwMDAwMCBuIAowMDAwMDI1MjQ2IDAwMDAwIG4gCjAwMDAwMDg5OTggMDAwMDAgbiAKMDAwMDAyMTYyNSAwMDAwMCBuIAowMDAwMDA4Mjg1IDAwMDAwIG4gCjAwMDAwMjAxODAgMDAwMDAgbiAKMDAwMDAwNzg3MyAwMDAwMCBuIAowMDAwMDE4NzY5IDAwMDAwIG4gCjAwMDAwMDcwNTAgMDAwMDAgbiAKMDAwMDAxNDI4NSAwMDAwMCBuIAowMDAwMDA2MjA0IDAwMDAwIG4gCjAwMDAwMTIyMjMgMDAwMDAgbiAKMDAwMDAwNTU4OCAwMDAwMCBuIAowMDAwMDExMjc2IDAwMDAwIG4gCjAwMDAwMDUzMzEgMDAwMDAgbiAKMDAwMDAxMDYwOSAwMDAwMCBuIAowMDAwMDA1MTk0IDAwMDAwIG4gCjAwMDAwMDUyMjQgMDAwMDAgbiAKMDAwMDAxMDg0NyAwMDAwMCBuIAowMDAwMDExNTI5IDAwMDAwIG4gCjAwMDAwMTI0ODMgMDAwMDAgbiAKMDAwMDAxNDY4MSAwMDAwMCBuIAowMDAwMDE5MDMxIDAwMDAwIG4gCjAwMDAwMjA0NTkgMDAwMDAgbiAKMDAwMDAyMTk1OCAwMDAwMCBuIAowMDAwMDI1NTA2IDAwMDAwIG4gCjAwMDAwMDU0OTUgMDAwMDAgbiAKMDAwMDAwNTgzMSAwMDAwMCBuIAowMDAwMDA1OTE4IDAwMDAwIG4gCjAwMDAwMDY1OTcgMDAwMDAgbiAKMDAwMDAwNjY5NyAwMDAwMCBuIAowMDAwMDA3NzE2IDAwMDAwIG4gCjAwMDAwMDgxOTYgMDAwMDAgbiAKMDAwMDAwODYyMSAwMDAwMCBuIAowMDAwMDA5NjMzIDAwMDAwIG4gCjAwMDAwMDk3NTYgMDAwMDAgbiAKMDAwMDAxMDUxNiAwMDAwMCBuIAowMDAwMDI3MjczIDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNDYgL1Jvb3QgMSAwIFIgL0luZm8gMiAwIFIKL0lEIFs8MTdCOUE2RDk2MjZENkMzM0U3REVCM0I5RDAxODMyREM+PDE3QjlBNkQ5NjI2RDZDMzNFN0RFQjNCOUQwMTgzMkRDPl0KPj4Kc3RhcnR4cmVmCjI4OTMzCiUlRU9GCg==", + "round": 1, + "pub_date": "2010-01-31T17:18:19.768Z" + } + }, + { + "pk": 2, + "model": "contests.contestattachment", + "fields": { + "contest": "c", + "description": "unpublished attachment", + "content": "data:problems/1/sum_7.pdf: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OCA1MDAKNTAwIDUwMCAwIDAgMCAwIDAgMCAwIDAgMjc4IDAgMCAwIDAgMAowIDAgMCAwIDc2NCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgNTU2IDcyMiAwIDAgMTAyOCAwIDAgMCAwIDAgMCAwIDAKMCA1MDAgNTU2IDQ0NCA1NTYgNDQ0IDAgNTAwIDU1NiAyNzggMzA2IDUyOCAyNzggODMzIDU1NiA1MDAKNTU2IDAgMzkyIDM5NCAzODkgNTU2IDUyOCA3MjIgMCA1MjggNDQ0IDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCA1MDAgNDQ0IDAgMCAwIDQ0NCAwIDAgMCAzMzYgMCAwIDAgMCAwCjAgMzk0IDAgMCAwIDAgMCAwIDAgNDQ0IDAgNDQ0IDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgNTAwXQovRW5jb2RpbmcgMzkgMCBSL1N1YnR5cGUvVHlwZTE+PgplbmRvYmoKMzkgMCBvYmoKPDwvVHlwZS9FbmNvZGluZy9CYXNlRW5jb2RpbmcvV2luQW5zaUVuY29kaW5nL0RpZmZlcmVuY2VzWwoxNjEvYW9nb25lay9jYWN1dGUKMTY2L2VvZ29uZWsKMTcwL2xzbGFzaAoxNzcvc2FjdXRlCjE4NS96YWN1dGUKMTg3L3pkb3RhY2NlbnRdPj4KZW5kb2JqCjE0IDAgb2JqCjw8L0Jhc2VGb250L1pJRVFNVitQTFJvbWFuMTAtSXRhbGljL0ZvbnREZXNjcmlwdG9yIDE1IDAgUi9UeXBlL0ZvbnQKL0ZpcnN0Q2hhciA0Ni9MYXN0Q2hhciAxMjMvV2lkdGhzWyAzMDcgMAo1MTEgNTExIDUxMSAwIDUxMSAwIDAgNTExIDAgMCAwIDAgMCAwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAw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OTowMiswMjowMDwveG1wOk1vZGlmeURhdGU+Cjx4bXA6Q3JlYXRlRGF0ZT4yMDEyLTA4LTA5VDE3OjE5OjAyKzAyOjAwPC94bXA6Q3JlYXRlRGF0ZT4KPHhtcDpDcmVhdG9yVG9vbD5kdmlwcyhrKSA1Ljk5MSBDb3B5cmlnaHQgMjAxMSBSYWRpY2FsIEV5ZSBTb2Z0d2FyZTwveG1wOkNyZWF0b3JUb29sPjwvcmRmOkRlc2NyaXB0aW9uPgo8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDoxOTViZGY4Ny0xYTUyLTExZWQtMDAwMC1iNjBlMWFhM2ZmZDcnIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vJyB4YXBNTTpEb2N1bWVudElEPSd1dWlkOjE5NWJkZjg3LTFhNTItMTFlZC0wMDAwLWI2MGUxYWEzZmZkNycvPgo8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDoxOTViZGY4Ny0xYTUyLTExZWQtMDAwMC1iNjBlMWFhM2ZmZDcnIHhtbG5zOmRjPSdodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLycgZGM6Zm9ybWF0PSdhcHBsaWNhdGlvbi9wZGYnPjxkYzp0aXRsZT48cmRmOkFsdD48cmRmOmxpIHhtbDpsYW5nPSd4LWRlZmF1bHQnPnN1bXphZC5kdmk8L3JkZjpsaT48L3JkZjpBbHQ+PC9kYzp0aXRsZT48L3JkZjpEZXNjcmlwdGlvbj4KPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSd3Jz8+CmVuZHN0cmVhbQplbmRvYmoKMiAwIG9iago8PC9Qcm9kdWNlcihHUEwgR2hvc3RzY3JpcHQgOS4wNSkKL0NyZWF0aW9uRGF0ZShEOjIwMTIwODA5MTcxOTAyKzAyJzAwJykKL01vZERhdGUoRDoyMDEyMDgwOTE3MTkwMiswMicwMCcpCi9DcmVhdG9yKGR2aXBzXChrXCkgNS45OTEgQ29weXJpZ2h0IDIwMTEgUmFkaWNhbCBFeWUgU29mdHdhcmUpCi9UaXRsZShzdW16YWQuZHZpKT4+ZW5kb2JqCnhyZWYKMCA0NgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDUwODggMDAwMDAgbiAKMDAwMDAyODcyOCAwMDAwMCBuIAowMDAwMDA1MDI5IDAwMDAwIG4gCjAwMDAwMDQ4NjkgMDAwMDAgbiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDA0ODQ5IDAwMDAwIG4gCjAwMDAwMDUxNTMgMDAwMDAgbiAKMDAwMDAxMDA2MSAwMDAwMCBuIAowMDAwMDI1MjQ2IDAwMDAwIG4gCjAwMDAwMDg5OTggMDAwMDAgbiAKMDAwMDAyMTYyNSAwMDAwMCBuIAowMDAwMDA4Mjg1IDAwMDAwIG4gCjAwMDAwMjAxODAgMDAwMDAgbiAKMDAwMDAwNzg3MyAwMDAwMCBuIAowMDAwMDE4NzY5IDAwMDAwIG4gCjAwMDAwMDcwNTAgMDAwMDAgbiAKMDAwMDAxNDI4NSAwMDAwMCBuIAowMDAwMDA2MjA0IDAwMDAwIG4gCjAwMDAwMTIyMjMgMDAwMDAgbiAKMDAwMDAwNTU4OCAwMDAwMCBuIAowMDAwMDExMjc2IDAwMDAwIG4gCjAwMDAwMDUzMzEgMDAwMDAgbiAKMDAwMDAxMDYwOSAwMDAwMCBuIAowMDAwMDA1MTk0IDAwMDAwIG4gCjAwMDAwMDUyMjQgMDAwMDAgbiAKMDAwMDAxMDg0NyAwMDAwMCBuIAowMDAwMDExNTI5IDAwMDAwIG4gCjAwMDAwMTI0ODMgMDAwMDAgbiAKMDAwMDAxNDY4MSAwMDAwMCBuIAowMDAwMDE5MDMxIDAwMDAwIG4gCjAwMDAwMjA0NTkgMDAwMDAgbiAKMDAwMDAyMTk1OCAwMDAwMCBuIAowMDAwMDI1NTA2IDAwMDAwIG4gCjAwMDAwMDU0OTUgMDAwMDAgbiAKMDAwMDAwNTgzMSAwMDAwMCBuIAowMDAwMDA1OTE4IDAwMDAwIG4gCjAwMDAwMDY1OTcgMDAwMDAgbiAKMDAwMDAwNjY5NyAwMDAwMCBuIAowMDAwMDA3NzE2IDAwMDAwIG4gCjAwMDAwMDgxOTYgMDAwMDAgbiAKMDAwMDAwODYyMSAwMDAwMCBuIAowMDAwMDA5NjMzIDAwMDAwIG4gCjAwMDAwMDk3NTYgMDAwMDAgbiAKMDAwMDAxMDUxNiAwMDAwMCBuIAowMDAwMDI3MjczIDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNDYgL1Jvb3QgMSAwIFIgL0luZm8gMiAwIFIKL0lEIFs8MTdCOUE2RDk2MjZENkMzM0U3REVCM0I5RDAxODMyREM+PDE3QjlBNkQ5NjI2RDZDMzNFN0RFQjNCOUQwMTgzMkRDPl0KPj4Kc3RhcnR4cmVmCjI4OTMzCiUlRU9GCg==", + "round": 1, + "pub_date": "2015-01-31T17:18:19.768Z" } } -] \ No newline at end of file +] diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py index 922344a4f..619719167 100644 --- a/oioioi/statistics/tests_monitoring.py +++ b/oioioi/statistics/tests_monitoring.py @@ -6,7 +6,7 @@ from oioioi.base.tests import TestCase, fake_time from oioioi.contests.models import Contest, ProblemInstance -from oioioi.statistics.views import get_permissions_info, get_rounds_info +from oioioi.statistics.views import get_permissions_info, get_rounds_info, get_attachments_info class TestContestMonitoringViews(TestCase): @@ -22,7 +22,7 @@ class TestContestMonitoringViews(TestCase): 'test_permissions', 'test_messages', 'test_second_user_messages', - 'test_submission_list_with_syserr' + 'test_contest_attachment', ] def setUp(self): @@ -48,7 +48,6 @@ def test_permissions_info(self): f.close() def test_round_info(self): - contest = Contest.objects.get() with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): self.assertTrue(self.client.login(username='test_admin')) rounds_info = get_rounds_info(self.request) @@ -71,4 +70,16 @@ def test_questions_info(self): def test_attachments_info(self): - pass + self.assertTrue(self.client.login(username='test_admin')) + attachments_info = get_attachments_info(self.request) + f = open("xd.html", "w") + for ai in attachments_info: + if ai.description == 'published attachment': + f.write('xd1') + ai.pub_date_relative == 'Published' + if ai.description == 'unpublished attachment': + f.write(f'\n{ai.pub_date_relative} {ai.pub_date} {self.request.timestamp}\n') + f.write('xd2') + ai.pub_date_relative == '' + f.close() + From 36faf0e2635dde51e0fb3195eca83b59505d2fff Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Wed, 5 Jun 2024 16:16:27 +0200 Subject: [PATCH 29/34] Add attachments tests --- oioioi/statistics/tests_monitoring.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py index 619719167..b2c2e7b70 100644 --- a/oioioi/statistics/tests_monitoring.py +++ b/oioioi/statistics/tests_monitoring.py @@ -36,16 +36,13 @@ def test_permissions_info(self): url = reverse('monitoring', kwargs={'contest_id': contest.id}) self.assertTrue(self.client.login(username='test_admin')) - with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): + with fake_time(datetime(2014, 8, 5, tzinfo=timezone.utc)): response = self.client.get(url) self.assertRegex(str(response.content), r"Admin1") self.assertRegex(str(response.content), r"Basic Admin1") self.assertRegex(str(response.content), r"Observer1") self.assertRegex(str(response.content), r"Personal Data1") self.assertRegex(str(response.content), r"Participant0") - f = open("monitoring_page.html", "w") - f.write(str(response.content)) - f.close() def test_round_info(self): with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): @@ -72,14 +69,9 @@ def test_questions_info(self): def test_attachments_info(self): self.assertTrue(self.client.login(username='test_admin')) attachments_info = get_attachments_info(self.request) - f = open("xd.html", "w") for ai in attachments_info: if ai.description == 'published attachment': - f.write('xd1') - ai.pub_date_relative == 'Published' + self.assertTrue(ai.pub_date_relative == 'Published') if ai.description == 'unpublished attachment': - f.write(f'\n{ai.pub_date_relative} {ai.pub_date} {self.request.timestamp}\n') - f.write('xd2') - ai.pub_date_relative == '' - f.close() + self.assertTrue(ai.pub_date_relative != 'Published') From 29970c207fcd11d85517ffee387c2da14d463547 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 5 Jun 2024 16:37:05 +0200 Subject: [PATCH 30/34] Changed fixture --- .../contests/fixtures/test_submission_list_with_syserr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oioioi/contests/fixtures/test_submission_list_with_syserr.json b/oioioi/contests/fixtures/test_submission_list_with_syserr.json index 8dfee63ab..840863e34 100644 --- a/oioioi/contests/fixtures/test_submission_list_with_syserr.json +++ b/oioioi/contests/fixtures/test_submission_list_with_syserr.json @@ -91,10 +91,10 @@ "pk": 3, "model": "contests.scorereport", "fields": { - "status": "OK", + "status": "SE", "comment": null, "score": null, - "submission_report": null, + "submission_report": 3, "max_score": "int:0000000000000000000" } }, @@ -186,7 +186,7 @@ "pk": 1, "model": "programs.testreport", "fields": { - "status": "OK", + "status": "SE", "comment": "", "test_time_limit": 10000, "submission_report": 1, From 324c65e59af5dc4c333086a182427f69079088fd Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 5 Jun 2024 16:49:04 +0200 Subject: [PATCH 31/34] Moved monitoring tests from tests_monitoring.py to tests.py --- oioioi/statistics/tests.py | 68 +++++++++++++++++++++++ oioioi/statistics/tests_monitoring.py | 77 --------------------------- 2 files changed, 68 insertions(+), 77 deletions(-) delete mode 100644 oioioi/statistics/tests_monitoring.py diff --git a/oioioi/statistics/tests.py b/oioioi/statistics/tests.py index a592358ea..0b2403ae5 100644 --- a/oioioi/statistics/tests.py +++ b/oioioi/statistics/tests.py @@ -15,6 +15,7 @@ submissions_histogram_contest, test_scores, ) +from oioioi.statistics.views import get_attachments_info, get_rounds_info class TestStatisticsPlotFunctions(TestCase): @@ -214,3 +215,70 @@ def test_statistics_view(self): }, ) self.assertContains(response, url) + + +class TestContestMonitoringViews(TestCase): + fixtures = [ + 'test_users', + 'test_contest', + 'test_full_package', + 'test_problem_instance', + 'test_submission', + 'test_submission_another_user_for_statistics', + 'test_extra_rounds', + 'test_extra_problem', + 'test_permissions', + 'test_messages', + 'test_second_user_messages', + 'test_contest_attachment', + ] + + def setUp(self): + self.request = RequestFactory().request() + self.request.user = User.objects.get(username='test_user') + self.request.contest = Contest.objects.get() + self.request.timestamp = datetime(2014, 8, 5, tzinfo=timezone.utc) + + def test_permissions_info(self): + contest = Contest.objects.get() + url = reverse('monitoring', kwargs={'contest_id': contest.id}) + self.assertTrue(self.client.login(username='test_admin')) + + with fake_time(datetime(2014, 8, 5, tzinfo=timezone.utc)): + response = self.client.get(url) + self.assertRegex(str(response.content), r"Admin1") + self.assertRegex(str(response.content), r"Basic Admin1") + self.assertRegex(str(response.content), r"Observer1") + self.assertRegex(str(response.content), r"Personal Data1") + self.assertRegex(str(response.content), r"Participant0") + + def test_round_info(self): + with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): + self.assertTrue(self.client.login(username='test_admin')) + rounds_info = get_rounds_info(self.request) + for ri in rounds_info: + if ri['name'] == 'Past round': + self.assertTrue(ri['start_relative'] == 'Started') + self.assertTrue(ri['end_relative'] == 'Finished') + if ri['name'] == 'Future round': + self.assertTrue(ri['start_relative'] == '360 days, 20:27:58') + + def test_questions_info(self): + contest = Contest.objects.get() + url = reverse('monitoring', kwargs={'contest_id': contest.id}) + self.assertTrue(self.client.login(username='test_admin')) + with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): + response = self.client.get(url) + self.assertRegex(str(response.content), r"Unanswered questions2") + self.assertRegex(str(response.content), r"Oldest unanswered question2012-09-07 13:14:24") + self.assertRegex(str(response.content), r"Submissions with system errors2") + + def test_attachments_info(self): + self.assertTrue(self.client.login(username='test_admin')) + attachments_info = get_attachments_info(self.request) + for ai in attachments_info: + if ai.description == 'published attachment': + self.assertTrue(ai.pub_date_relative == 'Published') + if ai.description == 'unpublished attachment': + self.assertTrue(ai.pub_date_relative != 'Published') + diff --git a/oioioi/statistics/tests_monitoring.py b/oioioi/statistics/tests_monitoring.py deleted file mode 100644 index b2c2e7b70..000000000 --- a/oioioi/statistics/tests_monitoring.py +++ /dev/null @@ -1,77 +0,0 @@ -from datetime import datetime, timezone # pylint: disable=E0611 - -from django.contrib.auth.models import User -from django.test import RequestFactory -from django.urls import reverse - -from oioioi.base.tests import TestCase, fake_time -from oioioi.contests.models import Contest, ProblemInstance -from oioioi.statistics.views import get_permissions_info, get_rounds_info, get_attachments_info - - -class TestContestMonitoringViews(TestCase): - fixtures = [ - 'test_users', - 'test_contest', - 'test_full_package', - 'test_problem_instance', - 'test_submission', - 'test_submission_another_user_for_statistics', - 'test_extra_rounds', - 'test_extra_problem', - 'test_permissions', - 'test_messages', - 'test_second_user_messages', - 'test_contest_attachment', - ] - - def setUp(self): - self.request = RequestFactory().request() - self.request.user = User.objects.get(username='test_user') - self.request.contest = Contest.objects.get() - self.request.timestamp = datetime(2014, 8, 5, tzinfo=timezone.utc) - - def test_permissions_info(self): - contest = Contest.objects.get() - url = reverse('monitoring', kwargs={'contest_id': contest.id}) - self.assertTrue(self.client.login(username='test_admin')) - - with fake_time(datetime(2014, 8, 5, tzinfo=timezone.utc)): - response = self.client.get(url) - self.assertRegex(str(response.content), r"Admin1") - self.assertRegex(str(response.content), r"Basic Admin1") - self.assertRegex(str(response.content), r"Observer1") - self.assertRegex(str(response.content), r"Personal Data1") - self.assertRegex(str(response.content), r"Participant0") - - def test_round_info(self): - with fake_time(datetime(2015, 7, 5, tzinfo=timezone.utc)): - self.assertTrue(self.client.login(username='test_admin')) - rounds_info = get_rounds_info(self.request) - for ri in rounds_info: - if ri['name'] == 'Past round': - self.assertTrue(ri['start_relative'] == 'Started') - self.assertTrue(ri['end_relative'] == 'Finished') - if ri['name'] == 'Future round': - self.assertTrue(ri['start_relative'] == '360 days, 20:27:58') - - def test_questions_info(self): - contest = Contest.objects.get() - url = reverse('monitoring', kwargs={'contest_id': contest.id}) - self.assertTrue(self.client.login(username='test_admin')) - with fake_time(datetime(2015, 8, 5, tzinfo=timezone.utc)): - response = self.client.get(url) - self.assertRegex(str(response.content), r"Unanswered questions2") - self.assertRegex(str(response.content), r"Oldest unanswered question2012-09-07 13:14:24") - self.assertRegex(str(response.content), r"Submissions with system errors2") - - - def test_attachments_info(self): - self.assertTrue(self.client.login(username='test_admin')) - attachments_info = get_attachments_info(self.request) - for ai in attachments_info: - if ai.description == 'published attachment': - self.assertTrue(ai.pub_date_relative == 'Published') - if ai.description == 'unpublished attachment': - self.assertTrue(ai.pub_date_relative != 'Published') - From 3875af17255e93dcded29e0de09a759f40665c86 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 5 Jun 2024 17:01:12 +0200 Subject: [PATCH 32/34] Added fixture to monitoring tests --- oioioi/statistics/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oioioi/statistics/tests.py b/oioioi/statistics/tests.py index 0b2403ae5..ea29d62be 100644 --- a/oioioi/statistics/tests.py +++ b/oioioi/statistics/tests.py @@ -231,6 +231,7 @@ class TestContestMonitoringViews(TestCase): 'test_messages', 'test_second_user_messages', 'test_contest_attachment', + 'test_submission_list_with_syserr', ] def setUp(self): From 36b4f1a6b01710c50f54245bfe34c2331b709124 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 5 Jun 2024 17:32:38 +0200 Subject: [PATCH 33/34] Changed colspan --- .../templates/statistics/_problems_and_tests_info.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oioioi/statistics/templates/statistics/_problems_and_tests_info.html b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html index 2888321cf..cee9af433 100644 --- a/oioioi/statistics/templates/statistics/_problems_and_tests_info.html +++ b/oioioi/statistics/templates/statistics/_problems_and_tests_info.html @@ -22,7 +22,7 @@

{{ round }}

{{ problem_info.solved }}
+ {% endif %} -

Test Run Config

@@ -40,7 +40,7 @@

Test Run Config

+

Tests

From c259d1c5ee60701a8652e22c15e64837b3742bf5 Mon Sep 17 00:00:00 2001 From: Mateusz Jacniacki Date: Thu, 4 Jul 2024 05:25:49 +0200 Subject: [PATCH 34/34] Use humanize module --- oioioi/statistics/tests.py | 2 +- oioioi/statistics/views.py | 7 ++++--- setup.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/oioioi/statistics/tests.py b/oioioi/statistics/tests.py index ea29d62be..78efb2ac3 100644 --- a/oioioi/statistics/tests.py +++ b/oioioi/statistics/tests.py @@ -262,7 +262,7 @@ def test_round_info(self): self.assertTrue(ri['start_relative'] == 'Started') self.assertTrue(ri['end_relative'] == 'Finished') if ri['name'] == 'Future round': - self.assertTrue(ri['start_relative'] == '360 days, 20:27:58') + self.assertTrue(ri['start_relative'] == '11 months') def test_questions_info(self): contest = Contest.objects.get() diff --git a/oioioi/statistics/views.py b/oioioi/statistics/views.py index 8b39cd5d8..7d95e6d14 100644 --- a/oioioi/statistics/views.py +++ b/oioioi/statistics/views.py @@ -9,6 +9,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.db.models import F +from humanize import naturaldelta from oioioi.base.menu import menu_registry from oioioi.base.permissions import enforce_condition @@ -198,13 +199,13 @@ def get_rounds_info(request): for round_, rt in rounds_times(request, request.contest).items(): round_time_info = {'name': str(round_), 'start': rt.start or _("Not set")} if rt.start: - round_time_info['start_relative'] = str(rt.start - request.timestamp)[:-7] if rt.is_future( + round_time_info['start_relative'] = naturaldelta(rt.start - request.timestamp) if rt.is_future( request.timestamp) else _("Started") else: round_time_info['start_relative'] = _("Not set") round_time_info['end'] = rt.end or _("Not set") if rt.end: - round_time_info['end_relative'] = str(rt.end - request.timestamp)[:-7] if not rt.is_past( + round_time_info['end_relative'] = naturaldelta(rt.end - request.timestamp) if not rt.is_past( request.timestamp) else _("Finished") else: round_time_info['end_relative'] = _("Not set") @@ -217,7 +218,7 @@ def get_attachments_info(request): for attachment in attachments: pub_date_relative = None if attachment.pub_date: - pub_date_relative = str(attachment.pub_date - request.timestamp)[:-7] \ + pub_date_relative = naturaldelta(attachment.pub_date - request.timestamp) \ if attachment.pub_date > request.timestamp else _("Published") setattr(attachment, 'pub_date_relative', pub_date_relative) return attachments diff --git a/setup.py b/setup.py index b1697aec0..02d360688 100644 --- a/setup.py +++ b/setup.py @@ -63,6 +63,8 @@ # A library allowing to nest inlines in django admin. # Used in quizzes module for adding new quizzes. "django-nested-admin>=4.0,<4.1", + # Library for parsing dates and timedelta + "humanize<=4.9.0", # SIO2 dependencies: "filetracker>=2.1,<3.0", "django-simple-captcha>=0.5,<=0.5.18",