1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-06-21 14:38:24 +02:00

Add harmonization view

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello
2024-03-30 20:38:13 +01:00
parent 109b603b7a
commit 1e7bd209a1
6 changed files with 238 additions and 68 deletions

View File

@ -201,4 +201,6 @@ class TournamentAdmin(admin.ModelAdmin):
@admin.register(Tweak)
class TweakAdmin(admin.ModelAdmin):
list_display = ('participation', 'pool', 'diff',)
list_filter = ('pool__tournament', 'pool__round',)
search_fields = ('participation__team__name', 'participation__team__trigram',)
autocomplete_fields = ('participation', 'pool',)

View File

@ -104,9 +104,9 @@
{% endfor %}
</ul>
</div>
{% if not available_notes_1 or not available_notes_2 %}
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
<div class="card-footer text-center">
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
<div class="card-footer text-center">
{% if not available_notes_1 or not available_notes_2 %}
<div class="btn-group">
{% if not available_notes_1 %}
<a href="{% url 'participation:tournament_publish_notes' pk=tournament.pk round=1 %}" class="btn btn-info">
@ -121,8 +121,18 @@
</a>
{% endif %}
</div>
{% endif %}
<div class="btn-group">
<a href="{% url 'participation:tournament_harmonize' pk=tournament.pk round=1 %}" class="btn btn-secondary">
<i class="fas fa-ranking-star"></i>
{% trans "Harmonize" %} - {% trans "Day" %} 1
</a>
<a href="{% url 'participation:tournament_harmonize' pk=tournament.pk round=2 %}" class="btn btn-secondary">
<i class="fas fa-ranking-star"></i>
{% trans "Harmonize" %} - {% trans "Day" %} 2
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}

View File

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card bg-body shadow">
<div class="card-header text-center">
<h5>{% trans "Ranking" %}</h5>
</div>
<div class="card-body">
<table class="table table-striped text-center">
<thead>
<tr>
<th>{% trans "Rank" %}</th>
<th>{% trans "team"|capfirst %}</th>
<th>{% trans "Note" %}</th>
<th>{% trans "Including bonus / malus" %}</th>
<th>{% trans "Add bonus / malus" %}</th>
</tr>
</thead>
<tbody>
{% for participation, note in notes %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ participation.team }}</td>
<td>{{ note.note|floatformat }}</td>
<td>{% if note.tweak >= 0 %}+{% endif %}{{ note.tweak }}</td>
<td>
<div class="btn-group">
<a href="{% url 'participation:tournament_harmonize_note' pk=tournament.pk round=round action="add" trigram=participation.team.trigram %}"
class="btn btn-sm btn-success">
+1
</a>
<a href="{% url 'participation:tournament_harmonize_note' pk=tournament.pk round=round action="remove" trigram=participation.team.trigram %}"
class="btn btn-sm btn-danger">
-1
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer text-center">
<a class="btn btn-secondary" href="{% url 'participation:tournament_detail' pk=tournament.pk %}">
<i class="fas fa-arrow-left-long"></i>
{% trans "Back to tournament page" %}
</a>
</div>
</div>
{% endblock %}

View File

@ -11,7 +11,8 @@ from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView,
ScaleNotationSheetTemplateView, SolutionsDownloadView, SolutionUploadView, SynthesisUploadView, \
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
TournamentListView, TournamentPaymentsView, TournamentPublishNotesView, TournamentUpdateView
TournamentHarmonizeView, TournamentHarmonizeNoteView, TournamentListView, TournamentPaymentsView, \
TournamentPublishNotesView, TournamentUpdateView
app_name = "participation"
@ -47,6 +48,10 @@ urlpatterns = [
name="tournament_notation_sheets"),
path("tournament/<int:pk>/publish-notes/<int:round>/", TournamentPublishNotesView.as_view(),
name="tournament_publish_notes"),
path("tournament/<int:pk>/harmonize/<int:round>/", TournamentHarmonizeView.as_view(),
name="tournament_harmonize"),
path("tournament/<int:pk>/harmonize/<int:round>/<str:action>/<str:trigram>/", TournamentHarmonizeNoteView.as_view(),
name="tournament_harmonize_note"),
path("pools/create/", PoolCreateView.as_view(), name="pool_create"),
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),

View File

@ -9,6 +9,7 @@ from tempfile import mkdtemp
from typing import Any, Dict
from zipfile import ZipFile
import gspread
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
@ -41,7 +42,7 @@ from tfjm.views import AdminMixin, VolunteerMixin
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
PoolForm, PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
UploadNotesForm, ValidateParticipationForm
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
@ -710,6 +711,81 @@ class TournamentPublishNotesView(VolunteerMixin, SingleObjectMixin, RedirectView
return reverse_lazy("participation:tournament_detail", args=(kwargs['pk'],))
class TournamentHarmonizeView(VolunteerMixin, DetailView):
"""
Harmonize the notes of a tournament.
"""
model = Tournament
template_name = "participation/tournament_harmonize.html"
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
tournament = self.get_object()
reg = request.user.registration
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
return self.handle_no_permission()
if self.kwargs['round'] not in (1, 2):
raise Http404
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tournament = self.get_object()
context['round'] = self.kwargs['round']
context['pools'] = tournament.pools.filter(round=context["round"]).all()
context['title'] = _("Harmonize notes of {tournament} - Day {round}") \
.format(tournament=tournament, round=context["round"])
notes = dict()
for participation in self.object.participations.all():
note = sum(pool.average(participation) for pool in context['pools'])
tweak = sum(tweak.diff for tweak in participation.tweaks.filter(pool__in=context['pools']).all())
notes[participation] = {'note': note, 'tweak': tweak}
context["notes"] = sorted(notes.items(), key=lambda x: x[1]['note'], reverse=True)
return context
class TournamentHarmonizeNoteView(VolunteerMixin, DetailView):
model = Tournament
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
tournament = self.get_object()
reg = request.user.registration
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
return self.handle_no_permission()
if self.kwargs['round'] not in (1, 2) or self.kwargs['action'] not in ('add', 'remove') \
or self.kwargs['trigram'] not in [p.team.trigram for p in tournament.participations.all()]:
raise Http404
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
tournament = self.get_object()
participation = tournament.participations.get(team__trigram=kwargs['trigram'])
pool = tournament.pools.get(round=kwargs['round'], participations=participation)
tweak_qs = Tweak.objects.filter(participation=participation, pool=pool)
old_diff = tweak_qs.first().diff if tweak_qs.exists() else 0
new_diff = old_diff + (1 if kwargs['action'] == 'add' else -1)
if new_diff == 0:
tweak_qs.delete()
else:
tweak_qs.update_or_create(defaults={'diff': new_diff},
create_defaults={'diff': new_diff, 'participation': participation, 'pool': pool})
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(tournament.notes_sheet_id)
worksheet = spreadsheet.worksheet("Classement final")
column = 3 if kwargs['round'] == '1' else 5
row = worksheet.find(f"{participation.team.name} ({participation.team.trigram})", in_column=1).row
worksheet.update_cell(row, column, new_diff)
return redirect(reverse_lazy("participation:tournament_harmonize", args=(tournament.pk, kwargs['round'],)))
class SolutionUploadView(LoginRequiredMixin, FormView):
template_name = "participation/upload_solution.html"
form_class = SolutionForm