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:
@ -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',)
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
@ -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"),
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user