mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-08-24 00:57:55 +02:00
Compare commits
88 Commits
7984ce8e1d
...
7f8934a647
Author | SHA1 | Date | |
---|---|---|---|
|
7f8934a647 | ||
|
815206a0a5 | ||
|
8350960d5f
|
||
968162f34e
|
|||
e848855072
|
|||
50409931cf
|
|||
d18f76cf80
|
|||
5f2cd16071
|
|||
c686584e74
|
|||
3a650a1e89
|
|||
51beb47191
|
|||
e3f5541774
|
|||
14de6cf824
|
|||
3e46d06817
|
|||
0fd9222055
|
|||
b67308065a
|
|||
644afc6a0d
|
|||
1ef981571d
|
|||
30a8676555
|
|||
cdf279bb02
|
|||
7515c2bec6
|
|||
cce5e7c33c
|
|||
f9e85dd63e
|
|||
cb86fd43ac
|
|||
be0662420d
|
|||
da1d7a83fa
|
|||
d37354dc24
|
|||
d210b2a221
|
|||
e9958faace
|
|||
ab1f4c2eba
|
|||
1ba5cfa3f8
|
|||
e9cfae99da
|
|||
700df123b7
|
|||
582a634da7
|
|||
837800345b
|
|||
384fbfd0b2
|
|||
d8f2e56d45
|
|||
ba6a6338f5
|
|||
9a1006b341
|
|||
e21c3bb413
|
|||
afde1d35d5
|
|||
9e885153c2
|
|||
ffaa6e8116
|
|||
9797268736
|
|||
fb4edccc40
|
|||
f8297eebe1
|
|||
e41ad64b54
|
|||
13c4c834d4
|
|||
d6aa285bc5
|
|||
bbd8ad43cd
|
|||
ef8d124ade
|
|||
bb01e1b0b5
|
|||
f9af52ce6a
|
|||
ef2911ab07
|
|||
3bd6d2e647
|
|||
9d741d76f2
|
|||
de504a1706
|
|||
30a0e63eb9
|
|||
de76abab5f
|
|||
833249191c
|
|||
0a99f10899
|
|||
5101746d29
|
|||
aa69e6eadb
|
|||
7dd85d7402
|
|||
6b2ca1d2e1
|
|||
fbedb941be
|
|||
46e75c7ae8
|
|||
d26dee3bcf
|
|||
4084f7abb5
|
|||
d4c7b39f46
|
|||
0576f3e32b
|
|||
d093414ec7
|
|||
cba4a01117
|
|||
fde2fdba63
|
|||
aff1bbda0b
|
|||
4f9dfadb71
|
|||
1df1766753
|
|||
9359aa7606
|
|||
a45d57e51a
|
|||
35863c4bda
|
|||
13414ee0c5
|
|||
cdacbe2ea1
|
|||
69325bff9a
|
|||
049234caae
|
|||
f8d38738ea
|
|||
f7d52aa6da
|
|||
99a2134a57
|
|||
8fc99803c1
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ coverage
|
|||||||
secrets.py
|
secrets.py
|
||||||
*.log
|
*.log
|
||||||
media/
|
media/
|
||||||
|
output/
|
||||||
# Virtualenv
|
# Virtualenv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
|
@@ -2,14 +2,6 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- quality-assurance
|
- quality-assurance
|
||||||
|
|
||||||
py38:
|
|
||||||
stage: test
|
|
||||||
image: python:3.8-alpine
|
|
||||||
before_script:
|
|
||||||
- apk add --no-cache libmagic
|
|
||||||
- pip install tox --no-cache-dir
|
|
||||||
script: tox -e py38
|
|
||||||
|
|
||||||
py39:
|
py39:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.9-alpine
|
image: python:3.9-alpine
|
||||||
@@ -18,6 +10,22 @@ py39:
|
|||||||
- pip install tox --no-cache-dir
|
- pip install tox --no-cache-dir
|
||||||
script: tox -e py39
|
script: tox -e py39
|
||||||
|
|
||||||
|
py310:
|
||||||
|
stage: test
|
||||||
|
image: python:3.10-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache libmagic
|
||||||
|
- pip install tox --no-cache-dir
|
||||||
|
script: tox -e py310
|
||||||
|
|
||||||
|
py311:
|
||||||
|
stage: test
|
||||||
|
image: python:3.11-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache libmagic
|
||||||
|
- pip install tox --no-cache-dir
|
||||||
|
script: tox -e py311
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: python:3-alpine
|
image: python:3-alpine
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
FROM python:3.8-alpine
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
ENV DJANGO_ALLOW_ASYNC_UNSAFE 1
|
ENV DJANGO_ALLOW_ASYNC_UNSAFE 1
|
||||||
|
|
||||||
RUN apk add --no-cache gettext nginx gcc libc-dev libffi-dev libxml2-dev libxslt-dev postgresql-dev libmagic texlive
|
RUN apk add --no-cache gettext nginx gcc git libc-dev libffi-dev libxml2-dev libxslt-dev postgresql-dev libmagic texlive
|
||||||
|
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
@@ -23,10 +23,8 @@ RUN python manage.py collectstatic --noinput && \
|
|||||||
python manage.py compilemessages
|
python manage.py compilemessages
|
||||||
|
|
||||||
# Configure nginx
|
# Configure nginx
|
||||||
RUN mkdir /run/nginx
|
|
||||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
|
RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
|
||||||
RUN ln -sf /code/nginx_tfjm.conf /etc/nginx/conf.d/tfjm.conf
|
RUN ln -sf /code/nginx_tfjm.conf /etc/nginx/http.d/tfjm.conf && rm /etc/nginx/http.d/default.conf
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
RUN crontab /code/tfjm.cron
|
RUN crontab /code/tfjm.cron
|
||||||
|
|
||||||
|
@@ -16,6 +16,14 @@ if "logs" in settings.INSTALLED_APPS:
|
|||||||
from logs.api.urls import register_logs_urls
|
from logs.api.urls import register_logs_urls
|
||||||
register_logs_urls(router, "logs")
|
register_logs_urls(router, "logs")
|
||||||
|
|
||||||
|
if "participation" in settings.INSTALLED_APPS:
|
||||||
|
from participation.api.urls import register_participation_urls
|
||||||
|
register_participation_urls(router, "participation")
|
||||||
|
|
||||||
|
if "registration" in settings.INSTALLED_APPS:
|
||||||
|
from registration.api.urls import register_registration_urls
|
||||||
|
register_registration_urls(router, "registration")
|
||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
|
|
||||||
# Wire up our API using automatic URL routing.
|
# Wire up our API using automatic URL routing.
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Team)
|
@admin.register(Team)
|
||||||
@@ -36,6 +36,11 @@ class PassageAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
|
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Note)
|
||||||
|
class NoteAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ('jury',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Solution)
|
@admin.register(Solution)
|
||||||
class SolutionAdmin(admin.ModelAdmin):
|
class SolutionAdmin(admin.ModelAdmin):
|
||||||
list_display = ('participation',)
|
list_display = ('participation',)
|
||||||
@@ -52,3 +57,8 @@ class SynthesisAdmin(admin.ModelAdmin):
|
|||||||
class TournamentAdmin(admin.ModelAdmin):
|
class TournamentAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name',)
|
list_display = ('name',)
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Tweak)
|
||||||
|
class TweakAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('participation', 'pool', 'diff',)
|
||||||
|
2
apps/participation/api/__init__.py
Normal file
2
apps/participation/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
63
apps/participation/api/serializers.py
Normal file
63
apps/participation/api/serializers.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
|
|
||||||
|
|
||||||
|
class NoteSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Note
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipationSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Participation
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PassageSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Passage
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PoolSerializer(serializers.ModelSerializer):
|
||||||
|
passages = serializers.ListSerializer(child=PassageSerializer(), read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Pool
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SolutionSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Solution
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SynthesisSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Synthesis
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class TeamSerializer(serializers.ModelSerializer):
|
||||||
|
participation = ParticipationSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class TournamentSerializer(serializers.ModelSerializer):
|
||||||
|
participations = serializers.ListSerializer(child=ParticipationSerializer())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tournament
|
||||||
|
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||||
|
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||||
|
'description', 'organizers', 'final', 'participations',)
|
19
apps/participation/api/urls.py
Normal file
19
apps/participation/api/urls.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from .views import NoteViewSet, ParticipationViewSet, PassageViewSet, PoolViewSet, \
|
||||||
|
SolutionViewSet, SynthesisViewSet, TeamViewSet, TournamentViewSet
|
||||||
|
|
||||||
|
|
||||||
|
def register_participation_urls(router, path):
|
||||||
|
"""
|
||||||
|
Configure router for participation REST API.
|
||||||
|
"""
|
||||||
|
router.register(path + "/note", NoteViewSet)
|
||||||
|
router.register(path + "/participation", ParticipationViewSet)
|
||||||
|
router.register(path + "/passage", PassageViewSet)
|
||||||
|
router.register(path + "/pool", PoolViewSet)
|
||||||
|
router.register(path + "/solution", SolutionViewSet)
|
||||||
|
router.register(path + "/synthesis", SynthesisViewSet)
|
||||||
|
router.register(path + "/team", TeamViewSet)
|
||||||
|
router.register(path + "/tournament", TournamentViewSet)
|
69
apps/participation/api/views.py
Normal file
69
apps/participation/api/views.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from .serializers import NoteSerializer, ParticipationSerializer, PassageSerializer, PoolSerializer, \
|
||||||
|
SolutionSerializer, SynthesisSerializer, TeamSerializer, TournamentSerializer
|
||||||
|
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
|
|
||||||
|
|
||||||
|
class NoteViewSet(ModelViewSet):
|
||||||
|
queryset = Note.objects.all()
|
||||||
|
serializer_class = NoteSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['jury', 'passage', 'defender_writing', 'defender_oral', 'opponent_writing',
|
||||||
|
'opponent_oral', 'reporter_writing', 'reporter_oral', ]
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipationViewSet(ModelViewSet):
|
||||||
|
queryset = Participation.objects.all()
|
||||||
|
serializer_class = ParticipationSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['team', 'team__name', 'team__trigram', 'tournament', 'tournament__name', 'valid', 'final', ]
|
||||||
|
|
||||||
|
|
||||||
|
class PassageViewSet(ModelViewSet):
|
||||||
|
queryset = Passage.objects.all()
|
||||||
|
serializer_class = PassageSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['pool', 'solution_number', 'defender', 'opponent', 'reporter', 'pool_tournament', ]
|
||||||
|
|
||||||
|
|
||||||
|
class PoolViewSet(ModelViewSet):
|
||||||
|
queryset = Pool.objects.all()
|
||||||
|
serializer_class = PoolSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['tournament', 'tournament__name', 'round', 'participations', 'juries', 'bbb_url', ]
|
||||||
|
|
||||||
|
|
||||||
|
class SolutionViewSet(ModelViewSet):
|
||||||
|
queryset = Solution.objects.all()
|
||||||
|
serializer_class = SolutionSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
|
||||||
|
|
||||||
|
|
||||||
|
class SynthesisViewSet(ModelViewSet):
|
||||||
|
queryset = Synthesis.objects.all()
|
||||||
|
serializer_class = SynthesisSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['participation', 'number', 'passage', 'type', ]
|
||||||
|
|
||||||
|
|
||||||
|
class TeamViewSet(ModelViewSet):
|
||||||
|
queryset = Team.objects.all()
|
||||||
|
serializer_class = TeamSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['name', 'trigram', 'access_code', 'participation__valid', 'participation__tournament',
|
||||||
|
'participation__tournament__name', 'participation__valid', 'participation__final', ]
|
||||||
|
|
||||||
|
|
||||||
|
class TournamentViewSet(ModelViewSet):
|
||||||
|
queryset = Tournament.objects.all()
|
||||||
|
serializer_class = TournamentSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||||
|
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||||
|
'description', 'organizers', 'final', ]
|
@@ -1,14 +1,20 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from io import StringIO
|
||||||
import re
|
import re
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
from bootstrap_datepicker_plus import DatePickerInput, DateTimePickerInput
|
from bootstrap_datepicker_plus.widgets import DatePickerInput, DateTimePickerInput
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.utils import formats
|
from django.utils import formats
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from PyPDF3 import PdfFileReader
|
from PyPDF3 import PdfFileReader
|
||||||
|
from registration.models import VolunteerRegistration
|
||||||
|
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
|
|
||||||
@@ -19,7 +25,7 @@ class TeamForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
if "name" in self.cleaned_data:
|
if "name" in self.cleaned_data:
|
||||||
name = self.cleaned_data["name"].upper()
|
name = self.cleaned_data["name"]
|
||||||
if not self.instance.pk and Team.objects.filter(name=name).exists():
|
if not self.instance.pk and Team.objects.filter(name=name).exists():
|
||||||
raise ValidationError(_("This name is already used."))
|
raise ValidationError(_("This name is already used."))
|
||||||
return name
|
return name
|
||||||
@@ -67,7 +73,7 @@ class ParticipationForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Participation
|
model = Participation
|
||||||
fields = ('tournament',)
|
fields = ('tournament', 'final',)
|
||||||
|
|
||||||
|
|
||||||
class MotivationLetterForm(forms.ModelForm):
|
class MotivationLetterForm(forms.ModelForm):
|
||||||
@@ -136,6 +142,7 @@ class TournamentForm(forms.ModelForm):
|
|||||||
self.fields["syntheses_second_phase_limit"].widget = DateTimePickerInput(
|
self.fields["syntheses_second_phase_limit"].widget = DateTimePickerInput(
|
||||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||||
self.fields["organizers"].widget = forms.CheckboxSelectMultiple()
|
self.fields["organizers"].widget = forms.CheckboxSelectMultiple()
|
||||||
|
self.fields["organizers"].queryset = VolunteerRegistration.objects.all()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tournament
|
model = Tournament
|
||||||
@@ -154,7 +161,7 @@ class SolutionForm(forms.ModelForm):
|
|||||||
pages = len(pdf_reader.pages)
|
pages = len(pdf_reader.pages)
|
||||||
if pages > 30:
|
if pages > 30:
|
||||||
raise ValidationError(_("The PDF file must not have more than 30 pages."))
|
raise ValidationError(_("The PDF file must not have more than 30 pages."))
|
||||||
return self.cleaned_data["photo_authorization"]
|
return self.cleaned_data["file"]
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
"""
|
"""
|
||||||
@@ -169,7 +176,7 @@ class SolutionForm(forms.ModelForm):
|
|||||||
class PoolForm(forms.ModelForm):
|
class PoolForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Pool
|
model = Pool
|
||||||
fields = ('tournament', 'round', 'bbb_url', 'juries',)
|
fields = ('tournament', 'round', 'bbb_url', 'results_available', 'juries',)
|
||||||
widgets = {
|
widgets = {
|
||||||
"juries": forms.CheckboxSelectMultiple,
|
"juries": forms.CheckboxSelectMultiple,
|
||||||
}
|
}
|
||||||
@@ -188,6 +195,70 @@ class PoolTeamsForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UploadNotesForm(forms.Form):
|
||||||
|
file = forms.FileField(
|
||||||
|
label=_("CSV file:"),
|
||||||
|
validators=[FileExtensionValidator(allowed_extensions=["csv"])],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['file'].widget.attrs['accept'] = 'text/csv'
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
|
if 'file' in cleaned_data:
|
||||||
|
file = cleaned_data['file']
|
||||||
|
with file:
|
||||||
|
try:
|
||||||
|
csvfile = csv.reader(StringIO(file.read().decode()))
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
self.add_error('file', _("This file contains non-UTF-8 content. "
|
||||||
|
"Please send your sheet as a CSV file."))
|
||||||
|
|
||||||
|
self.process(csvfile, cleaned_data)
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def process(self, csvfile: Iterable[str], cleaned_data: dict):
|
||||||
|
parsed_notes = {}
|
||||||
|
for line in csvfile:
|
||||||
|
line = [s for s in line if s]
|
||||||
|
if len(line) < 19:
|
||||||
|
continue
|
||||||
|
name = line[0]
|
||||||
|
notes = line[1:19]
|
||||||
|
if not all(s.isnumeric() for s in notes):
|
||||||
|
continue
|
||||||
|
notes = list(map(int, notes))
|
||||||
|
if max(notes) < 3 or min(notes) < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
max_notes = 3 * [20, 16, 9, 10, 9, 10]
|
||||||
|
for n, max_n in zip(notes, max_notes):
|
||||||
|
if n > max_n:
|
||||||
|
self.add_error('file',
|
||||||
|
_("The following note is higher of the maximum expected value:")
|
||||||
|
+ str(n) + " > " + str(max_n))
|
||||||
|
|
||||||
|
first_name, last_name = tuple(name.split(' ', 1))
|
||||||
|
|
||||||
|
jury = User.objects.filter(first_name=first_name, last_name=last_name,
|
||||||
|
registration__volunteerregistration__isnull=False)
|
||||||
|
if jury.count() != 1:
|
||||||
|
self.add_error('file', _("The following user was not found:") + " " + name)
|
||||||
|
continue
|
||||||
|
jury = jury.get()
|
||||||
|
|
||||||
|
vr = jury.registration
|
||||||
|
parsed_notes[vr] = notes
|
||||||
|
|
||||||
|
cleaned_data['parsed_notes'] = parsed_notes
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class PassageForm(forms.ModelForm):
|
class PassageForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
@@ -202,7 +273,7 @@ class PassageForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Passage
|
model = Passage
|
||||||
fields = ('solution_number', 'place', 'defender', 'opponent', 'reporter',)
|
fields = ('solution_number', 'defender', 'opponent', 'reporter', 'defender_penalties',)
|
||||||
|
|
||||||
|
|
||||||
class SynthesisForm(forms.ModelForm):
|
class SynthesisForm(forms.ModelForm):
|
||||||
@@ -213,7 +284,7 @@ class SynthesisForm(forms.ModelForm):
|
|||||||
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
|
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
|
||||||
if file.content_type != "application/pdf":
|
if file.content_type != "application/pdf":
|
||||||
raise ValidationError(_("The uploaded file must be a PDF file."))
|
raise ValidationError(_("The uploaded file must be a PDF file."))
|
||||||
return self.cleaned_data["photo_authorization"]
|
return self.cleaned_data["file"]
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
"""
|
"""
|
||||||
@@ -222,7 +293,7 @@ class SynthesisForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Synthesis
|
model = Synthesis
|
||||||
fields = ('type', 'file',)
|
fields = ('file',)
|
||||||
|
|
||||||
|
|
||||||
class NoteForm(forms.ModelForm):
|
class NoteForm(forms.ModelForm):
|
||||||
|
@@ -5,19 +5,31 @@ import os
|
|||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
|
from django.db.models import Q
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def handle(self, *args, **options): # noqa: C901
|
def handle(self, *args, **options): # noqa: C901
|
||||||
|
# Get access token
|
||||||
|
response = requests.post('https://api.helloasso.com/oauth2/token', headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
}, data={
|
||||||
|
'client_id': os.getenv('HELLOASSO_CLIENT_ID', ''),
|
||||||
|
'client_secret': os.getenv('HELLOASSO_CLIENT_SECRET', ''),
|
||||||
|
'grant_type': 'client_credentials',
|
||||||
|
}).json()
|
||||||
|
|
||||||
|
token = response['access_token']
|
||||||
|
|
||||||
organization = "animath"
|
organization = "animath"
|
||||||
form_slug = "tfjmm-2018"
|
form_slug = "tfjm-2022-tournois-regionaux"
|
||||||
from_date = "2000-01-01"
|
from_date = "2000-01-01"
|
||||||
url = f"https://api.helloasso.com/v5/organizations/{organization}/forms/Event/{form_slug}/payments" \
|
url = f"https://api.helloasso.com/v5/organizations/{organization}/forms/Event/{form_slug}/payments" \
|
||||||
f"?from={from_date}&pageIndex=1&pageSize=10000&retrieveOfflineDonations=false"
|
f"?from={from_date}&pageIndex=1&pageSize=100&retrieveOfflineDonations=false"
|
||||||
headers = {
|
headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Authorization": f"Bearer {os.getenv('HELLO_ASSO_TOKEN', '')}",
|
"Authorization": f"Bearer {token}",
|
||||||
}
|
}
|
||||||
http_response = requests.get(url, headers=headers)
|
http_response = requests.get(url, headers=headers)
|
||||||
response = http_response.json()
|
response = http_response.json()
|
||||||
@@ -27,17 +39,38 @@ class Command(BaseCommand):
|
|||||||
self.stderr.write(f"Error while querying Hello Asso: {message}")
|
self.stderr.write(f"Error while querying Hello Asso: {message}")
|
||||||
return
|
return
|
||||||
|
|
||||||
for payment in response:
|
for payment in response["data"]:
|
||||||
if payment["state"] != "Authorized":
|
if payment["state"] != "Authorized":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
payer = payment["payer"]
|
payer = payment["payer"]
|
||||||
email = payer["email"]
|
email = payer["email"]
|
||||||
qs = User.objects.filter(email=email)
|
last_name = payer["lastName"]
|
||||||
|
first_name = payer["firstName"]
|
||||||
|
base_filter = Q(
|
||||||
|
registration__participantregistration__isnull=False,
|
||||||
|
registration__participantregistration__team__isnull=False,
|
||||||
|
registration__participantregistration__team__participation__valid=True,
|
||||||
|
)
|
||||||
|
qs = User.objects.filter(
|
||||||
|
base_filter,
|
||||||
|
email=email,
|
||||||
|
)
|
||||||
if not qs.exists():
|
if not qs.exists():
|
||||||
self.stderr.write(f"Warning: a payment was found by the email address {email}, "
|
qs = User.objects.filter(
|
||||||
|
base_filter,
|
||||||
|
last_name__icontains=last_name,
|
||||||
|
)
|
||||||
|
if qs.count() >= 2:
|
||||||
|
qs = qs.filter(first_name__icontains=first_name)
|
||||||
|
if not qs.exists():
|
||||||
|
self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), "
|
||||||
"but this user is unknown.")
|
"but this user is unknown.")
|
||||||
continue
|
continue
|
||||||
|
if qs.count() > 1:
|
||||||
|
self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), "
|
||||||
|
f"but there are {qs.count()} matching users.")
|
||||||
|
continue
|
||||||
user = qs.get()
|
user = qs.get()
|
||||||
if not user.registration.participates:
|
if not user.registration.participates:
|
||||||
self.stderr.write(f"Warning: a payment was found by the email address {email}, "
|
self.stderr.write(f"Warning: a payment was found by the email address {email}, "
|
||||||
|
86
apps/participation/management/commands/export_results.py
Normal file
86
apps/participation/management/commands/export_results.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Copyright (C) 2021 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.utils.formats import date_format
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
|
from .models import Tournament
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
activate('fr')
|
||||||
|
|
||||||
|
tournaments = Tournament.objects.order_by('-date_start', 'name')
|
||||||
|
for tournament in tournaments:
|
||||||
|
self.handle_tournament(tournament)
|
||||||
|
self.w("")
|
||||||
|
self.w("")
|
||||||
|
|
||||||
|
def w(self, msg):
|
||||||
|
self.stdout.write(msg)
|
||||||
|
|
||||||
|
def handle_tournament(self, tournament):
|
||||||
|
name = tournament.name
|
||||||
|
date_start = date_format(tournament.date_start, "DATE_FORMAT")
|
||||||
|
date_end = date_format(tournament.date_end, "DATE_FORMAT")
|
||||||
|
|
||||||
|
notes = dict()
|
||||||
|
for participation in tournament.participations.filter(valid=True).all():
|
||||||
|
note = sum(pool.average(participation)
|
||||||
|
for pool in tournament.pools.filter(participations=participation).all())
|
||||||
|
notes[participation] = note
|
||||||
|
notes = sorted(notes.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
self.w("<!-- wp:heading {\"level\":3} -->")
|
||||||
|
self.w(f"<h3><strong>{name}</strong></h3>")
|
||||||
|
self.w("<!-- /wp:heading -->")
|
||||||
|
self.w("")
|
||||||
|
self.w("<!-- wp:paragraph -->")
|
||||||
|
if tournament.final:
|
||||||
|
self.w(f"<p>La finale a eu lieu le weekend du {date_start} au {date_end} et a été remporté par l'équipe "
|
||||||
|
f"<em>{notes[0][0].team.name}</em> suivie de l'équipe <em>{notes[1][0].team.name}</em>. "
|
||||||
|
f"Les deux premières équipes sont sélectionnées pour représenter la France lors de l'ITYM.</p>")
|
||||||
|
else:
|
||||||
|
self.w(f"<p>Le tournoi de {name} a eu lieu le weekend du {date_start} au {date_end} et a été remporté par "
|
||||||
|
f"l'équipe <em>{notes[0][0].team.name}</em>.</p>")
|
||||||
|
self.w("<!-- /wp:paragraph -->")
|
||||||
|
self.w("")
|
||||||
|
self.w("")
|
||||||
|
self.w("<!-- wp:table -->")
|
||||||
|
self.w("<figure class=\"wp-block-table\">")
|
||||||
|
self.w("<table>")
|
||||||
|
self.w("<thead>")
|
||||||
|
self.w("<tr>")
|
||||||
|
self.w("\t<th>Équipe</th>")
|
||||||
|
self.w("\t<th>Score Tour 1</th>")
|
||||||
|
self.w("\t<th>Score Tour 2</th>")
|
||||||
|
self.w("\t<th>Total</th>")
|
||||||
|
self.w("\t<th class=\"has-text-align-center\">Prix</th>")
|
||||||
|
self.w("</tr>")
|
||||||
|
self.w("</thead>")
|
||||||
|
self.w("<tbody>")
|
||||||
|
for i, (participation, note) in enumerate(notes):
|
||||||
|
self.w("<tr>")
|
||||||
|
if i < (2 if len(notes) >= 7 else 1):
|
||||||
|
self.w(f"\t<th>{participation.team.name} ({participation.team.trigram})</td>")
|
||||||
|
else:
|
||||||
|
self.w(f"\t<td>{participation.team.name} ({participation.team.trigram})</td>")
|
||||||
|
for pool in tournament.pools.filter(participations=participation).all():
|
||||||
|
pool_note = pool.average(participation)
|
||||||
|
self.w(f"\t<td>{pool_note:.01f}</td>")
|
||||||
|
self.w(f"\t<td>{note:.01f}</td>")
|
||||||
|
if i == 0:
|
||||||
|
self.w("\t<td class=\"has-text-align-center\">1<sup>er</sup> prix</td>")
|
||||||
|
elif i < (5 if tournament.final else 3):
|
||||||
|
self.w(f"\t<td class=\"has-text-align-center\">{i + 1}<sup>ème</sup> prix</td>")
|
||||||
|
elif i < 2 * len(notes) / 3:
|
||||||
|
self.w("\t<td class=\"has-text-align-center\">Mention très honorable</td>")
|
||||||
|
else:
|
||||||
|
self.w("\t<td class=\"has-text-align-center\">Mention honorable</td>")
|
||||||
|
self.w("</tr>")
|
||||||
|
self.w("</tbody>")
|
||||||
|
self.w("</table>")
|
||||||
|
self.w("</figure>")
|
||||||
|
self.w("<!-- /wp:table -->")
|
82
apps/participation/management/commands/export_solutions.py
Normal file
82
apps/participation/management/commands/export_solutions.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Copyright (C) 2021 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
|
from .models import Solution, Tournament
|
||||||
|
|
||||||
|
|
||||||
|
PROBLEMS = [
|
||||||
|
"Pliage de polygones",
|
||||||
|
"Mélodie des hirondelles",
|
||||||
|
"Professeur confiné",
|
||||||
|
"Nain sans mémoire",
|
||||||
|
"Bricolage microscopique",
|
||||||
|
"Villes jumelées",
|
||||||
|
"Promenade de chiens",
|
||||||
|
"Persée et la Gorgone",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
activate('fr')
|
||||||
|
|
||||||
|
base_dir = Path(__file__).parent.parent.parent.parent.parent
|
||||||
|
base_dir /= "output"
|
||||||
|
if not base_dir.is_dir():
|
||||||
|
base_dir.mkdir()
|
||||||
|
base_dir /= "Par équipe"
|
||||||
|
if not base_dir.is_dir():
|
||||||
|
base_dir.mkdir()
|
||||||
|
|
||||||
|
tournaments = Tournament.objects.all()
|
||||||
|
for tournament in tournaments:
|
||||||
|
self.handle_tournament(tournament, base_dir)
|
||||||
|
|
||||||
|
base_dir = base_dir.parent / "Par problème"
|
||||||
|
if not base_dir.is_dir():
|
||||||
|
base_dir.mkdir()
|
||||||
|
|
||||||
|
for problem_id, problem_name in enumerate(PROBLEMS):
|
||||||
|
dir_name = f"Problème n°{problem_id + 1} : {problem_name}"
|
||||||
|
problem_dir = base_dir / dir_name
|
||||||
|
if not problem_dir.is_dir():
|
||||||
|
problem_dir.mkdir()
|
||||||
|
self.handle_problem(problem_id + 1, problem_dir)
|
||||||
|
|
||||||
|
def handle_tournament(self, tournament, base_dir):
|
||||||
|
name = tournament.name
|
||||||
|
tournament_dir = base_dir / name
|
||||||
|
if not tournament_dir.is_dir():
|
||||||
|
tournament_dir.mkdir()
|
||||||
|
|
||||||
|
for participation in tournament.participations.filter(valid=True).all():
|
||||||
|
self.handle_participation(participation, tournament_dir)
|
||||||
|
|
||||||
|
def handle_participation(self, participation, tournament_dir):
|
||||||
|
name = participation.team.name
|
||||||
|
trigram = participation.team.trigram
|
||||||
|
team_dir = tournament_dir / f"{name} ({trigram})"
|
||||||
|
if not team_dir.is_dir():
|
||||||
|
team_dir.mkdir()
|
||||||
|
|
||||||
|
for solution in participation.solutions.all():
|
||||||
|
filename = f"{solution}.pdf"
|
||||||
|
with solution.file as file_input:
|
||||||
|
with open(team_dir / filename, 'wb') as file_output:
|
||||||
|
file_output.write(file_input.read())
|
||||||
|
|
||||||
|
def handle_problem(self, problem_id, directory):
|
||||||
|
solutions = Solution.objects.filter(problem=problem_id).all()
|
||||||
|
|
||||||
|
for solution in solutions:
|
||||||
|
team = solution.participation.team
|
||||||
|
tournament_name = team.participation.tournament.name
|
||||||
|
output_file = directory / f'{solution}.pdf'
|
||||||
|
if output_file.is_file():
|
||||||
|
output_file.unlink()
|
||||||
|
output_file.symlink_to(f'../../Par équipe/{tournament_name}/{team.name} ({team.trigram})/{solution}.pdf')
|
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.translation import activate
|
from django.utils.translation import activate
|
||||||
@@ -16,32 +16,33 @@ class Command(BaseCommand):
|
|||||||
def handle(self, *args, **options): # noqa: C901
|
def handle(self, *args, **options): # noqa: C901
|
||||||
activate("fr")
|
activate("fr")
|
||||||
|
|
||||||
Matrix.set_display_name("Bot du TFJM²")
|
async def main():
|
||||||
|
await Matrix.set_display_name("Bot du TFJM²")
|
||||||
|
|
||||||
if not os.getenv("SYNAPSE_PASSWORD"):
|
if not os.getenv("SYNAPSE_PASSWORD"):
|
||||||
avatar_uri = "plop"
|
avatar_uri = "plop"
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
if not os.path.isfile(".matrix_avatar"):
|
if not os.path.isfile(".matrix_avatar"):
|
||||||
avatar_uri = Matrix.get_avatar()
|
avatar_uri = await Matrix.get_avatar()
|
||||||
if isinstance(avatar_uri, str):
|
if isinstance(avatar_uri, str):
|
||||||
with open(".matrix_avatar", "w") as f:
|
with open(".matrix_avatar", "w") as f:
|
||||||
f.write(avatar_uri)
|
f.write(avatar_uri)
|
||||||
else:
|
else:
|
||||||
stat_file = os.stat("tfjm/static/logo.png")
|
stat_file = os.stat("tfjm/static/logo.png")
|
||||||
with open("tfjm/static/logo.png", "rb") as f:
|
with open("tfjm/static/logo.png", "rb") as f:
|
||||||
resp = Matrix.upload(f, filename="logo.png", content_type="image/png",
|
resp = (await Matrix.upload(f, filename="logo.png", content_type="image/png",
|
||||||
filesize=stat_file.st_size)[0][0]
|
filesize=stat_file.st_size))[0][0]
|
||||||
avatar_uri = resp.content_uri
|
avatar_uri = resp.content_uri
|
||||||
with open(".matrix_avatar", "w") as f:
|
with open(".matrix_avatar", "w") as f:
|
||||||
f.write(avatar_uri)
|
f.write(avatar_uri)
|
||||||
Matrix.set_avatar(avatar_uri)
|
await Matrix.set_avatar(avatar_uri)
|
||||||
|
|
||||||
with open(".matrix_avatar", "r") as f:
|
with open(".matrix_avatar", "r") as f:
|
||||||
avatar_uri = f.read().rstrip(" \t\r\n")
|
avatar_uri = f.read().rstrip(" \t\r\n")
|
||||||
|
|
||||||
# Create basic channels
|
# Create basic channels
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#aide-jurys-orgas:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#aide-jurys-orgas:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="aide-jurys-orgas",
|
alias="aide-jurys-orgas",
|
||||||
name="Aide jurys & orgas",
|
name="Aide jurys & orgas",
|
||||||
@@ -50,8 +51,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#annonces:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#annonces:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="annonces",
|
alias="annonces",
|
||||||
name="Annonces",
|
name="Annonces",
|
||||||
@@ -60,18 +61,18 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.public_chat,
|
preset=RoomPreset.public_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#bienvenue:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#bienvenue:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="bienvenue",
|
alias="bienvenue",
|
||||||
name="Bienvenue",
|
name="Bienvenue",
|
||||||
topic="Bienvenue au TFJM² 2021 !",
|
topic="Bienvenue au TFJM² 2022 !",
|
||||||
federate=False,
|
federate=False,
|
||||||
preset=RoomPreset.public_chat,
|
preset=RoomPreset.public_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#bot:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#bot:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="bot",
|
alias="bot",
|
||||||
name="Bot",
|
name="Bot",
|
||||||
@@ -80,8 +81,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.public_chat,
|
preset=RoomPreset.public_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#cno:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#cno:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="cno",
|
alias="cno",
|
||||||
name="CNO",
|
name="CNO",
|
||||||
@@ -90,8 +91,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#dev-bot:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#dev-bot:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="dev-bot",
|
alias="dev-bot",
|
||||||
name="Bot - développement",
|
name="Bot - développement",
|
||||||
@@ -100,8 +101,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#faq:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#faq:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="faq",
|
alias="faq",
|
||||||
name="FAQ",
|
name="FAQ",
|
||||||
@@ -110,8 +111,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.public_chat,
|
preset=RoomPreset.public_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#flood:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#flood:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="flood",
|
alias="flood",
|
||||||
name="Flood",
|
name="Flood",
|
||||||
@@ -120,8 +121,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.public_chat,
|
preset=RoomPreset.public_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)("#je-cherche-une-equipe:tfjm.org"):
|
if not await Matrix.resolve_room_alias("#je-cherche-une-equipe:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias="je-cherche-une-equipe",
|
alias="je-cherche-une-equipe",
|
||||||
name="Je cherche une équipe",
|
name="Je cherche une équipe",
|
||||||
@@ -131,52 +132,61 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Setup avatars
|
# Setup avatars
|
||||||
Matrix.set_room_avatar("#aide-jurys-orgas:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#aide-jurys-orgas:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#annonces:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#annonces:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#bienvenue:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#bienvenue:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#bot:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#bot:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#cno:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#cno:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#dev-bot:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#dev-bot:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#faq:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#faq:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#flood:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#flood:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar("#je-cherche-une-equipe:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar("#je-cherche-une-equipe:tfjm.org", avatar_uri)
|
||||||
|
|
||||||
# Read-only channels
|
# Read-only channels
|
||||||
Matrix.set_room_power_level_event("#annonces:tfjm.org", "events_default", 50)
|
await Matrix.set_room_power_level_event("#annonces:tfjm.org", "events_default", 50)
|
||||||
Matrix.set_room_power_level_event("#bienvenue:tfjm.org", "events_default", 50)
|
await Matrix.set_room_power_level_event("#bienvenue:tfjm.org", "events_default", 50)
|
||||||
|
|
||||||
# Invite everyone to public channels
|
# Invite everyone to public channels
|
||||||
for r in Registration.objects.all():
|
for r in Registration.objects.all():
|
||||||
Matrix.invite("#annonces:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
await Matrix.invite("#annonces:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#bienvenue:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
await Matrix.invite("#bienvenue:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#bot:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
await Matrix.invite("#bot:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#faq:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
await Matrix.invite("#faq:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#flood:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
await Matrix.invite("#flood:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#je-cherche-une-equipe:tfjm.org",
|
await Matrix.invite("#je-cherche-une-equipe:tfjm.org",
|
||||||
f"@{r.matrix_username}:tfjm.org")
|
f"@{r.matrix_username}:tfjm.org")
|
||||||
self.stdout.write(f"Invite {r} in most common channels...")
|
self.stdout.write(f"Invite {r} in most common channels...")
|
||||||
|
|
||||||
# Volunteers have access to the help channel
|
# Volunteers have access to the help channel
|
||||||
for volunteer in VolunteerRegistration.objects.all():
|
for volunteer in VolunteerRegistration.objects.all():
|
||||||
Matrix.invite("#aide-jurys-orgas:tfjm.org", f"@{volunteer.matrix_username}:tfjm.org")
|
await Matrix.invite("#aide-jurys-orgas:tfjm.org", f"@{volunteer.matrix_username}:tfjm.org")
|
||||||
self.stdout.write(f"Invite {volunteer} in #aide-jury-orgas...")
|
self.stdout.write(f"Invite {volunteer} in #aide-jury-orgas...")
|
||||||
|
|
||||||
# Admins are admins
|
# Admins are admins
|
||||||
for admin in AdminRegistration.objects.all():
|
for admin in AdminRegistration.objects.all():
|
||||||
self.stdout.write(f"Invite {admin} in #cno and #dev-bot...")
|
self.stdout.write(f"Invite {admin} in #cno and #dev-bot...")
|
||||||
Matrix.invite("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
self.stdout.write(f"Give admin permissions for {admin}...")
|
self.stdout.write(f"Give admin permissions for {admin}...")
|
||||||
Matrix.set_room_power_level("#aide-jurys-orgas:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level("#aide-jurys-orgas:tfjm.org",
|
||||||
Matrix.set_room_power_level("#annonces:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level("#bienvenue:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level("#annonces:tfjm.org",
|
||||||
Matrix.set_room_power_level("#bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level("#bienvenue:tfjm.org",
|
||||||
Matrix.set_room_power_level("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level("#faq:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level("#bot:tfjm.org",
|
||||||
Matrix.set_room_power_level("#flood:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level("#je-cherche-une-equipe:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level("#cno:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level("#dev-bot:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level("#faq:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level("#flood:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level("#je-cherche-une-equipe:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
|
||||||
# Create tournament-specific channels
|
# Create tournament-specific channels
|
||||||
for tournament in Tournament.objects.all():
|
for tournament in Tournament.objects.all():
|
||||||
@@ -185,8 +195,8 @@ class Command(BaseCommand):
|
|||||||
name = tournament.name
|
name = tournament.name
|
||||||
slug = name.lower().replace(" ", "-")
|
slug = name.lower().replace(" ", "-")
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#annonces-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#annonces-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"annonces-{slug}",
|
alias=f"annonces-{slug}",
|
||||||
name=f"{name} - Annonces",
|
name=f"{name} - Annonces",
|
||||||
@@ -195,8 +205,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#general-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#general-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"general-{slug}",
|
alias=f"general-{slug}",
|
||||||
name=f"{name} - Général",
|
name=f"{name} - Général",
|
||||||
@@ -205,8 +215,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#flood-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#flood-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"flood-{slug}",
|
alias=f"flood-{slug}",
|
||||||
name=f"{name} - Flood",
|
name=f"{name} - Flood",
|
||||||
@@ -215,8 +225,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#jury-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#jury-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"jury-{slug}",
|
alias=f"jury-{slug}",
|
||||||
name=f"{name} - Jury",
|
name=f"{name} - Jury",
|
||||||
@@ -225,8 +235,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#orga-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#orga-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"orga-{slug}",
|
alias=f"orga-{slug}",
|
||||||
name=f"{name} - Organisateurs",
|
name=f"{name} - Organisateurs",
|
||||||
@@ -235,8 +245,8 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#tirage-au-sort-{slug}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#tirage-au-sort-{slug}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"tirage-au-sort-{slug}",
|
alias=f"tirage-au-sort-{slug}",
|
||||||
name=f"{name} - Tirage au sort",
|
name=f"{name} - Tirage au sort",
|
||||||
@@ -246,146 +256,181 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Setup avatars
|
# Setup avatars
|
||||||
Matrix.set_room_avatar(f"#annonces-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#annonces-{slug}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#flood-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#flood-{slug}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#general-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#general-{slug}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#jury-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#jury-{slug}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#orga-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#orga-{slug}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#tirage-au-sort-{slug}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#tirage-au-sort-{slug}:tfjm.org", avatar_uri)
|
||||||
|
|
||||||
# Invite admins and give permissions
|
# Invite admins and give permissions
|
||||||
for admin in AdminRegistration.objects.all():
|
for admin in AdminRegistration.objects.all():
|
||||||
self.stdout.write(f"Invite {admin} in all channels of the tournament {name}...")
|
self.stdout.write(f"Invite {admin} in all channels of the tournament {name}...")
|
||||||
Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#general-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
self.stdout.write(f"Give permissions to {admin} in all channels of the tournament {name}...")
|
self.stdout.write(f"Give permissions to {admin} in all channels of the tournament {name}...")
|
||||||
Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level(f"#general-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
await Matrix.set_room_power_level(f"#general-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
await Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
|
||||||
# Invite organizers and give permissions
|
# Invite organizers and give permissions
|
||||||
for orga in tournament.organizers.all():
|
for orga in tournament.organizers.all():
|
||||||
self.stdout.write(f"Invite organizer {orga} in all channels of the tournament {name}...")
|
self.stdout.write(f"Invite organizer {orga} in all channels of the tournament {name}...")
|
||||||
Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#general-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
if not orga.is_admin:
|
if not orga.is_admin:
|
||||||
Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org", 50)
|
await Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org", 50)
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
Matrix.set_room_power_level(f"#general-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org", 50)
|
await Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org", 50)
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org", 50)
|
await Matrix.set_room_power_level(f"#general-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org",
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
|
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||||
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
|
await Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org",
|
||||||
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
|
await Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
|
|
||||||
# Invite participants
|
# Invite participants
|
||||||
for participation in tournament.participations.filter(valid=True).all():
|
for participation in tournament.participations.filter(valid=True).all():
|
||||||
for participant in participation.team.participants.all():
|
for participant in participation.team.participants.all():
|
||||||
self.stdout.write(f"Invite {participant} in public channels of the tournament {name}...")
|
self.stdout.write(f"Invite {participant} in public channels of the tournament {name}...")
|
||||||
Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#annonces-{slug}:tfjm.org",
|
||||||
Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#general-{slug}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#flood-{slug}:tfjm.org",
|
||||||
Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.invite(f"#general-{slug}:tfjm.org",
|
||||||
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||||
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
# Create pool-specific channels
|
# Create pool-specific channels
|
||||||
for pool in tournament.pools.all():
|
for pool in tournament.pools.all():
|
||||||
self.stdout.write(f"Managing {pool}...")
|
self.stdout.write(f"Managing {pool}...")
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#poule-{slug}-{pool.id}:tfjm.org"):
|
five = pool.participations.count() >= 5
|
||||||
Matrix.create_room(
|
for i in range(2 if five else 1):
|
||||||
|
# Fix for five teams-pools
|
||||||
|
suffix = f"-{chr(ord('A') + i)}" if five else ""
|
||||||
|
if not await Matrix.resolve_room_alias(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org"):
|
||||||
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"poule-{slug}-{pool.id}",
|
alias=f"poule-{slug}-{pool.id}{suffix}",
|
||||||
name=f"{name} - Jour {pool.round} - Poule "
|
name=f"{name} - Jour {pool.round} - Poule " +
|
||||||
f"{', '.join(participation.team.trigram for participation in pool.participations.all())}",
|
', '.join(participation.team.trigram
|
||||||
topic=f"Discussion avec les équipes - {pool}",
|
for participation in pool.participations.all()) + suffix,
|
||||||
|
topic=f"Discussion avec les équipes - {pool}{suffix}",
|
||||||
federate=False,
|
federate=False,
|
||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#poule-{slug}-{pool.id}-jurys:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"poule-{slug}-{pool.id}-jurys",
|
alias=f"poule-{slug}-{pool.id}{suffix}-jurys",
|
||||||
name=f"{name} - Jour {pool.round} - Jurys poule "
|
name=f"{name} - Jour {pool.round}{suffix} - Jurys poule " +
|
||||||
f"{', '.join(participation.team.trigram for participation in pool.participations.all())}",
|
', '.join(participation.team.trigram
|
||||||
topic=f"Discussion avec les jurys - {pool}",
|
for participation in pool.participations.all()) + suffix,
|
||||||
|
topic=f"Discussion avec les jurys - {pool}{suffix}",
|
||||||
federate=False,
|
federate=False,
|
||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
|
|
||||||
Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org", avatar_uri)
|
||||||
Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}-jurys:tfjm.org", avatar_uri)
|
await Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org", avatar_uri)
|
||||||
|
|
||||||
url_params = urlencode(dict(url=pool.bbb_url,
|
bbb_url = pool.bbb_url.strip()
|
||||||
|
if five and ';' in bbb_url:
|
||||||
|
bbb_url = bbb_url.split(";")[i].strip()
|
||||||
|
url_params = urlencode(dict(url=bbb_url,
|
||||||
isAudioConf='false', displayName='$matrix_display_name',
|
isAudioConf='false', displayName='$matrix_display_name',
|
||||||
avatarUrl='$matrix_avatar_url', userId='$matrix_user_id')) \
|
avatarUrl='$matrix_avatar_url', userId='$matrix_user_id')) \
|
||||||
.replace("%24", "$")
|
.replace("%24", "$")
|
||||||
Matrix.add_integration(f"#poule-{slug}-{pool.id}:tfjm.org",
|
await Matrix.add_integration(
|
||||||
|
f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
f"https://scalar.vector.im/api/widgets/bigbluebutton.html?{url_params}",
|
f"https://scalar.vector.im/api/widgets/bigbluebutton.html?{url_params}",
|
||||||
f"bbb-{slug}-{pool.id}", "bigbluebutton", "BigBlueButton", str(pool))
|
f"bbb-{slug}-{pool.id}{suffix}", "bigbluebutton", "BigBlueButton", str(pool))
|
||||||
Matrix.add_integration(f"#poule-{slug}-{pool.id}:tfjm.org",
|
await Matrix.add_integration(
|
||||||
|
f"#poule-{slug}-{pool.id}:tfjm.org",
|
||||||
f"https://board.tfjm.org/boards/{slug}-{pool.id}", f"board-{slug}-{pool.id}",
|
f"https://board.tfjm.org/boards/{slug}-{pool.id}", f"board-{slug}-{pool.id}",
|
||||||
"customwidget", "Tableau", str(pool))
|
"customwidget", "Tableau", str(pool))
|
||||||
|
|
||||||
# Invite admins and give permissions
|
# Invite admins and give permissions
|
||||||
for admin in AdminRegistration.objects.all():
|
for admin in AdminRegistration.objects.all():
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}-jurys:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
f"@{admin.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
|
f"@{admin.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}:tfjm.org",
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}-jurys:tfjm.org",
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||||
|
|
||||||
# Invite organizers and give permissions
|
# Invite organizers and give permissions
|
||||||
for orga in VolunteerRegistration.objects.all():
|
for orga in tournament.organizers.all():
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}-jurys:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
f"@{orga.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
|
f"@{orga.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
if not orga.is_admin:
|
if not orga.is_admin:
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}:tfjm.org",
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}-jurys:tfjm.org",
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||||
|
|
||||||
# Invite the jury, give good permissions
|
# Invite the jury, give good permissions
|
||||||
for jury in pool.juries.all():
|
for jury in pool.juries.all():
|
||||||
Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#general-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}-jurys:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
f"@{jury.matrix_username}:tfjm.org")
|
||||||
Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
|
f"@{jury.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||||
|
f"@{jury.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
if not jury.is_admin:
|
if not jury.is_admin:
|
||||||
Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org", 50)
|
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}:tfjm.org",
|
|
||||||
f"@{jury.matrix_username}:tfjm.org", 50)
|
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||||
Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}-jurys:tfjm.org",
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
|
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||||
|
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||||
f"@{jury.matrix_username}:tfjm.org", 50)
|
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||||
|
|
||||||
# Invite participants to the right pool
|
# Invite participants to the right pool
|
||||||
for participation in pool.participations.all():
|
for participation in pool.participations.all():
|
||||||
for participant in participation.team.participants.all():
|
for participant in participation.team.participants.all():
|
||||||
Matrix.invite(f"#poule-{slug}-{pool.id}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||||
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
|
|
||||||
# Create private channels for teams
|
# Create private channels for teams
|
||||||
for team in Team.objects.all():
|
for team in Team.objects.all():
|
||||||
self.stdout.write(f"Create private channel for {team}...")
|
self.stdout.write(f"Create private channel for {team}...")
|
||||||
if not async_to_sync(Matrix.resolve_room_alias)(f"#equipe-{team.trigram.lower()}:tfjm.org"):
|
if not await Matrix.resolve_room_alias(f"#equipe-{team.trigram.lower()}:tfjm.org"):
|
||||||
Matrix.create_room(
|
await Matrix.create_room(
|
||||||
visibility=RoomVisibility.public,
|
visibility=RoomVisibility.public,
|
||||||
alias=f"equipe-{team.trigram.lower()}",
|
alias=f"equipe-{team.trigram.lower()}",
|
||||||
name=f"Équipe {team.trigram}",
|
name=f"Équipe {team.trigram}",
|
||||||
@@ -394,6 +439,39 @@ class Command(BaseCommand):
|
|||||||
preset=RoomPreset.private_chat,
|
preset=RoomPreset.private_chat,
|
||||||
)
|
)
|
||||||
for participant in team.participants.all():
|
for participant in team.participants.all():
|
||||||
Matrix.invite(f"#equipe-{team.trigram.lower}:tfjm.org", f"@{participant.matrix_username}:tfjm.org")
|
await Matrix.invite(f"#equipe-{team.trigram.lower}:tfjm.org",
|
||||||
Matrix.set_room_power_level(f"#equipe-{team.trigram.lower()}:tfjm.org",
|
f"@{participant.matrix_username}:tfjm.org")
|
||||||
|
await Matrix.set_room_power_level(f"#equipe-{team.trigram.lower()}:tfjm.org",
|
||||||
f"@{participant.matrix_username}:tfjm.org", 50)
|
f"@{participant.matrix_username}:tfjm.org", 50)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Manage channels to discuss about problems
|
||||||
|
for i in range(9):
|
||||||
|
self.stdout.write(f"Create channel for problem {i}...")
|
||||||
|
if not await Matrix.resolve_room_alias(f"#mec-{i}:tfjm.org"):
|
||||||
|
await Matrix.create_room(
|
||||||
|
visibility=RoomVisibility.public,
|
||||||
|
alias=f"mec-{i}",
|
||||||
|
name=f"Mise en commun - {'Général' if i == 0 else f'Problème {i}'}",
|
||||||
|
topic=f"Discussion autour des problèmes",
|
||||||
|
federate=False,
|
||||||
|
preset=RoomPreset.public_chat,
|
||||||
|
invite=[f"@{registration.matrix_username}:tfjm.org"
|
||||||
|
for registration in Registration.objects.all()],
|
||||||
|
power_level_override={
|
||||||
|
f"@{registration.matrix_username}:tfjm.org": (95 if registration.is_admin else 50)
|
||||||
|
for registration in VolunteerRegistration.objects.all()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await Matrix.set_room_avatar(f"#mec-{i}:tfjm.org", avatar_uri)
|
||||||
|
|
||||||
|
for registration in Registration.objects.all():
|
||||||
|
await Matrix.invite(f"#mec-{i}:tfjm.org", registration.matrix_username)
|
||||||
|
|
||||||
|
for registration in VolunteerRegistration.objects.all():
|
||||||
|
await Matrix.set_room_power_level(f"#mec-{i}:tfjm.org",
|
||||||
|
f"@{registration.matrix_username}:tfjm.org",
|
||||||
|
95 if registration.is_admin else 50)
|
||||||
|
"""
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(main())
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.7 on 2021-04-03 19:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0003_tournament_remote'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='passage',
|
||||||
|
name='defender_penalties',
|
||||||
|
field=models.PositiveSmallIntegerField(default=0, help_text='Number of penalties for the defender. The defender will loose a 0.5 coefficient per penalty.', verbose_name='penalties'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/participation/migrations/0005_pool_results_available.py
Normal file
18
apps/participation/migrations/0005_pool_results_available.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.7 on 2021-04-10 07:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0004_passage_defender_penalties'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pool',
|
||||||
|
name='results_available',
|
||||||
|
field=models.BooleanField(default=False, help_text='Check this case when results become accessible to teams. They stay accessible to you. Only averages are given.', verbose_name='results available'),
|
||||||
|
),
|
||||||
|
]
|
21
apps/participation/migrations/0006_auto_20220426_1346.py
Normal file
21
apps/participation/migrations/0006_auto_20220426_1346.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-04-26 11:46
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0005_pool_results_available'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='solution',
|
||||||
|
options={'ordering': ('participation__team__trigram', 'final_solution', 'problem'), 'verbose_name': 'solution', 'verbose_name_plural': 'solutions'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='synthesis',
|
||||||
|
options={'ordering': ('passage__pool__round', 'type'), 'verbose_name': 'synthesis', 'verbose_name_plural': 'syntheses'},
|
||||||
|
),
|
||||||
|
]
|
17
apps/participation/migrations/0007_remove_passage_place.py
Normal file
17
apps/participation/migrations/0007_remove_passage_place.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-04-26 19:42
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0006_auto_20220426_1346'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='passage',
|
||||||
|
name='place',
|
||||||
|
),
|
||||||
|
]
|
24
apps/participation/migrations/0008_auto_20220429_1853.py
Normal file
24
apps/participation/migrations/0008_auto_20220429_1853.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-04-29 16:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import participation.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0007_remove_passage_place'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='solution',
|
||||||
|
name='file',
|
||||||
|
field=models.FileField(unique=True, upload_to=participation.models.get_solution_filename, verbose_name='file'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='synthesis',
|
||||||
|
name='file',
|
||||||
|
field=models.FileField(unique=True, upload_to=participation.models.get_synthesis_filename, verbose_name='file'),
|
||||||
|
),
|
||||||
|
]
|
27
apps/participation/migrations/0009_tweak.py
Normal file
27
apps/participation/migrations/0009_tweak.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-05-15 14:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0008_auto_20220429_1853'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Tweak',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('diff', models.IntegerField(help_text='Score to add/remove on the final score', verbose_name='difference')),
|
||||||
|
('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tweaks', to='participation.participation', verbose_name='participation')),
|
||||||
|
('pool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.pool', verbose_name='passage')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'tweak',
|
||||||
|
'verbose_name_plural': 'tweaks',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@@ -368,18 +368,26 @@ class Pool(models.Model):
|
|||||||
help_text=_("The link of the BBB visio for this pool."),
|
help_text=_("The link of the BBB visio for this pool."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
results_available = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("results available"),
|
||||||
|
help_text=_("Check this case when results become accessible to teams. "
|
||||||
|
"They stay accessible to you. Only averages are given."),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def solutions(self):
|
def solutions(self):
|
||||||
return Solution.objects.filter(participation__in=self.participations, final_solution=self.tournament.final)
|
return Solution.objects.filter(participation__in=self.participations, final_solution=self.tournament.final)
|
||||||
|
|
||||||
def average(self, participation):
|
def average(self, participation):
|
||||||
return sum(passage.average(participation) for passage in self.passages.all())
|
return sum(passage.average(participation) for passage in self.passages.all()) \
|
||||||
|
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy("participation:pool_detail", args=(self.pk,))
|
return reverse_lazy("participation:pool_detail", args=(self.pk,))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Pool {round} for tournament {tournament} with teams {teams}")\
|
return _("Pool of day {round} for tournament {tournament} with teams {teams}")\
|
||||||
.format(round=self.round,
|
.format(round=self.round,
|
||||||
tournament=str(self.tournament),
|
tournament=str(self.tournament),
|
||||||
teams=", ".join(participation.team.trigram for participation in self.participations.all()))
|
teams=", ".join(participation.team.trigram for participation in self.participations.all()))
|
||||||
@@ -397,13 +405,6 @@ class Passage(models.Model):
|
|||||||
related_name="passages",
|
related_name="passages",
|
||||||
)
|
)
|
||||||
|
|
||||||
place = models.CharField(
|
|
||||||
verbose_name=_("place"),
|
|
||||||
max_length=255,
|
|
||||||
help_text=_("Where the solution is presented?"),
|
|
||||||
default="Non indiqué",
|
|
||||||
)
|
|
||||||
|
|
||||||
solution_number = models.PositiveSmallIntegerField(
|
solution_number = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_("defended solution"),
|
verbose_name=_("defended solution"),
|
||||||
choices=[
|
choices=[
|
||||||
@@ -432,6 +433,13 @@ class Passage(models.Model):
|
|||||||
related_name="+",
|
related_name="+",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defender_penalties = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_("penalties"),
|
||||||
|
default=0,
|
||||||
|
help_text=_("Number of penalties for the defender. "
|
||||||
|
"The defender will loose a 0.5 coefficient per penalty."),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def defended_solution(self) -> "Solution":
|
def defended_solution(self) -> "Solution":
|
||||||
return Solution.objects.get(
|
return Solution.objects.get(
|
||||||
@@ -439,44 +447,44 @@ class Passage(models.Model):
|
|||||||
problem=self.solution_number,
|
problem=self.solution_number,
|
||||||
final_solution=self.pool.tournament.final)
|
final_solution=self.pool.tournament.final)
|
||||||
|
|
||||||
def avg(self, iterator) -> int:
|
def avg(self, iterator) -> float:
|
||||||
items = [i for i in iterator if i]
|
items = [i for i in iterator if i]
|
||||||
return sum(items) / len(items) if items else 0
|
return sum(items) / len(items) if items else 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender_writing(self) -> int:
|
def average_defender_writing(self) -> float:
|
||||||
return self.avg(note.defender_writing for note in self.notes.all())
|
return self.avg(note.defender_writing for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender_oral(self) -> int:
|
def average_defender_oral(self) -> float:
|
||||||
return self.avg(note.defender_oral for note in self.notes.all())
|
return self.avg(note.defender_oral for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender(self) -> int:
|
def average_defender(self) -> float:
|
||||||
return self.average_defender_writing + 2 * self.average_defender_oral
|
return self.average_defender_writing + (2 - 0.5 * self.defender_penalties) * self.average_defender_oral
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_opponent_writing(self) -> int:
|
def average_opponent_writing(self) -> float:
|
||||||
return self.avg(note.opponent_writing for note in self.notes.all())
|
return self.avg(note.opponent_writing for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_opponent_oral(self) -> int:
|
def average_opponent_oral(self) -> float:
|
||||||
return self.avg(note.opponent_oral for note in self.notes.all())
|
return self.avg(note.opponent_oral for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_opponent(self) -> int:
|
def average_opponent(self) -> float:
|
||||||
return self.average_opponent_writing + 2 * self.average_opponent_oral
|
return self.average_opponent_writing + 2 * self.average_opponent_oral
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_reporter_writing(self) -> int:
|
def average_reporter_writing(self) -> float:
|
||||||
return self.avg(note.reporter_writing for note in self.notes.all())
|
return self.avg(note.reporter_writing for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_reporter_oral(self) -> int:
|
def average_reporter_oral(self) -> float:
|
||||||
return self.avg(note.reporter_oral for note in self.notes.all())
|
return self.avg(note.reporter_oral for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_reporter(self) -> int:
|
def average_reporter(self) -> float:
|
||||||
return self.average_reporter_writing + self.average_reporter_oral
|
return self.average_reporter_writing + self.average_reporter_oral
|
||||||
|
|
||||||
def average(self, participation):
|
def average(self, participation):
|
||||||
@@ -507,6 +515,33 @@ class Passage(models.Model):
|
|||||||
verbose_name_plural = _("passages")
|
verbose_name_plural = _("passages")
|
||||||
|
|
||||||
|
|
||||||
|
class Tweak(models.Model):
|
||||||
|
pool = models.ForeignKey(
|
||||||
|
Pool,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("passage"),
|
||||||
|
)
|
||||||
|
|
||||||
|
participation = models.ForeignKey(
|
||||||
|
Participation,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("participation"),
|
||||||
|
related_name='tweaks',
|
||||||
|
)
|
||||||
|
|
||||||
|
diff = models.IntegerField(
|
||||||
|
verbose_name=_("difference"),
|
||||||
|
help_text=_("Score to add/remove on the final score"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Tweak for {self.participation.team} of {self.diff} points"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("tweak")
|
||||||
|
verbose_name_plural = _("tweaks")
|
||||||
|
|
||||||
|
|
||||||
def get_solution_filename(instance, filename):
|
def get_solution_filename(instance, filename):
|
||||||
return f"solutions/{instance.participation.team.trigram}_{instance.problem}" \
|
return f"solutions/{instance.participation.team.trigram}_{instance.problem}" \
|
||||||
+ ("final" if instance.final_solution else "")
|
+ ("final" if instance.final_solution else "")
|
||||||
@@ -540,18 +575,18 @@ class Solution(models.Model):
|
|||||||
verbose_name=_("file"),
|
verbose_name=_("file"),
|
||||||
upload_to=get_solution_filename,
|
upload_to=get_solution_filename,
|
||||||
unique=True,
|
unique=True,
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Solution of team {team} for problem {problem}")\
|
return _("Solution of team {team} for problem {problem}")\
|
||||||
.format(team=self.participation.team.name, problem=self.problem)
|
.format(team=self.participation.team.name, problem=self.problem)\
|
||||||
|
+ (" " + str(_("for final")) if self.final_solution else "")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("solution")
|
verbose_name = _("solution")
|
||||||
verbose_name_plural = _("solutions")
|
verbose_name_plural = _("solutions")
|
||||||
unique_together = (('participation', 'problem', 'final_solution', ), )
|
unique_together = (('participation', 'problem', 'final_solution', ), )
|
||||||
|
ordering = ('participation__team__trigram', 'final_solution', 'problem',)
|
||||||
|
|
||||||
|
|
||||||
class Synthesis(models.Model):
|
class Synthesis(models.Model):
|
||||||
@@ -579,17 +614,21 @@ class Synthesis(models.Model):
|
|||||||
verbose_name=_("file"),
|
verbose_name=_("file"),
|
||||||
upload_to=get_synthesis_filename,
|
upload_to=get_synthesis_filename,
|
||||||
unique=True,
|
unique=True,
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Synthesis for the {type} of the {passage}").format(type=self.get_type_display(), passage=self.passage)
|
return _("Synthesis of {team} as {type} for problem {problem} of {defender}").format(
|
||||||
|
team=self.participation.team.trigram,
|
||||||
|
type=self.get_type_display(),
|
||||||
|
problem=self.passage.solution_number,
|
||||||
|
defender=self.passage.defender.team.trigram,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("synthesis")
|
verbose_name = _("synthesis")
|
||||||
verbose_name_plural = _("syntheses")
|
verbose_name_plural = _("syntheses")
|
||||||
unique_together = (('participation', 'passage', 'type', ), )
|
unique_together = (('participation', 'passage', 'type', ), )
|
||||||
|
ordering = ('passage__pool__round', 'type',)
|
||||||
|
|
||||||
|
|
||||||
class Note(models.Model):
|
class Note(models.Model):
|
||||||
@@ -643,6 +682,15 @@ class Note(models.Model):
|
|||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
|
||||||
|
reporter_writing: int, reporter_oral: int):
|
||||||
|
self.defender_writing = defender_writing
|
||||||
|
self.defender_oral = defender_oral
|
||||||
|
self.opponent_writing = opponent_writing
|
||||||
|
self.opponent_oral = opponent_oral
|
||||||
|
self.reporter_writing = reporter_writing
|
||||||
|
self.reporter_oral = reporter_oral
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy("participation:passage_detail", args=(self.passage.pk,))
|
return reverse_lazy("participation:passage_detail", args=(self.passage.pk,))
|
||||||
|
|
||||||
|
@@ -107,20 +107,20 @@ class PassageTable(tables.Table):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def render_defender(self, value):
|
def render_defender(self, value):
|
||||||
return value.team
|
return value.team.trigram
|
||||||
|
|
||||||
def render_opponent(self, value):
|
def render_opponent(self, value):
|
||||||
return value.team
|
return value.team.trigram
|
||||||
|
|
||||||
def render_reporter(self, value):
|
def render_reporter(self, value):
|
||||||
return value.team
|
return value.team.trigram
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'class': 'table table-condensed table-striped text-center',
|
'class': 'table table-condensed table-striped text-center',
|
||||||
}
|
}
|
||||||
model = Passage
|
model = Passage
|
||||||
fields = ('defender', 'opponent', 'reporter', 'place',)
|
fields = ('defender', 'opponent', 'reporter', 'solution_number', )
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
|
||||||
|
|
||||||
|
@@ -24,24 +24,33 @@
|
|||||||
|
|
||||||
<dt class="col-sm-2">{% trans "Solutions:" %}</dt>
|
<dt class="col-sm-2">{% trans "Solutions:" %}</dt>
|
||||||
<dd class="col-sm-10">
|
<dd class="col-sm-10">
|
||||||
|
<ul>
|
||||||
{% for solution in participation.solutions.all %}
|
{% for solution in participation.solutions.all %}
|
||||||
<a href="{{ solution.file.url }}" data-turbolinks="false">{{ solution }}{% if not forloop.last %}, {% endif %}</a>
|
<li><a href="{{ solution.file.url }}" data-turbolinks="false">{{ solution }}</a></li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans "No solution was uploaded yet." %}
|
<li>{% trans "No solution was uploaded yet." %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
{% if participation.pools.all %}
|
{% if participation.pools.all %}
|
||||||
<dt class="col-sm-2">{% trans "Pools:" %}</dt>
|
<dt class="col-sm-2">{% trans "Pools:" %}</dt>
|
||||||
<dd class="col-sm-10">
|
<dd class="col-sm-10">
|
||||||
|
<ul>
|
||||||
{% for pool in participation.pools.all %}
|
{% for pool in participation.pools.all %}
|
||||||
<a href="{{ pool.get_absolute_url }}" data-turbolinks="false">{{ pool }}{% if not forloop.last %}, {% endif %}</a>
|
<li><a href="{{ pool.get_absolute_url }}" data-turbolinks="false">{{ pool }}{% if not forloop.last %}, {% endif %}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
|
{% if participation.final %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% trans "If you upload a solution, this will replace the version for the final tournament." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<button class="btn btn-primary" data-toggle="modal" data-target="#uploadSolutionModal">{% trans "Upload solution" %}</button>
|
<button class="btn btn-primary" data-toggle="modal" data-target="#uploadSolutionModal">{% trans "Upload solution" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -25,8 +25,8 @@
|
|||||||
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
|
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
|
||||||
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}" data-turbolinks="false">{{ passage.defended_solution }}</a></dd>
|
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}" data-turbolinks="false">{{ passage.defended_solution }}</a></dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Place:" %}</dt>
|
<dt class="col-sm-3">{% trans "Defender penalties count:" %}</dt>
|
||||||
<dd class="col-sm-9">{{ passage.place }}</dd>
|
<dd class="col-sm-9">{{ passage.defender_penalties }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
|
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
@@ -38,9 +38,11 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
{% if user.registration.is_admin %}
|
{% if notes is not None %}
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
|
{% if my_note is not None %}
|
||||||
<button class="btn btn-info" data-toggle="modal" data-target="#updateNotesModal">{% trans "Update notes" %}</button>
|
<button class="btn btn-info" data-toggle="modal" data-target="#updateNotesModal">{% trans "Update notes" %}</button>
|
||||||
|
{% endif %}
|
||||||
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePassageModal">{% trans "Update" %}</button>
|
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePassageModal">{% trans "Update" %}</button>
|
||||||
</div>
|
</div>
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
@@ -61,50 +63,52 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-8">{% trans "Average points for the defender writing:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the defender writing:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_defender_writing }}/20</dd>
|
<dd class="col-sm-4">{{ passage.average_defender_writing|floatformat }}/20</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Average points for the defender oral:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the defender oral:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_defender_oral }}/16</dd>
|
<dd class="col-sm-4">{{ passage.average_defender_oral|floatformat }}/16</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Average points for the opponent writing:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the opponent writing:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_opponent_writing }}/9</dd>
|
<dd class="col-sm-4">{{ passage.average_opponent_writing|floatformat }}/9</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Average points for the opponent oral:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the opponent oral:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_opponent_oral }}/10</dd>
|
<dd class="col-sm-4">{{ passage.average_opponent_oral|floatformat }}/10</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Average points for the reporter writing:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the reporter writing:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_reporter_writing }}/9</dd>
|
<dd class="col-sm-4">{{ passage.average_reporter_writing|floatformat }}/9</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Average points for the reporter oral:" %}</dt>
|
<dt class="col-sm-8">{% trans "Average points for the reporter oral:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_reporter_oral }}/10</dd>
|
<dd class="col-sm-4">{{ passage.average_reporter_oral|floatformat }}/10</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-8">{% trans "Defender points:" %}</dt>
|
<dt class="col-sm-8">{% trans "Defender points:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_defender }}/52</dd>
|
<dd class="col-sm-4">{{ passage.average_defender|floatformat }}/52</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Opponent points:" %}</dt>
|
<dt class="col-sm-8">{% trans "Opponent points:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_opponent }}/29</dd>
|
<dd class="col-sm-4">{{ passage.average_opponent|floatformat }}/29</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">{% trans "Reporter points:" %}</dt>
|
<dt class="col-sm-8">{% trans "Reporter points:" %}</dt>
|
||||||
<dd class="col-sm-4">{{ passage.average_reporter }}/19</dd>
|
<dd class="col-sm-4">{{ passage.average_reporter|floatformat }}/19</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.registration.is_admin %}
|
{% if notes is not None %}
|
||||||
{% trans "Update passage" as modal_title %}
|
{% trans "Update passage" as modal_title %}
|
||||||
{% trans "Update" as modal_button %}
|
{% trans "Update" as modal_button %}
|
||||||
{% url "participation:passage_update" pk=passage.pk as modal_action %}
|
{% url "participation:passage_update" pk=passage.pk as modal_action %}
|
||||||
{% include "base_modal.html" with modal_id="updatePassage" %}
|
{% include "base_modal.html" with modal_id="updatePassage" %}
|
||||||
|
|
||||||
|
{% if my_note is not None %}
|
||||||
{% trans "Update notes" as modal_title %}
|
{% trans "Update notes" as modal_title %}
|
||||||
{% trans "Update" as modal_button %}
|
{% trans "Update" as modal_button %}
|
||||||
{% url "participation:update_notes" pk=my_note.pk as modal_action %}
|
{% url "participation:update_notes" pk=my_note.pk as modal_action %}
|
||||||
{% include "base_modal.html" with modal_id="updateNotes" %}
|
{% include "base_modal.html" with modal_id="updateNotes" %}
|
||||||
|
{% endif %}
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
{% trans "Upload synthesis" as modal_title %}
|
{% trans "Upload synthesis" as modal_title %}
|
||||||
{% trans "Upload" as modal_button %}
|
{% trans "Upload" as modal_button %}
|
||||||
@@ -116,18 +120,20 @@
|
|||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
{% if user.registration.is_admin %}
|
{% if notes is not None %}
|
||||||
$('button[data-target="#updatePassageModal"]').click(function() {
|
$('button[data-target="#updatePassageModal"]').click(function() {
|
||||||
let modalBody = $("#updatePassageModal div.modal-body");
|
let modalBody = $("#updatePassageModal div.modal-body");
|
||||||
if (!modalBody.html().trim())
|
if (!modalBody.html().trim())
|
||||||
modalBody.load("{% url "participation:passage_update" pk=passage.pk %} #form-content")
|
modalBody.load("{% url "participation:passage_update" pk=passage.pk %} #form-content")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{% if my_note is not None %}
|
||||||
$('button[data-target="#updateNotesModal"]').click(function() {
|
$('button[data-target="#updateNotesModal"]').click(function() {
|
||||||
let modalBody = $("#updateNotesModal div.modal-body");
|
let modalBody = $("#updateNotesModal div.modal-body");
|
||||||
if (!modalBody.html().trim())
|
if (!modalBody.html().trim())
|
||||||
modalBody.load("{% url "participation:update_notes" pk=my_note.pk %} #form-content")
|
modalBody.load("{% url "participation:update_notes" pk=my_note.pk %} #form-content")
|
||||||
});
|
});
|
||||||
|
{% endif %}
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
$('button[data-target="#uploadSynthesisModal"]').click(function() {
|
$('button[data-target="#uploadSynthesisModal"]').click(function() {
|
||||||
let modalBody = $("#uploadSynthesisModal div.modal-body");
|
let modalBody = $("#uploadSynthesisModal div.modal-body");
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "BigBlueButton link:" %}</dt>
|
<dt class="col-sm-3">{% trans "BigBlueButton link:" %}</dt>
|
||||||
<dd class="col-sm-9"><a href="{{ pool.bbb_url }}">{{ pool.bbb_url }}</a></dd>
|
<dd class="col-sm-9">{{ pool.bbb_url|urlize }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<div class="card bg-light shadow">
|
<div class="card bg-light shadow">
|
||||||
@@ -43,17 +43,18 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ul>
|
<ul>
|
||||||
{% for participation, note in notes %}
|
{% for participation, note in notes %}
|
||||||
<li><strong>{{ participation.team }} :</strong> {{ note }}</li>
|
<li><strong>{{ participation.team }} :</strong> {{ note|floatformat }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if user.registration.is_admin %}
|
{% if user.registration.is_volunteer %}
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<button class="btn btn-success" data-toggle="modal" data-target="#addPassageModal">{% trans "Add passage" %}</button>
|
<button class="btn btn-success" data-toggle="modal" data-target="#addPassageModal">{% trans "Add passage" %}</button>
|
||||||
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePoolModal">{% trans "Update" %}</button>
|
<button class="btn btn-primary" data-toggle="modal" data-target="#updatePoolModal">{% trans "Update" %}</button>
|
||||||
<button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamsModal">{% trans "Update teams" %}</button>
|
<button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamsModal">{% trans "Update teams" %}</button>
|
||||||
|
<button class="btn btn-primary" data-toggle="modal" data-target="#uploadNotesModal">{% trans "Upload notes from a CSV file" %}</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -78,6 +79,11 @@
|
|||||||
{% trans "Update" as modal_button %}
|
{% trans "Update" as modal_button %}
|
||||||
{% url "participation:pool_update_teams" pk=pool.pk as modal_action %}
|
{% url "participation:pool_update_teams" pk=pool.pk as modal_action %}
|
||||||
{% include "base_modal.html" with modal_id="updateTeams" %}
|
{% include "base_modal.html" with modal_id="updateTeams" %}
|
||||||
|
|
||||||
|
{% trans "Upload notes" as modal_title %}
|
||||||
|
{% trans "Upload" as modal_button %}
|
||||||
|
{% url "participation:pool_upload_notes" pk=pool.pk as modal_action %}
|
||||||
|
{% include "base_modal.html" with modal_id="uploadNotes" modal_button_type="success" modal_enctype="multipart/form-data" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
@@ -100,6 +106,12 @@
|
|||||||
if (!modalBody.html().trim())
|
if (!modalBody.html().trim())
|
||||||
modalBody.load("{% url "participation:passage_create" pk=pool.pk %} #form-content")
|
modalBody.load("{% url "participation:passage_create" pk=pool.pk %} #form-content")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('button[data-target="#uploadNotesModal"]').click(function() {
|
||||||
|
let modalBody = $("#uploadNotesModal div.modal-body");
|
||||||
|
if (!modalBody.html().trim())
|
||||||
|
modalBody.load("{% url "participation:pool_upload_notes" pk=pool.pk %} #form-content")
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -101,11 +101,13 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
{% if user.registration.is_volunteer %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="btn btn-info" href="{% url "participation:team_authorizations" pk=team.pk %}" data-turbolinks="false">
|
<a class="btn btn-info" href="{% url "participation:team_authorizations" pk=team.pk %}" data-turbolinks="false">
|
||||||
<i class="fas fa-file-archive"></i> {% trans "Download all submitted authorizations" %}
|
<i class="fas fa-file-archive"></i> {% trans "Download all submitted authorizations" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamModal">{% trans "Update" %}</button>
|
<button class="btn btn-primary" data-toggle="modal" data-target="#updateTeamModal">{% trans "Update" %}</button>
|
||||||
|
@@ -62,6 +62,7 @@
|
|||||||
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<a href="{% url "participation:tournament_update" pk=tournament.pk %}"><button class="btn btn-secondary">{% trans "Edit tournament" %}</button></a>
|
<a href="{% url "participation:tournament_update" pk=tournament.pk %}"><button class="btn btn-secondary">{% trans "Edit tournament" %}</button></a>
|
||||||
|
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}"><button class="btn btn-success">{% trans "Export as CSV" %}</button></a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ul>
|
<ul>
|
||||||
{% for participation, note in notes %}
|
{% for participation, note in notes %}
|
||||||
<li><strong>{{ participation.team }} :</strong> {{ note }}</li>
|
<li><strong>{{ participation.team }} :</strong> {{ note|floatformat }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
14
apps/participation/templates/participation/upload_notes.html
Normal file
14
apps/participation/templates/participation/upload_notes.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<div id="form-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Upload" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@@ -1,10 +1,15 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% load crispy_forms_filters i18n %}
|
{% load crispy_forms_filters i18n static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
<div id="form-content">
|
<div id="form-content">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% trans "Templates:" %}
|
||||||
|
<a class="alert-link" href="{% static "Fiche_synthèse.pdf" %}"> PDF</a> -
|
||||||
|
<a class="alert-link" href="{% static "Fiche_synthèse.tex" %}"> TEX</a>
|
||||||
|
</div>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -487,20 +487,6 @@ class TestStudentParticipation(TestCase):
|
|||||||
resp = self.client.get(reverse("participation:participation_detail", args=(self.second_team.pk,)))
|
resp = self.client.get(reverse("participation:participation_detail", args=(self.second_team.pk,)))
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
def test_cover_matrix(self):
|
|
||||||
"""
|
|
||||||
Load matrix scripts, to cover them and ensure that they can run.
|
|
||||||
"""
|
|
||||||
self.user.registration.team = self.team
|
|
||||||
self.user.registration.save()
|
|
||||||
self.second_user.registration.team = self.second_team
|
|
||||||
self.second_user.registration.save()
|
|
||||||
self.team.participation.valid = True
|
|
||||||
self.team.participation.received_participation = self.second_team.participation
|
|
||||||
self.team.participation.save()
|
|
||||||
|
|
||||||
call_command('fix_matrix_channels')
|
|
||||||
|
|
||||||
|
|
||||||
class TestAdmin(TestCase):
|
class TestAdmin(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
@@ -6,9 +6,10 @@ from django.views.generic import TemplateView
|
|||||||
|
|
||||||
from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, \
|
from .views import CreateTeamView, JoinTeamView, MyParticipationDetailView, MyTeamDetailView, NoteUpdateView, \
|
||||||
ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
|
ParticipationDetailView, PassageCreateView, PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, \
|
||||||
PoolUpdateTeamsView, PoolUpdateView, SolutionUploadView, SynthesisUploadView, TeamAuthorizationsView, \
|
PoolUpdateTeamsView, PoolUpdateView, PoolUploadNotesView, SolutionUploadView, SynthesisUploadView,\
|
||||||
TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TeamUploadMotivationLetterView, TournamentCreateView, \
|
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
||||||
TournamentDetailView, TournamentListView, TournamentUpdateView
|
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
||||||
|
TournamentListView, TournamentUpdateView
|
||||||
|
|
||||||
|
|
||||||
app_name = "participation"
|
app_name = "participation"
|
||||||
@@ -31,10 +32,12 @@ urlpatterns = [
|
|||||||
path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"),
|
path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"),
|
||||||
path("tournament/<int:pk>/", TournamentDetailView.as_view(), name="tournament_detail"),
|
path("tournament/<int:pk>/", TournamentDetailView.as_view(), name="tournament_detail"),
|
||||||
path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"),
|
path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"),
|
||||||
|
path("tournament/<int:pk>/csv/", TournamentExportCSVView.as_view(), name="tournament_csv"),
|
||||||
path("pools/create/", PoolCreateView.as_view(), name="pool_create"),
|
path("pools/create/", PoolCreateView.as_view(), name="pool_create"),
|
||||||
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
||||||
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
||||||
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
|
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
|
||||||
|
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
|
||||||
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
|
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
|
||||||
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
||||||
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
|
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import csv
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import os
|
import os
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
@@ -28,7 +29,7 @@ from tfjm.views import AdminMixin, VolunteerMixin
|
|||||||
|
|
||||||
from .forms import JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, PoolForm, \
|
from .forms import JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, PoolForm, \
|
||||||
PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
PoolTeamsForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
||||||
ValidateParticipationForm
|
UploadNotesForm, ValidateParticipationForm
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
||||||
|
|
||||||
@@ -164,7 +165,9 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||||||
if user.registration.is_admin or user.registration.participates and \
|
if user.registration.is_admin or user.registration.participates and \
|
||||||
user.registration.team and user.registration.team.pk == kwargs["pk"] \
|
user.registration.team and user.registration.team.pk == kwargs["pk"] \
|
||||||
or user.registration.is_volunteer \
|
or user.registration.is_volunteer \
|
||||||
and self.object.participation.tournament in user.registration.interesting_tournaments:
|
and (self.object.participation.tournament in user.registration.interesting_tournaments
|
||||||
|
or self.object.participation.final
|
||||||
|
and Tournament.final_tournament() in user.registration.interesting_tournaments):
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
@@ -292,7 +295,9 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
if user.registration.is_admin or user.registration.participates and \
|
if user.registration.is_admin or user.registration.participates and \
|
||||||
user.registration.team and user.registration.team.pk == kwargs["pk"] \
|
user.registration.team and user.registration.team.pk == kwargs["pk"] \
|
||||||
or user.registration.is_volunteer \
|
or user.registration.is_volunteer \
|
||||||
and self.get_object().participation.tournament in user.registration.interesting_tournaments:
|
and (self.get_object().participation.tournament in user.registration.interesting_tournaments
|
||||||
|
or self.get_object().participation.final
|
||||||
|
and Tournament.final_tournament() in user.registration.interesting_tournaments):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
@@ -342,6 +347,7 @@ class MotivationLetterView(LoginRequiredMixin, View):
|
|||||||
"""
|
"""
|
||||||
Display the sent motivation letter.
|
Display the sent motivation letter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
filename = kwargs["filename"]
|
filename = kwargs["filename"]
|
||||||
path = f"media/authorization/motivation_letters/{filename}"
|
path = f"media/authorization/motivation_letters/{filename}"
|
||||||
@@ -372,9 +378,10 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
|
|||||||
user = request.user
|
user = request.user
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
return super().handle_no_permission()
|
return super().handle_no_permission()
|
||||||
if user.registration.is_admin or user.registration.participates and user.registration.team.pk == kwargs["pk"] \
|
if user.registration.is_admin or user.registration.is_volunteer \
|
||||||
or user.registration.is_volunteer \
|
and (self.get_object().participation.tournament in user.registration.interesting_tournaments
|
||||||
and self.get_object().participation.tournament in user.registration.interesting_tournaments:
|
or self.get_object().participation.final
|
||||||
|
and Tournament.final_tournament() in user.registration.interesting_tournaments):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
@@ -454,6 +461,7 @@ class MyParticipationDetailView(LoginRequiredMixin, RedirectView):
|
|||||||
"""
|
"""
|
||||||
Redirects to the detail view of the participation of the team.
|
Redirects to the detail view of the participation of the team.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
registration = user.registration
|
registration = user.registration
|
||||||
@@ -480,7 +488,9 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView):
|
|||||||
and user.registration.team.participation \
|
and user.registration.team.participation \
|
||||||
and user.registration.team.participation.pk == kwargs["pk"] \
|
and user.registration.team.participation.pk == kwargs["pk"] \
|
||||||
or user.registration.is_volunteer \
|
or user.registration.is_volunteer \
|
||||||
and self.object.tournament in user.registration.interesting_tournaments:
|
and (self.get_object().tournament in user.registration.interesting_tournaments
|
||||||
|
or self.get_object().final
|
||||||
|
and Tournament.final_tournament() in user.registration.interesting_tournaments):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
@@ -535,12 +545,14 @@ class TournamentDetailView(DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["teams"] = ParticipationTable(self.object.participations.all())
|
context["teams"] = ParticipationTable(self.object.participations.all())
|
||||||
context["pools"] = PoolTable(self.object.pools.all())
|
context["pools"] = PoolTable(self.object.pools.order_by('id').all())
|
||||||
|
|
||||||
notes = dict()
|
notes = dict()
|
||||||
for participation in self.object.participations.all():
|
for participation in self.object.participations.all():
|
||||||
note = sum(pool.average(participation)
|
note = sum(pool.average(participation)
|
||||||
for pool in self.object.pools.filter(participations=participation).all())
|
for pool in self.object.pools.filter(participations=participation).all()
|
||||||
|
if pool.results_available
|
||||||
|
or (self.request.user.is_authenticated and self.request.user.registration.is_volunteer))
|
||||||
if note:
|
if note:
|
||||||
notes[participation] = note
|
notes[participation] = note
|
||||||
context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True)
|
context["notes"] = sorted(notes.items(), key=lambda x: x[1], reverse=True)
|
||||||
@@ -548,6 +560,40 @@ class TournamentDetailView(DetailView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class TournamentExportCSVView(VolunteerMixin, DetailView):
|
||||||
|
"""
|
||||||
|
Export team information in a CSV file.
|
||||||
|
"""
|
||||||
|
model = Tournament
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
tournament = self.get_object()
|
||||||
|
|
||||||
|
resp = HttpResponse(
|
||||||
|
content_type='text/csv',
|
||||||
|
headers={'Content-Disposition': f'attachment; filename="Tournoi de {tournament.name}.csv"'},
|
||||||
|
)
|
||||||
|
writer = csv.DictWriter(resp, ('Tournoi', 'Équipe', 'Trigramme', 'Nom', 'Prénom', 'Genre', 'Date de naissance'))
|
||||||
|
writer.writeheader()
|
||||||
|
|
||||||
|
for participation in tournament.participations.filter(valid=True).order_by('team__trigram').all():
|
||||||
|
for registration in participation.team.participants\
|
||||||
|
.order_by('coachregistration', 'user__last_name').all():
|
||||||
|
writer.writerow({
|
||||||
|
'Tournoi': tournament.name,
|
||||||
|
'Équipe': participation.team.name,
|
||||||
|
'Trigramme': participation.team.trigram,
|
||||||
|
'Nom': registration.user.last_name,
|
||||||
|
'Prénom': registration.user.first_name,
|
||||||
|
'Genre': registration.get_gender_display() if isinstance(registration, StudentRegistration)
|
||||||
|
else 'Encandrant⋅e',
|
||||||
|
'Date de naissance': registration.birth_date if isinstance(registration, StudentRegistration)
|
||||||
|
else 'Encandrant⋅e',
|
||||||
|
})
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
class SolutionUploadView(LoginRequiredMixin, FormView):
|
class SolutionUploadView(LoginRequiredMixin, FormView):
|
||||||
template_name = "participation/upload_solution.html"
|
template_name = "participation/upload_solution.html"
|
||||||
form_class = SolutionForm
|
form_class = SolutionForm
|
||||||
@@ -563,6 +609,7 @@ class SolutionUploadView(LoginRequiredMixin, FormView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
When a solution is submitted, it replaces a previous solution if existing,
|
When a solution is submitted, it replaces a previous solution if existing,
|
||||||
@@ -574,7 +621,7 @@ class SolutionUploadView(LoginRequiredMixin, FormView):
|
|||||||
problem=form_sol.problem,
|
problem=form_sol.problem,
|
||||||
final_solution=self.participation.final)
|
final_solution=self.participation.final)
|
||||||
|
|
||||||
tournament = Tournament.final_tournament() if self.participation.final else self.participation.final
|
tournament = Tournament.final_tournament() if self.participation.final else self.participation.tournament
|
||||||
if timezone.now() > tournament.solution_limit and sol_qs.exists():
|
if timezone.now() > tournament.solution_limit and sol_qs.exists():
|
||||||
form.add_error(None, _("You can't upload a solution after the deadline."))
|
form.add_error(None, _("You can't upload a solution after the deadline."))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
@@ -585,7 +632,7 @@ class SolutionUploadView(LoginRequiredMixin, FormView):
|
|||||||
sol.save()
|
sol.save()
|
||||||
sol.delete()
|
sol.delete()
|
||||||
form_sol.participation = self.participation
|
form_sol.participation = self.participation
|
||||||
form_sol.final = self.participation.final
|
form_sol.final_solution = self.participation.final
|
||||||
form_sol.save()
|
form_sol.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
@@ -615,8 +662,10 @@ class PoolDetailView(LoginRequiredMixin, DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
context["passages"] = PassageTable(self.object.passages.all())
|
context["passages"] = PassageTable(self.object.passages.order_by('id').all())
|
||||||
|
|
||||||
|
if self.object.results_available or self.request.user.registration.is_volunteer:
|
||||||
|
# Hide notes before the end of the turn
|
||||||
notes = dict()
|
notes = dict()
|
||||||
for participation in self.object.participations.all():
|
for participation in self.object.participations.all():
|
||||||
note = self.object.average(participation)
|
note = self.object.average(participation)
|
||||||
@@ -655,6 +704,43 @@ class PoolUpdateTeamsView(VolunteerMixin, UpdateView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
|
|
||||||
|
class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
||||||
|
model = Pool
|
||||||
|
form_class = UploadNotesForm
|
||||||
|
template_name = 'participation/upload_notes.html'
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
|
||||||
|
if request.user.registration.is_admin or request.user.registration.is_volunteer \
|
||||||
|
and (self.object.tournament in request.user.registration.organized_tournaments.all()
|
||||||
|
or request.user.registration in self.object.juries.all()):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
return self.handle_no_permission()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def form_valid(self, form):
|
||||||
|
pool = self.get_object()
|
||||||
|
parsed_notes = form.cleaned_data['parsed_notes']
|
||||||
|
|
||||||
|
for vr, notes in parsed_notes.items():
|
||||||
|
if vr not in pool.juries.all():
|
||||||
|
form.add_error('file', _("The following user is not registered as a jury:") + " " + str(vr))
|
||||||
|
|
||||||
|
for i, passage in enumerate(pool.passages.all()):
|
||||||
|
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
|
||||||
|
passage_notes = notes[6 * i:6 * (i + 1)]
|
||||||
|
note.set_all(*passage_notes)
|
||||||
|
note.save()
|
||||||
|
|
||||||
|
messages.success(self.request, _("Notes were successfully uploaded."))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('participation:pool_detail', args=(self.kwargs['pk'],))
|
||||||
|
|
||||||
|
|
||||||
class PassageCreateView(VolunteerMixin, CreateView):
|
class PassageCreateView(VolunteerMixin, CreateView):
|
||||||
model = Passage
|
model = Passage
|
||||||
form_class = PassageForm
|
form_class = PassageForm
|
||||||
@@ -703,7 +789,9 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.request.user.registration in self.object.pool.juries.all():
|
if self.request.user.registration in self.object.pool.juries.all():
|
||||||
context["my_note"] = Note.objects.get(passage=self.object, jury=self.request.user.registration)
|
context["my_note"] = Note.objects.get_or_create(passage=self.object, jury=self.request.user.registration)[0]
|
||||||
|
context["notes"] = NoteTable([note for note in self.object.notes.all() if note])
|
||||||
|
elif self.request.user.registration.is_admin:
|
||||||
context["notes"] = NoteTable([note for note in self.object.notes.all() if note])
|
context["notes"] = NoteTable([note for note in self.object.notes.all() if note])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -738,7 +826,7 @@ class SynthesisUploadView(LoginRequiredMixin, FormView):
|
|||||||
self.participation = self.request.user.registration.team.participation
|
self.participation = self.request.user.registration.team.participation
|
||||||
self.passage = qs.get()
|
self.passage = qs.get()
|
||||||
|
|
||||||
if self.participation not in [self.passage.defender, self.passage.opponent, self.passage.reporter]:
|
if self.participation not in [self.passage.opponent, self.passage.reporter]:
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@@ -750,6 +838,7 @@ class SynthesisUploadView(LoginRequiredMixin, FormView):
|
|||||||
It is discriminating whenever the team is selected for the final tournament or not.
|
It is discriminating whenever the team is selected for the final tournament or not.
|
||||||
"""
|
"""
|
||||||
form_syn = form.instance
|
form_syn = form.instance
|
||||||
|
form_syn.type = 1 if self.participation == self.passage.opponent else 2
|
||||||
syn_qs = Synthesis.objects.filter(participation=self.participation,
|
syn_qs = Synthesis.objects.filter(participation=self.participation,
|
||||||
passage=self.passage,
|
passage=self.passage,
|
||||||
type=form_syn.type).all()
|
type=form_syn.type).all()
|
||||||
|
2
apps/registration/api/__init__.py
Normal file
2
apps/registration/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
47
apps/registration/api/serializers.py
Normal file
47
apps/registration/api/serializers.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||||
|
|
||||||
|
from ..models import AdminRegistration, CoachRegistration, ParticipantRegistration, \
|
||||||
|
StudentRegistration, VolunteerRegistration
|
||||||
|
|
||||||
|
|
||||||
|
class AdminSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AdminRegistration
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class CoachSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CoachRegistration
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipantSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ParticipantRegistration
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class StudentSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = StudentRegistration
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class VolunteerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = VolunteerRegistration
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationSerializer(PolymorphicSerializer):
|
||||||
|
model_serializer_mapping = {
|
||||||
|
AdminRegistration: AdminSerializer,
|
||||||
|
CoachRegistration: CoachSerializer,
|
||||||
|
StudentRegistration: StudentSerializer,
|
||||||
|
VolunteerRegistration: VolunteerSerializer,
|
||||||
|
}
|
11
apps/registration/api/urls.py
Normal file
11
apps/registration/api/urls.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from .views import RegistrationViewSet
|
||||||
|
|
||||||
|
|
||||||
|
def register_registration_urls(router, path):
|
||||||
|
"""
|
||||||
|
Configure router for registration REST API.
|
||||||
|
"""
|
||||||
|
router.register(path + "/registration", RegistrationViewSet)
|
15
apps/registration/api/views.py
Normal file
15
apps/registration/api/views.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (C) 2020 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from .serializers import RegistrationSerializer
|
||||||
|
from ..models import Registration
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationViewSet(ModelViewSet):
|
||||||
|
queryset = Registration.objects.all()
|
||||||
|
serializer_class = RegistrationSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_fields = ['user', 'participantregistration__team', ]
|
@@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"pos": 100,
|
"pos": 100,
|
||||||
"name": "Plateforme du TFJM²",
|
"name": "Plateforme du TFJM²",
|
||||||
"pattern": "^https://tfjm.org:8448/.*$",
|
"pattern": "^https://tfjm.org(:8448)?/.*$",
|
||||||
"user_field": "matrix_username",
|
"user_field": "matrix_username",
|
||||||
"restrict_users": false,
|
"restrict_users": false,
|
||||||
"proxy": true,
|
"proxy": true,
|
||||||
|
@@ -221,7 +221,7 @@ class PaymentForm(forms.ModelForm):
|
|||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
if "type" in cleaned_data and cleaned_data["type"] == "scholarship" \
|
if "type" in cleaned_data and cleaned_data["type"] == "scholarship" \
|
||||||
and "scholarship" not in cleaned_data and not self.instance.scholarship_file:
|
and "scholarship_file" not in cleaned_data and not self.instance.scholarship_file:
|
||||||
self.add_error("scholarship_file", _("You must upload your scholarship attestation."))
|
self.add_error("scholarship_file", _("You must upload your scholarship attestation."))
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
18
apps/registration/migrations/0003_alter_payment_type.py
Normal file
18
apps/registration/migrations/0003_alter_payment_type.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-04-26 11:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('registration', '0002_participantregistration_health_issues'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='payment',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(blank=True, choices=[('', 'No payment'), ('helloasso', 'Hello Asso'), ('scholarship', 'Scholarship'), ('bank_transfer', 'Bank transfer'), ('other', 'Other (please indicate)'), ('free', 'The tournament is free')], default='', max_length=16, verbose_name='type'),
|
||||||
|
),
|
||||||
|
]
|
@@ -333,6 +333,7 @@ class Payment(models.Model):
|
|||||||
('helloasso', "Hello Asso"),
|
('helloasso', "Hello Asso"),
|
||||||
('scholarship', _("Scholarship")),
|
('scholarship', _("Scholarship")),
|
||||||
('bank_transfer', _("Bank transfer")),
|
('bank_transfer', _("Bank transfer")),
|
||||||
|
('other', _("Other (please indicate)")),
|
||||||
('free', _("The tournament is free")),
|
('free', _("The tournament is free")),
|
||||||
],
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
You can pay with a credit card through
|
You can pay with a credit card through
|
||||||
<a class="alert-link" href="https://www.helloasso.com/associations/animath/evenements/tfjmm-2018">our Hello Asso page</a>.
|
<a class="alert-link" href="https://www.helloasso.com/associations/animath/evenements/tfjm-2022-tournois-regionaux">our Hello Asso page</a>.
|
||||||
To make the validation of the payment easier, <span class="text-danger">please use the same e-mail
|
To make the validation of the payment easier, <span class="text-danger">please use the same e-mail
|
||||||
address that you use on this platform.</span> The payment verification will be checked automatically
|
address that you use on this platform.</span> The payment verification will be checked automatically
|
||||||
under 10 minutes, you don't necessary need to fill this form.
|
under 10 minutes, you don't necessary need to fill this form.
|
||||||
|
@@ -53,7 +53,7 @@ né(e) le {{ registration.birth_date }},
|
|||||||
à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) organisé \`a :
|
à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) organisé \`a :
|
||||||
{{ tournament.place }}, du {{ tournament.date_start }} au {{ tournament.date_end }}.
|
{{ tournament.place }}, du {{ tournament.date_start }} au {{ tournament.date_end }}.
|
||||||
|
|
||||||
Iel se rendra au lieu indiqu\'e ci-dessus le vendredi matin et quittera les lieux l'après-midi du dimanche par
|
Iel se rendra au lieu indiqu\'e ci-dessus le samedi matin et quittera les lieux l'après-midi du dimanche par
|
||||||
ses propres moyens et sous la responsabilité du représentant légal.
|
ses propres moyens et sous la responsabilité du représentant légal.
|
||||||
|
|
||||||
|
|
||||||
|
@@ -73,7 +73,7 @@ Si le paiement de plusieurs élèves est fait en une seule opération, merci de
|
|||||||
\href{mailto: contact@tfjm.org}{contact@tfjm.org} \textbf{avant le paiement} pour garantir l'identification de ce dernier.
|
\href{mailto: contact@tfjm.org}{contact@tfjm.org} \textbf{avant le paiement} pour garantir l'identification de ce dernier.
|
||||||
|
|
||||||
\subsubsection*{Carte bancaire (uniquement les cartes françaises)}
|
\subsubsection*{Carte bancaire (uniquement les cartes françaises)}
|
||||||
Le paiement s'effectue en ligne via la plateforme à l'adresse : \url{https://www.helloasso.com/associations/animath/evenements/tfjmm-2021}
|
Le paiement s'effectue en ligne via la plateforme à l'adresse : \url{https://www.helloasso.com/associations/animath/evenements/tfjm-2022-tournois-regionaux}
|
||||||
|
|
||||||
Vous devez impérativement indiquer dans le champ "Référence" la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}.
|
Vous devez impérativement indiquer dans le champ "Référence" la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}.
|
||||||
|
|
||||||
|
@@ -242,7 +242,9 @@ class UserDetailView(LoginRequiredMixin, DetailView):
|
|||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
if user == me or me.registration.is_admin or me.registration.is_volunteer \
|
if user == me or me.registration.is_admin or me.registration.is_volunteer \
|
||||||
and user.registration.participates and user.registration.team \
|
and user.registration.participates and user.registration.team \
|
||||||
and user.registration.team.participation.tournament in me.registration.organized_tournaments.all() \
|
and (user.registration.team.participation.tournament in me.registration.organized_tournaments.all()
|
||||||
|
or user.registration.team.participation.final
|
||||||
|
and Tournament.final_tournament() in me.registration.organized_tournaments.all()) \
|
||||||
or user.registration.is_volunteer and me.registration.is_volunteer \
|
or user.registration.is_volunteer and me.registration.is_volunteer \
|
||||||
and me.registration.interesting_tournaments.intersection(user.registration.interesting_tournaments):
|
and me.registration.interesting_tournaments.intersection(user.registration.interesting_tournaments):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@@ -541,12 +543,19 @@ class SolutionView(LoginRequiredMixin, View):
|
|||||||
raise Http404
|
raise Http404
|
||||||
solution = Solution.objects.get(file__endswith=filename)
|
solution = Solution.objects.get(file__endswith=filename)
|
||||||
user = request.user
|
user = request.user
|
||||||
|
if user.registration.participates:
|
||||||
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
|
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
|
||||||
| Q(opponent=user.registration.team.participation)
|
| Q(opponent=user.registration.team.participation)
|
||||||
| Q(reporter=user.registration.team.participation),
|
| Q(reporter=user.registration.team.participation),
|
||||||
defender=solution.participation,
|
defender=solution.participation,
|
||||||
solution_number=solution.problem)
|
solution_number=solution.problem)
|
||||||
if not (user.registration.is_admin or user.registration.is_volunteer
|
else:
|
||||||
|
passage_participant_qs = Passage.objects.none()
|
||||||
|
if not (user.registration.is_admin
|
||||||
|
or user.registration.is_volunteer and user.registration
|
||||||
|
in (solution.participation.tournament
|
||||||
|
if not solution.final_solution else Tournament.final_tournament()).organizers.all()
|
||||||
|
or user.registration.is_volunteer
|
||||||
and Passage.objects.filter(Q(pool__juries=user.registration)
|
and Passage.objects.filter(Q(pool__juries=user.registration)
|
||||||
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
|
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
|
||||||
defender=solution.participation,
|
defender=solution.participation,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,24 @@
|
|||||||
asgiref~=3.3.1
|
Django>=3.2,<4.0
|
||||||
Django~=3.0
|
|
||||||
django-address~=0.2
|
django-address~=0.2
|
||||||
django-bootstrap-datepicker-plus~=3.0
|
django-bootstrap-datepicker-plus~=4.0
|
||||||
django-cas-server~=1.2
|
django-cas-server~=1.3
|
||||||
django-crispy-forms~=1.9
|
django-crispy-forms~=1.9
|
||||||
django-extensions~=3.0
|
django-extensions~=3.0
|
||||||
django-filter~=2.3
|
django-filter~=2.4
|
||||||
django-haystack~=3.0
|
django-haystack~=3.0
|
||||||
django-mailer~=2.0
|
django-mailer~=2.1
|
||||||
django-phonenumber-field~=5.0.0
|
django-phonenumber-field~=5.0.0
|
||||||
django-polymorphic~=3.0
|
django-polymorphic~=3.0
|
||||||
django-tables2~=2.3
|
django-tables2~=2.4
|
||||||
djangorestframework~=3.12
|
djangorestframework~=3.12
|
||||||
django-rest-polymorphic~=0.1
|
django-rest-polymorphic~=0.1
|
||||||
gunicorn~=20.0
|
gunicorn~=20.1
|
||||||
matrix-nio~=0.15
|
matrix-nio~=0.16
|
||||||
phonenumbers~=8.9.10
|
phonenumbers~=8.9.10
|
||||||
psycopg2-binary~=2.8
|
psycopg2-binary~=2.8
|
||||||
PyPDF3~=1.0.2
|
PyPDF3~=1.0.2
|
||||||
ipython~=7.19.0
|
ipython~=7.19.0
|
||||||
python-magic==0.4.18
|
python-magic>=0.4.22
|
||||||
requests~=2.25.0
|
requests~=2.25.1
|
||||||
sympasoap~=1.0
|
sympasoap~=1.0
|
||||||
whoosh~=2.7
|
whoosh~=2.7
|
@@ -8,10 +8,10 @@
|
|||||||
0 * * * * cd /code && python manage.py update_index -v 0
|
0 * * * * cd /code && python manage.py update_index -v 0
|
||||||
|
|
||||||
# Recreate sympa lists
|
# Recreate sympa lists
|
||||||
*/6 * * * * cd /code && python manage.py fix_sympa_lists &> /dev/null
|
*/2 * * * * cd /code && python manage.py fix_sympa_lists &> /dev/null
|
||||||
|
|
||||||
# Update matrix channels
|
# Update matrix channels
|
||||||
*/6 * * * * cd /code && python manage.py fix_matrix_channels &> /dev/null
|
03 */6 * * * cd /code && python manage.py fix_matrix_channels &> /dev/null
|
||||||
|
|
||||||
# Check payments from Hello Asso
|
# Check payments from Hello Asso
|
||||||
*/6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null
|
*/6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null
|
||||||
|
@@ -4,8 +4,6 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
|
|
||||||
|
|
||||||
class Matrix:
|
class Matrix:
|
||||||
"""
|
"""
|
||||||
@@ -51,7 +49,6 @@ class Matrix:
|
|||||||
return client
|
return client
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def set_display_name(cls, name: str):
|
async def set_display_name(cls, name: str):
|
||||||
"""
|
"""
|
||||||
Set the display name of the bot account.
|
Set the display name of the bot account.
|
||||||
@@ -60,7 +57,6 @@ class Matrix:
|
|||||||
return await client.set_displayname(name)
|
return await client.set_displayname(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def set_avatar(cls, avatar_url: str): # pragma: no cover
|
async def set_avatar(cls, avatar_url: str): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Set the display avatar of the bot account.
|
Set the display avatar of the bot account.
|
||||||
@@ -69,7 +65,6 @@ class Matrix:
|
|||||||
return await client.set_avatar(avatar_url)
|
return await client.set_avatar(avatar_url)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def get_avatar(cls): # pragma: no cover
|
async def get_avatar(cls): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Set the display avatar of the bot account.
|
Set the display avatar of the bot account.
|
||||||
@@ -79,7 +74,6 @@ class Matrix:
|
|||||||
return resp.avatar_url if hasattr(resp, "avatar_url") else resp
|
return resp.avatar_url if hasattr(resp, "avatar_url") else resp
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def upload(
|
async def upload(
|
||||||
cls,
|
cls,
|
||||||
data_provider,
|
data_provider,
|
||||||
@@ -146,7 +140,6 @@ class Matrix:
|
|||||||
if not isinstance(client, FakeMatrixClient) else None, None
|
if not isinstance(client, FakeMatrixClient) else None, None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def create_room(
|
async def create_room(
|
||||||
cls,
|
cls,
|
||||||
visibility=None,
|
visibility=None,
|
||||||
@@ -232,7 +225,6 @@ class Matrix:
|
|||||||
return resp.room_id if resp and hasattr(resp, "room_id") else None
|
return resp.room_id if resp and hasattr(resp, "room_id") else None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def invite(cls, room_id: str, user_id: str):
|
async def invite(cls, room_id: str, user_id: str):
|
||||||
"""
|
"""
|
||||||
Invite a user to a room.
|
Invite a user to a room.
|
||||||
@@ -251,7 +243,6 @@ class Matrix:
|
|||||||
return await client.room_invite(room_id, user_id)
|
return await client.room_invite(room_id, user_id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def send_message(cls, room_id: str, body: str, formatted_body: str = None,
|
async def send_message(cls, room_id: str, body: str, formatted_body: str = None,
|
||||||
msgtype: str = "m.text", html: bool = True):
|
msgtype: str = "m.text", html: bool = True):
|
||||||
"""
|
"""
|
||||||
@@ -274,7 +265,6 @@ class Matrix:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def add_integration(cls, room_id: str, widget_url: str, state_key: str,
|
async def add_integration(cls, room_id: str, widget_url: str, state_key: str,
|
||||||
widget_type: str = "customwidget", widget_name: str = "Custom widget",
|
widget_type: str = "customwidget", widget_name: str = "Custom widget",
|
||||||
widget_title: str = ""):
|
widget_title: str = ""):
|
||||||
@@ -301,7 +291,6 @@ class Matrix:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def remove_integration(cls, room_id: str, state_key: str):
|
async def remove_integration(cls, room_id: str, state_key: str):
|
||||||
client = await cls._get_client()
|
client = await cls._get_client()
|
||||||
if room_id.startswith("#"):
|
if room_id.startswith("#"):
|
||||||
@@ -314,7 +303,6 @@ class Matrix:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def kick(cls, room_id: str, user_id: str, reason: str = None):
|
async def kick(cls, room_id: str, user_id: str, reason: str = None):
|
||||||
"""
|
"""
|
||||||
Kick a user from a room, or withdraw their invitation.
|
Kick a user from a room, or withdraw their invitation.
|
||||||
@@ -337,7 +325,6 @@ class Matrix:
|
|||||||
return await client.room_kick(room_id, user_id, reason)
|
return await client.room_kick(room_id, user_id, reason)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def set_room_power_level(cls, room_id: str, user_id: str, power_level: int): # pragma: no cover
|
async def set_room_power_level(cls, room_id: str, user_id: str, power_level: int): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Put a given power level to a user in a certain room.
|
Put a given power level to a user in a certain room.
|
||||||
@@ -364,7 +351,6 @@ class Matrix:
|
|||||||
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def set_room_power_level_event(cls, room_id: str, event: str, power_level: int): # pragma: no cover
|
async def set_room_power_level_event(cls, room_id: str, event: str, power_level: int): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Define the minimal power level to have to send a certain event type
|
Define the minimal power level to have to send a certain event type
|
||||||
@@ -395,7 +381,6 @@ class Matrix:
|
|||||||
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_to_sync
|
|
||||||
async def set_room_avatar(cls, room_id: str, avatar_uri: str):
|
async def set_room_avatar(cls, room_id: str, avatar_uri: str):
|
||||||
"""
|
"""
|
||||||
Define the avatar of a room.
|
Define the avatar of a room.
|
||||||
|
@@ -190,6 +190,8 @@ MEDIA_URL = '/media/'
|
|||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
||||||
|
|
||||||
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html'
|
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html'
|
||||||
|
7
tfjm/static/bootstrap/css/bootstrap.min.css
vendored
Normal file
7
tfjm/static/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
tfjm/static/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
7
tfjm/static/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
tfjm/static/fontawasome/css/all.css
Normal file
5
tfjm/static/fontawasome/css/all.css
Normal file
File diff suppressed because one or more lines are too long
5
tfjm/static/fontawasome/css/v4-shims.css
Normal file
5
tfjm/static/fontawasome/css/v4-shims.css
Normal file
File diff suppressed because one or more lines are too long
BIN
tfjm/static/fontawasome/webfonts/fa-brands-400.woff
Normal file
BIN
tfjm/static/fontawasome/webfonts/fa-brands-400.woff
Normal file
Binary file not shown.
BIN
tfjm/static/fontawasome/webfonts/fa-brands-400.woff2
Normal file
BIN
tfjm/static/fontawasome/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
tfjm/static/fontawasome/webfonts/fa-solid-900.woff
Normal file
BIN
tfjm/static/fontawasome/webfonts/fa-solid-900.woff
Normal file
Binary file not shown.
BIN
tfjm/static/fontawasome/webfonts/fa-solid-900.woff2
Normal file
BIN
tfjm/static/fontawasome/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
2
tfjm/static/jquery/jquery-3.6.0.min.js
vendored
Normal file
2
tfjm/static/jquery/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
tfjm/static/turbolinks/turbolinks.js
Normal file
6
tfjm/static/turbolinks/turbolinks.js
Normal file
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="text-justify">
|
<div class="text-justify">
|
||||||
<p>
|
<p>
|
||||||
La plateforme d'inscription du TFJM² a été développée entre 2019 et 2021
|
La plateforme d'inscription du TFJM² a été développée entre 2019 et 2022
|
||||||
par Yohann D'ANELLO, bénévole pour l'association Animath. Elle est vouée à être utilisée par les participants
|
par Yohann D'ANELLO, bénévole pour l'association Animath. Elle est vouée à être utilisée par les participants
|
||||||
pour intéragir avec les organisateurs et les autres participants.
|
pour intéragir avec les organisateurs et les autres participants.
|
||||||
</p>
|
</p>
|
||||||
|
@@ -19,17 +19,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Bootstrap CSS #}
|
{# Bootstrap CSS #}
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
|
||||||
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
|
<link rel="stylesheet" href="{% static 'fontawasome/css/all.css' %}">
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css">
|
<link rel="stylesheet" href="{% static 'fontawasome/css/v4-shims.css' %}">
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css">
|
|
||||||
|
|
||||||
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" crossorigin="anonymous"></script>
|
<script src="{% static 'jquery/jquery-3.6.0.min.js' %}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
|
<script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||||
integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
|
<script src="{% static 'turbolinks/turbolinks.js' %}"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
||||||
{% if form.media %}
|
{% if form.media %}
|
||||||
|
@@ -3,18 +3,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-success">
|
||||||
<p>
|
<p>
|
||||||
Certains d'entre vous rencontrent des difficultés lors de l'inscription. Quelques points :
|
Les inscriptions sont à présent ouvertes, vous pouvez créer votre compte. Prenez garde toutefois
|
||||||
<ul>
|
aux dates indiquées qui sont pour l'instant provisoires.
|
||||||
<li>Tout d'abord, il est inutile de créer plusieurs comptes.</li>
|
|
||||||
<li>Si vous ne recevez pas le mail de validation de l'adresse mail, vous pouvez quand même vous connecter.
|
|
||||||
Vous pourrez via un lien redemander un mail. Merci de ne pas spammer ce bouton toutefois. Pensez à vérifier
|
|
||||||
vos spams. Contactez-nous si vous ne recevez toujours rien.</li>
|
|
||||||
<li>Les différentes autorisations sont à soumettre sur la page de votre compte.</li>
|
|
||||||
<li>La date limite d'inscription est fixée en général à un mois avant le début du tournoi, soit début mars.
|
|
||||||
Le site <a href="https://tfjm.org/">https://tfjm.org/</a> sera mis à jour dans les jours qui suivent.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Une documentation plus complète sera disponible dans les jours à venir et régulièrement mise à jour pour apprendre à utiliser la plateforme.
|
Une documentation plus complète sera disponible dans les jours à venir et régulièrement mise à jour pour apprendre à utiliser la plateforme.
|
||||||
@@ -45,11 +37,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<h4><strong><i class="fas fa-newspaper"></i> Grande nouveauté :</strong></h4>
|
<h4><strong><i class="fas fa-newspaper"></i> Informations 2022 :</strong></h4>
|
||||||
Cette année, après avoir appris de l'édition complètement à distance de 2020, la communication se simplifie :
|
<p>
|
||||||
retrouvez-nous sur Element, disponible sur tous vos appareils, ou sur votre navigateur à l'adresse
|
Après 2 ans de pandémie, le tournoi devrait (enfin !) revenir en présentiel. Selon les mesures
|
||||||
<a class="alert-link" href="https://element.tfjm.org">element.tfjm.org</a>
|
gouvernementales en vigueur au moment du tournoi et peut-être même selon le lieu d'accueil du tournoi,
|
||||||
(connectez-vous avec les identifiants de cette plateforme une fois inscrits)
|
le pass sanitaire ou vaccinal pourra être requis.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
La plateforme <a class="alert-link" href="https://element.tfjm.org">element.tfjm.org</a> reste ouverte
|
||||||
|
cette année pour faciliter d'éventuelles communications. C'est ici notamment que se déroulera le tirage
|
||||||
|
au sort. Nous vous invitons à tenter de vous connecter sur la plateforme une fois votre compte créé.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from haystack.generic_views import SearchView
|
from haystack.generic_views import SearchView
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +31,11 @@ class UserMixin(LoginRequiredMixin):
|
|||||||
class UserRegistrationMixin(LoginRequiredMixin):
|
class UserRegistrationMixin(LoginRequiredMixin):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
user = request.user
|
user = request.user
|
||||||
if user.is_authenticated and not user.registration.is_admin and user.registration.pk != kwargs["pk"]:
|
user_object = User.objects.get(registration__pk=kwargs["pk"])
|
||||||
|
if user.is_authenticated and not user.registration.is_admin and user.registration.pk != kwargs["pk"] and \
|
||||||
|
not (user.registration.is_volunteer and user_object.registration.team is not None
|
||||||
|
and user_object.registration.team.participation.tournament
|
||||||
|
in user.registration.organized_tournaments.all()):
|
||||||
self.handle_no_permission()
|
self.handle_no_permission()
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
15
tox.ini
15
tox.ini
@@ -1,7 +1,8 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py38
|
|
||||||
py39
|
py39
|
||||||
|
py310
|
||||||
|
py311
|
||||||
|
|
||||||
linters
|
linters
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
@@ -10,20 +11,20 @@ skipsdist = True
|
|||||||
sitepackages = False
|
sitepackages = False
|
||||||
deps =
|
deps =
|
||||||
coverage
|
coverage
|
||||||
Django~=3.1
|
Django>=3.2,<4.0
|
||||||
django-address~=0.2
|
django-address~=0.2
|
||||||
django-bootstrap-datepicker-plus~=3.0
|
django-bootstrap-datepicker-plus~=4.0
|
||||||
django-crispy-forms~=1.9
|
django-crispy-forms~=1.9
|
||||||
django-filter~=2.3
|
django-filter~=2.4
|
||||||
django-haystack~=3.0
|
django-haystack~=3.0
|
||||||
django-phonenumber-field~=5.0.0
|
django-phonenumber-field~=5.0.0
|
||||||
django-polymorphic~=3.0
|
django-polymorphic~=3.0
|
||||||
django-tables2~=2.3
|
django-tables2~=2.4
|
||||||
djangorestframework~=3.12
|
djangorestframework~=3.12
|
||||||
django-rest-polymorphic~=0.1
|
django-rest-polymorphic~=0.1
|
||||||
phonenumbers~=8.9.10
|
phonenumbers~=8.9.10
|
||||||
PyPDF3~=1.0.2
|
PyPDF3~=1.0.2
|
||||||
python-magic==0.4.18
|
python-magic==0.4.22
|
||||||
whoosh~=2.7
|
whoosh~=2.7
|
||||||
commands =
|
commands =
|
||||||
coverage run --source=apps,tfjm ./manage.py test apps/ tfjm/
|
coverage run --source=apps,tfjm ./manage.py test apps/ tfjm/
|
||||||
@@ -57,4 +58,4 @@ max-complexity = 10
|
|||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
import-order-style = google
|
import-order-style = google
|
||||||
application-import-names = flake8
|
application-import-names = flake8
|
||||||
format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
#format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
||||||
|
Reference in New Issue
Block a user