1
0
mirror of https://gitlab.com/animath/si/plateforme-corres2math.git synced 2025-07-04 06:12:12 +02:00

Compare commits

..

25 Commits

Author SHA1 Message Date
4c9e63be3d Merge branch 'improvements' into 'master'
Questions can be seen during the third phase

See merge request animath/si/plateforme-corres2math!11
2021-01-08 12:42:44 +00:00
35a197be07 Questions can be seen during the third phase 2021-01-08 13:37:14 +01:00
01f6d74cae Merge branch 'improvements' into 'master'
Improvements

See merge request animath/si/plateforme-corres2math!10
2021-01-01 15:00:33 +00:00
d55671efd1 Use warning background for teams that didn't sent their video 2021-01-01 15:54:50 +01:00
bcdbc445c6 Fix the fix sympa lists script 2021-01-01 15:41:23 +01:00
95a69f35d2 A user can have no team 2020-12-28 11:52:34 +01:00
eec941c816 Admins can change email validation status 2020-12-27 21:19:09 +01:00
64d471936f Add copyright information 2020-12-26 21:26:26 +01:00
02c977264d Pending teams can change their composition 2020-12-26 19:43:46 +01:00
8f86ea15c8 Pending teams can change their composition 2020-12-26 19:24:37 +01:00
7628387158 Fix the fix of the sympa lists 2020-12-24 00:43:07 +01:00
a82c6b4673 Merge branch 'improvements' into 'master'
django-cas-server does not support Django 3.1 and Python 3.9 yet

See merge request animath/si/plateforme-corres2math!9
2020-12-22 22:29:51 +00:00
bf2feb9c35 django-cas-server does not support Django 3.1 and Python 3.9 yet 2020-12-22 23:24:15 +01:00
4ee5ac309d Merge branch 'improvements' into 'master'
Improvements, bug fixes

See merge request animath/si/plateforme-corres2math!8
2020-12-22 20:36:57 +00:00
969e53b712 Linting 2020-12-22 21:31:22 +01:00
84e149e8c9 Add about page 2020-12-22 21:30:34 +01:00
9e6a3eb1ca Add Gitlab logo 2020-12-22 21:06:11 +01:00
1f9f60d880 Unit test for large files uploading 2020-12-22 20:57:03 +01:00
205760f2e9 More protection on pages that require authentication 2020-12-22 20:48:25 +01:00
8f742b8e14 Increase nginx upload limit to have a better message in the Django form 2020-12-22 20:40:01 +01:00
996d00c7f0 Don't send too large files 2020-12-22 20:20:35 +01:00
24e1cf3845 Merge branch 'improvements' into 'master'
Improvements

See merge request animath/si/plateforme-corres2math!7
2020-12-11 13:11:23 +00:00
91640f8fb1 Merge branch 'improvements' into 'master'
Fix signup

See merge request animath/si/plateforme-corres2math!6
2020-12-04 00:49:30 +00:00
ce048a30d6 Invite people in Matrix rooms at registration 2020-11-16 19:07:18 +01:00
d104c2ff1f Merge branch 'improvements' into 'master'
Improvements

See merge request animath/si/plateforme-corres2math!5
2020-11-16 11:06:10 +00:00
67 changed files with 372 additions and 93 deletions

View File

@ -1,4 +1,4 @@
FROM python:3-alpine FROM python:3.8-alpine
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV DJANGO_ALLOW_ASYNC_UNSAFE 1 ENV DJANGO_ALLOW_ASYNC_UNSAFE 1

View File

@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author> Copyright (C) 2020 Animath
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author> <program> Copyright (C) 2020 Animath
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.

View File

@ -1 +1,4 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'api.apps.APIConfig' default_app_config = 'api.apps.APIConfig'

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework import serializers from rest_framework import serializers

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from unittest.case import skipIf from unittest.case import skipIf
from django.conf import settings from django.conf import settings

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from rest_framework import routers from rest_framework import routers

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter

View File

@ -1 +1,4 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'eastereggs.apps.EastereggsConfig' default_app_config = 'eastereggs.apps.EastereggsConfig'

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path
from django.views.generic import TemplateView from django.views.generic import TemplateView

View File

@ -0,0 +1,2 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1 +1,4 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'participation.apps.ParticipationConfig' default_app_config = 'participation.apps.ParticipationConfig'

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
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 _

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_save, pre_delete, pre_save from django.db.models.signals import post_save, pre_delete, pre_save

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import re import re
from bootstrap_datepicker_plus import DateTimePickerInput from bootstrap_datepicker_plus import DateTimePickerInput

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
@ -85,5 +88,7 @@ class Command(BaseCommand):
f"@{admin.matrix_username}:correspondances-maths.fr", 95) f"@{admin.matrix_username}:correspondances-maths.fr", 95)
Matrix.set_room_power_level("#faq:correspondances-maths.fr", Matrix.set_room_power_level("#faq:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95) f"@{admin.matrix_username}:correspondances-maths.fr", 95)
Matrix.set_room_power_level("#je-cherche-une-equipe:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95)
Matrix.set_room_power_level("#flood:correspondances-maths.fr", Matrix.set_room_power_level("#flood:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95) f"@{admin.matrix_username}:correspondances-maths.fr", 95)

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from corres2math.lists import get_sympa_client from corres2math.lists import get_sympa_client
from django.core.management import BaseCommand from django.core.management import BaseCommand
from django.db.models import Q from django.db.models import Q
@ -26,13 +29,15 @@ class Command(BaseCommand):
f" des Correspondances.", "education", raise_error=False) f" des Correspondances.", "education", raise_error=False)
for team in Team.objects.filter(participation__valid=True).all(): for team in Team.objects.filter(participation__valid=True).all():
sympa.subscribe(team.email, "equipes", f"Equipe {team.name}", True, True) team.create_mailing_list()
sympa.subscribe(team.email, "equipes", f"Equipe {team.name}", True)
sympa.subscribe(team.email, f"probleme-{team.participation.problem}", f"Equipe {team.name}", True) sympa.subscribe(team.email, f"probleme-{team.participation.problem}", f"Equipe {team.name}", True)
for team in Team.objects.filter(Q(participation__valid=False) | Q(participation__valid__isnull=True)).all(): for team in Team.objects.filter(Q(participation__valid=False) | Q(participation__valid__isnull=True)).all():
team.create_mailing_list()
sympa.subscribe(team.email, "equipes-non-valides", f"Equipe {team.name}", True) sympa.subscribe(team.email, "equipes-non-valides", f"Equipe {team.name}", True)
for student in StudentRegistration.objects.filter(team__isnull=False).all(): for student in StudentRegistration.objects.filter(team__isnull=False).all():
sympa.subscribe(student.user.email, f"equipe-{student.team.trigram.lower}", True, f"{student}") sympa.subscribe(student.user.email, f"equipe-{student.team.trigram.lower()}", True, f"{student}")
for coach in CoachRegistration.objects.filter(team__isnull=False).all(): for coach in CoachRegistration.objects.filter(team__isnull=False).all():
sympa.subscribe(coach.user.email, f"equipe-{coach.team.trigram.lower}", True, f"{coach}") sympa.subscribe(coach.user.email, f"equipe-{coach.team.trigram.lower()}", True, f"{coach}")

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from corres2math.matrix import Matrix, RoomVisibility from corres2math.matrix import Matrix, RoomVisibility
from django.core.management import BaseCommand from django.core.management import BaseCommand
from participation.models import Participation from participation.models import Participation

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
import re import re
@ -285,7 +288,7 @@ class Phase(models.Model):
) )
@classmethod @classmethod
def current_phase(cls): def current_phase(cls) -> "Phase":
""" """
Retrieve the current phase of this day Retrieve the current phase of this day
""" """

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from haystack import indexes from haystack import indexes
from .models import Participation, Team, Video from .models import Participation, Team, Video

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from corres2math.lists import get_sympa_client from corres2math.lists import get_sympa_client
from participation.models import Participation, Team, Video from participation.models import Participation, Team, Video

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
@ -39,6 +42,9 @@ class TeamTable(tables.Table):
attrs = { attrs = {
'class': 'table table condensed table-striped', 'class': 'table table condensed table-striped',
} }
row_attrs = {
'class': lambda record: '' if record.participation.solution.link else 'bg-warning',
}
model = Team model = Team
fields = ('name', 'trigram', 'problem',) fields = ('name', 'trigram', 'problem',)
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'

View File

@ -194,13 +194,14 @@
{% trans "This video platform is not supported yet." as unsupported_platform %} {% trans "This video platform is not supported yet." as unsupported_platform %}
{% include "base_modal.html" with modal_id="displayOtherSolution" modal_action="" modal_button="" modal_additional_class="modal-lg" modal_content=participation.received_participation.solution.as_iframe|default:unsupported_platform %} {% include "base_modal.html" with modal_id="displayOtherSolution" modal_action="" modal_button="" modal_additional_class="modal-lg" modal_content=participation.received_participation.solution.as_iframe|default:unsupported_platform %}
{% endif %} {% endif %}
{% endif %}
{% if user.registration.participates and current_phase.phase_number == 2 %} {% if current_phase.phase_number == 2 %}
{% trans "Add question" as modal_title %} {% trans "Add question" as modal_title %}
{% trans "Add" as modal_button %} {% trans "Add" as modal_button %}
{% url "participation:add_question" pk=participation.pk as modal_action %} {% url "participation:add_question" pk=participation.pk as modal_action %}
{% include "base_modal.html" with modal_id="addQuestion" modal_button_type="success" %} {% include "base_modal.html" with modal_id="addQuestion" modal_button_type="success" %}
{% endif %}
{% for question in participation.questions.all %} {% for question in participation.questions.all %}
{% with number_str=forloop.counter|stringformat:"d"%} {% with number_str=forloop.counter|stringformat:"d"%}
{% with modal_id="updateQuestion"|add:number_str %} {% with modal_id="updateQuestion"|add:number_str %}
@ -260,27 +261,29 @@
}); });
{% endif %} {% endif %}
{% if user.registration.participates and current_phase.phase_number == 2 %} {% if current_phase.phase_number == 2 %}
$('button[data-target="#addQuestionModal"]').click(function() { $('button[data-target="#addQuestionModal"]').click(function() {
let modalBody = $("#addQuestionModal div.modal-body"); let modalBody = $("#addQuestionModal div.modal-body");
if (!modalBody.html().trim()) if (!modalBody.html().trim())
modalBody.load("{% url "participation:add_question" pk=participation.pk %} #form-content"); modalBody.load("{% url "participation:add_question" pk=participation.pk %} #form-content");
}); });
{% endif %}
{% for question in participation.questions.all %} {% for question in participation.questions.all %}
$('button[data-target="#updateQuestion{{ forloop.counter }}Modal"]').click(function() { $('button[data-target="#updateQuestion{{ forloop.counter }}Modal"]').click(function() {
let modalBody = $("#updateQuestion{{ forloop.counter }}Modal div.modal-body"); let modalBody = $("#updateQuestion{{ forloop.counter }}Modal div.modal-body");
if (!modalBody.html().trim()) if (!modalBody.html().trim())
modalBody.load("{% url "participation:update_question" pk=question.pk %} #form-content"); modalBody.load("{% url "participation:update_question" pk=question.pk %} #form-content");
}); });
{% if current_phase.phase_number == 2 %}
$('button[data-target="#deleteQuestion{{ forloop.counter }}Modal"]').click(function() { $('button[data-target="#deleteQuestion{{ forloop.counter }}Modal"]').click(function() {
let modalBody = $("#deleteQuestion{{ forloop.counter }}Modal div.modal-body"); let modalBody = $("#deleteQuestion{{ forloop.counter }}Modal div.modal-body");
if (!modalBody.html().trim()) if (!modalBody.html().trim())
modalBody.load("{% url "participation:delete_question" pk=question.pk %} #form-content"); modalBody.load("{% url "participation:delete_question" pk=question.pk %} #form-content");
}); });
{% endfor %} {% endif %}
{% endif %} {% endfor %}
$('button[data-target="#uploadSolutionModal"]').click(function() { $('button[data-target="#uploadSolutionModal"]').click(function() {
let modalBody = $("#uploadSolutionModal div.modal-body"); let modalBody = $("#uploadSolutionModal div.modal-body");

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django import template from django import template
from ..models import Phase from ..models import Phase

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -409,12 +412,6 @@ class TestStudentParticipation(TestCase):
self.user.registration.team = self.team self.user.registration.team = self.team
self.user.registration.save() self.user.registration.save()
# Team is pending validation
self.team.participation.valid = False
self.team.participation.save()
response = self.client.post(reverse("participation:team_leave"))
self.assertEqual(response.status_code, 403)
# Team is valid # Team is valid
self.team.participation.valid = True self.team.participation.valid = True
self.team.participation.save() self.team.participation.save()
@ -669,7 +666,7 @@ class TestStudentParticipation(TestCase):
def test_forbidden_access(self): def test_forbidden_access(self):
""" """
Load personnal pages and ensure that these are protected. Load personal pages and ensure that these are protected.
""" """
self.user.registration.team = self.team self.user.registration.team = self.team
self.user.registration.save() self.user.registration.save()

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path
from django.views.generic import TemplateView from django.views.generic import TemplateView

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from io import BytesIO from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
@ -39,6 +42,8 @@ class CreateTeamView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
registration = user.registration registration = user.registration
if not registration.participates: if not registration.participates:
raise PermissionDenied(_("You don't participate, so you can't create a team.")) raise PermissionDenied(_("You don't participate, so you can't create a team."))
@ -85,6 +90,8 @@ class JoinTeamView(LoginRequiredMixin, FormView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
registration = user.registration registration = user.registration
if not registration.participates: if not registration.participates:
raise PermissionDenied(_("You don't participate, so you can't create a team.")) raise PermissionDenied(_("You don't participate, so you can't create a team."))
@ -265,6 +272,8 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
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 and \
user.registration.team.pk == kwargs["pk"]: user.registration.team.pk == kwargs["pk"]:
@ -299,6 +308,8 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
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.participates and user.registration.team.pk == kwargs["pk"]:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
@ -333,7 +344,7 @@ class TeamLeaveView(LoginRequiredMixin, TemplateView):
return self.handle_no_permission() return self.handle_no_permission()
if not request.user.registration.participates or not request.user.registration.team: if not request.user.registration.participates or not request.user.registration.team:
raise PermissionDenied(_("You are not in a team.")) raise PermissionDenied(_("You are not in a team."))
if request.user.registration.team.participation.valid is not None: if request.user.registration.team.participation.valid:
raise PermissionDenied(_("The team is already validated or the validation is pending.")) raise PermissionDenied(_("The team is already validated or the validation is pending."))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -377,6 +388,8 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
if not self.get_object().valid: if not self.get_object().valid:
raise PermissionDenied(_("The team is not validated yet.")) raise PermissionDenied(_("The team is not validated yet."))
if user.registration.is_admin or user.registration.participates \ if user.registration.is_admin or user.registration.participates \
@ -464,6 +477,12 @@ class UpdateQuestionView(LoginRequiredMixin, UpdateView):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
def form_valid(self, form):
if not self.request.user.registration.is_admin and Phase.current_phase().phase_number != 2:
form.add_error(None, _("You can update your questions now."))
return self.form_invalid(form)
return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,)) return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,))
@ -486,6 +505,11 @@ class DeleteQuestionView(LoginRequiredMixin, DeleteView):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
def delete(self, request, *args, **kwargs):
if not request.user.registration.is_admin and Phase.current_phase().phase_number != 2:
raise PermissionDenied(_("You can update your questions now."))
return super().delete(request, *args, **kwargs)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,)) return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,))
@ -501,6 +525,8 @@ class UploadVideoView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
user = request.user user = request.user
if not user.is_authenticated:
return super().handle_no_permission()
if user.registration.is_admin or user.registration.participates \ if user.registration.is_admin or user.registration.participates \
and user.registration.team.participation.pk == self.get_object().participation.pk: and user.registration.team.participation.pk == self.get_object().participation.pk:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)

View File

@ -1 +1,4 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'registration.apps.RegistrationConfig' default_app_config = 'registration.apps.RegistrationConfig'

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_save, pre_save from django.db.models.signals import post_save, pre_save

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from cas_server.auth import DjangoAuthUser # pragma: no cover from cas_server.auth import DjangoAuthUser # pragma: no cover

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -72,10 +75,13 @@ class PhotoAuthorizationForm(forms.ModelForm):
Form to send a photo authorization. Form to send a photo authorization.
""" """
def clean_photo_authorization(self): def clean_photo_authorization(self):
file = self.files["photo_authorization"] if "photo_authorization" in self.files:
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]: file = self.files["photo_authorization"]
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file.")) if file.size > 2e6:
return self.cleaned_data["photo_authorization"] raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
return self.cleaned_data["photo_authorization"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -50,7 +50,7 @@ class Migration(migrations.Migration):
('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')), ('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')),
('school', models.CharField(max_length=255, verbose_name='school')), ('school', models.CharField(max_length=255, verbose_name='school')),
('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_filename, verbose_name='photo authorization')), ('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_filename, verbose_name='photo authorization')),
('team', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='students', to='participation.team', verbose_name='team')), ('team', models.ForeignKey(default=None, blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='students', to='participation.team', verbose_name='team')),
], ],
options={ options={
'verbose_name': 'student registration', 'verbose_name': 'student registration',
@ -63,7 +63,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')), ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
('professional_activity', models.TextField(verbose_name='professional activity')), ('professional_activity', models.TextField(verbose_name='professional activity')),
('team', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='coachs', to='participation.team', verbose_name='team')), ('team', models.ForeignKey(default=None, blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='coachs', to='participation.team', verbose_name='team')),
], ],
options={ options={
'verbose_name': 'coach registration', 'verbose_name': 'coach registration',

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from corres2math.tokens import email_validation_token from corres2math.tokens import email_validation_token
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import models from django.db import models
@ -102,6 +105,7 @@ class StudentRegistration(Registration):
related_name="students", related_name="students",
on_delete=models.PROTECT, on_delete=models.PROTECT,
null=True, null=True,
blank=True,
default=None, default=None,
verbose_name=_("team"), verbose_name=_("team"),
) )
@ -151,6 +155,7 @@ class CoachRegistration(Registration):
related_name="coachs", related_name="coachs",
on_delete=models.PROTECT, on_delete=models.PROTECT,
null=True, null=True,
blank=True,
default=None, default=None,
verbose_name=_("team"), verbose_name=_("team"),
) )

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from haystack import indexes from haystack import indexes
from .models import Registration from .models import Registration

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from corres2math.lists import get_sympa_client from corres2math.lists import get_sympa_client
from corres2math.matrix import Matrix from corres2math.matrix import Matrix
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -45,9 +48,9 @@ def invite_to_public_rooms(instance: Registration, created: bool, **_):
""" """
When a user got registered, automatically invite the Matrix user into public rooms. When a user got registered, automatically invite the Matrix user into public rooms.
""" """
if not created: if created:
Matrix.invite("#annonces:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr") Matrix.invite("#annonces:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr")
Matrix.invite("#faq:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr") Matrix.invite("#faq:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr")
Matrix.invite("#je-cherche-une-equip:correspondances-maths.fr", Matrix.invite("#je-cherche-une-equipe:correspondances-maths.fr",
f"@{instance.matrix_username}:correspondances-maths.fr") f"@{instance.matrix_username}:correspondances-maths.fr")
Matrix.invite("#flood:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr") Matrix.invite("#flood:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr")

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django import template from django import template
from django_tables2 import Table from django_tables2 import Table
from participation.models import Participation, Team, Video from participation.models import Participation, Team, Video

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta from datetime import timedelta
import os import os
@ -5,13 +8,14 @@ from corres2math.tokens import email_validation_token
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management import call_command from django.core.management import call_command
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
from participation.models import Phase from participation.models import Phase, Team
from .models import AdminRegistration, CoachRegistration, StudentRegistration from .models import AdminRegistration, CoachRegistration, StudentRegistration
@ -35,6 +39,24 @@ class TestIndexPage(TestCase):
response = self.client.get(reverse("registration:user_detail", args=(1,))) response = self.client.get(reverse("registration:user_detail", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:user_detail", args=(1,))) self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:user_detail", args=(1,)))
Team.objects.create()
response = self.client.get(reverse("participation:team_detail", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:team_detail", args=(1,)))
response = self.client.get(reverse("participation:update_team", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:update_team", args=(1,)))
response = self.client.get(reverse("participation:create_team"))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:create_team"))
response = self.client.get(reverse("participation:join_team"))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:join_team"))
response = self.client.get(reverse("participation:team_authorizations", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next="
+ reverse("participation:team_authorizations", args=(1,)))
response = self.client.get(reverse("participation:participation_detail", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next="
+ reverse("participation:participation_detail", args=(1,)))
response = self.client.get(reverse("participation:upload_video", args=(1,)))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:upload_video", args=(1,)))
class TestRegistration(TestCase): class TestRegistration(TestCase):
def setUp(self) -> None: def setUp(self) -> None:
@ -268,6 +290,14 @@ class TestRegistration(TestCase):
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# Don't send too large files
response = self.client.post(reverse("registration:upload_user_photo_authorization",
args=(self.student.registration.pk,)), data=dict(
photo_authorization=SimpleUploadedFile("file.pdf", content=int(0).to_bytes(2000001, "big"),
content_type="application/pdf"),
))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse("registration:upload_user_photo_authorization", response = self.client.post(reverse("registration:upload_user_photo_authorization",
args=(self.student.registration.pk,)), data=dict( args=(self.student.registration.pk,)), data=dict(
photo_authorization=open("corres2math/static/Autorisation de droit à l'image - majeur.pdf", "rb"), photo_authorization=open("corres2math/static/Autorisation de droit à l'image - majeur.pdf", "rb"),

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path from django.urls import path
from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \ from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from corres2math.tokens import email_validation_token from corres2math.tokens import email_validation_token
@ -210,7 +213,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
context["title"] = _("Update user {user}").format(user=str(self.object.registration)) context["title"] = _("Update user {user}").format(user=str(self.object.registration))
context["registration_form"] = user.registration.form_class(data=self.request.POST or None, context["registration_form"] = user.registration.form_class(data=self.request.POST or None,
instance=self.object.registration) instance=self.object.registration)
if not user.registration.is_admin: if not self.request.user.registration.is_admin:
if "team" in context["registration_form"].fields: if "team" in context["registration_form"].fields:
del context["registration_form"].fields["team"] del context["registration_form"].fields["team"]
del context["registration_form"].fields["email_confirmed"] del context["registration_form"].fields["email_confirmed"]
@ -221,7 +224,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
user = form.instance user = form.instance
registration_form = user.registration.form_class(data=self.request.POST or None, registration_form = user.registration.form_class(data=self.request.POST or None,
instance=self.object.registration) instance=self.object.registration)
if not user.registration.is_admin: if not self.request.user.registration.is_admin:
if "team" in registration_form.fields: if "team" in registration_form.fields:
del registration_form.fields["team"] del registration_form.fields["team"]
del registration_form.fields["email_confirmed"] del registration_form.fields["email_confirmed"]

View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
""" """
ASGI config for corres2math project. ASGI config for corres2math project.

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
_client = None _client = None

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from enum import Enum from enum import Enum
import os import os

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from threading import local from threading import local
from django.conf import settings from django.conf import settings

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
""" """
Django settings for corres2math project. Django settings for corres2math project.

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
# Break it, fix it! # Break it, fix it!

View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block contenttitle %}
<h1>À propos</h1>
{% endblock %}
{% block content %}
<p>
La plateforme d'inscription des Correspondances des Jeunes Mathématiciennes a été développée entre 2019 et 2021
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.
</p>
<p>
La plateforme est développée avec le framework <a href="https://www.djangoproject.com/">Django</a> et le code
source est accessible librement sur <a href="https://gitlab.com/animath/si/plateforme-corres2math">Gitlab</a>.
Le code est distribué sous la licence <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPL v3</a>,
qui vous autorise à consulter le code, à le partager, à réutiliser des parties du code et à contribuer.
</p>
<p>
Le site principal présent sur <a href="https://inscription.correspondances-maths.fr/">https://inscription.correspondances-maths.fr</a>
est hébergé chez <a href="https://www.scaleway.com/fr/">Scaleway</a>.
</p>
<p>
Les données collectées par cette plateforme sont utilisées uniquement dans le cadre des Correspondances et sont
détruites dès l'action touche à sa fin, soit au plus tard 1 an après le début de l'action. Sur autorisation
explicite, des informations de contact peuvent être conservées afin d'être tenu au courant des actions futures
de l'association Animath. Aucune information personnelle n'est collectée à votre insu. Aucune information
personnelle n'est cédée à des tiers.
</p>
<p>
Pour toute demande ou réclammation, merci de nous contacter à l'adresse
<a target="_blank" href="mailto:&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;">
&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;
</a>.
</p>
{% endblock %}

View File

@ -192,7 +192,7 @@
class="form-inline"> class="form-inline">
<span class="text-muted mr-1"> <span class="text-muted mr-1">
<a target="_blank" href="mailto:&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;" <a target="_blank" href="mailto:&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;"
class="text-muted">{% trans "Contact us" %}</a> &mdash; class="text-muted"><i class="fas fa-envelope"></i> {% trans "Contact us" %}</a>
</span> </span>
{% csrf_token %} {% csrf_token %}
<select title="language" name="language" <select title="language" name="language"
@ -207,10 +207,15 @@
{{ lang_name }} ({{ lang_code }}) {{ lang_name }} ({{ lang_code }})
</option> </option>
{% endfor %} {% endfor %}
</select> </select> &nbsp;
<noscript> <noscript>
<input type="submit"> <input type="submit">
</noscript> </noscript> &nbsp;
<a target="_blank" class="text-muted" href="{% url "about" %}">{% trans "About" %}</a> &nbsp; &mdash; &nbsp;
<a target="_blank" class="text-muted"
href="https://gitlab.com/animath/si/plateforme-corres2math">
<i class="fab fa-gitlab"></i>
</a>
</form> </form>
</div> </div>
<div class="col text-right"> <div class="col text-right">

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from django.core.handlers.asgi import ASGIHandler from django.core.handlers.asgi import ASGIHandler

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.tokens import PasswordResetTokenGenerator

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
"""corres2math URL Configuration """corres2math URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
@ -23,7 +26,8 @@ from registration.views import PhotoAuthorizationView
from .views import AdminSearchView from .views import AdminSearchView
urlpatterns = [ urlpatterns = [
path('', TemplateView.as_view(template_name="index.html", extra_context=dict(title="Accueil")), name='index'), path('', TemplateView.as_view(template_name="index.html"), name='index'),
path('about/', TemplateView.as_view(template_name="about.html"), name='about'),
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),
path('admin/doc/', include('django.contrib.admindocs.urls')), path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls, name="admin"), path('admin/', admin.site.urls, name="admin"),

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from haystack.generic_views import SearchView from haystack.generic_views import SearchView

View File

@ -1,3 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
""" """
WSGI config for corres2math project. WSGI config for corres2math project.

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Corres2math\n" "Project-Id-Version: Corres2math\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-11 16:09+0100\n" "POT-Creation-Date: 2020-12-22 21:30+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n" "Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -324,12 +324,12 @@ msgstr ""
"contacter :)" "contacter :)"
#: apps/participation/templates/participation/create_team.html:11 #: apps/participation/templates/participation/create_team.html:11
#: corres2math/templates/base.html:242 #: corres2math/templates/base.html:247
msgid "Create" msgid "Create"
msgstr "Créer" msgstr "Créer"
#: apps/participation/templates/participation/join_team.html:11 #: apps/participation/templates/participation/join_team.html:11
#: corres2math/templates/base.html:237 #: corres2math/templates/base.html:242
msgid "Join" msgid "Join"
msgstr "Rejoindre" msgstr "Rejoindre"
@ -503,7 +503,7 @@ msgstr "Définir l'équipe qui recevra votre vidéo"
#: apps/participation/templates/participation/participation_detail.html:181 #: apps/participation/templates/participation/participation_detail.html:181
#: apps/participation/templates/participation/participation_detail.html:233 #: apps/participation/templates/participation/participation_detail.html:233
#: apps/participation/views.py:499 #: apps/participation/views.py:510
msgid "Upload video" msgid "Upload video"
msgstr "Envoyer la vidéo" msgstr "Envoyer la vidéo"
@ -538,7 +538,7 @@ msgid "Update question"
msgstr "Modifier la question" msgstr "Modifier la question"
#: apps/participation/templates/participation/participation_detail.html:217 #: apps/participation/templates/participation/participation_detail.html:217
#: apps/participation/views.py:475 #: apps/participation/views.py:486
msgid "Delete question" msgid "Delete question"
msgstr "Supprimer la question" msgstr "Supprimer la question"
@ -548,8 +548,8 @@ msgid "Display synthesis"
msgstr "Afficher la synthèse" msgstr "Afficher la synthèse"
#: apps/participation/templates/participation/phase_list.html:10 #: apps/participation/templates/participation/phase_list.html:10
#: apps/participation/views.py:518 corres2math/templates/base.html:68 #: apps/participation/views.py:531 corres2math/templates/base.html:68
#: corres2math/templates/base.html:70 corres2math/templates/base.html:226 #: corres2math/templates/base.html:70 corres2math/templates/base.html:231
msgid "Calendar" msgid "Calendar"
msgstr "Calendrier" msgstr "Calendrier"
@ -661,7 +661,7 @@ msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: apps/participation/templates/participation/team_detail.html:127 #: apps/participation/templates/participation/team_detail.html:127
#: apps/participation/views.py:328 #: apps/participation/views.py:337
msgid "Leave team" msgid "Leave team"
msgstr "Quitter l'équipe" msgstr "Quitter l'équipe"
@ -670,53 +670,53 @@ msgid "Are you sure that you want to leave this team?"
msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?" msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?"
#: apps/participation/templates/participation/team_list.html:6 #: apps/participation/templates/participation/team_list.html:6
#: corres2math/templates/base.html:230 #: corres2math/templates/base.html:235
msgid "All teams" msgid "All teams"
msgstr "Toutes les équipes" msgstr "Toutes les équipes"
#: apps/participation/views.py:36 corres2math/templates/base.html:84 #: apps/participation/views.py:37 corres2math/templates/base.html:84
#: corres2math/templates/base.html:241 #: corres2math/templates/base.html:246
msgid "Create team" msgid "Create team"
msgstr "Créer une équipe" msgstr "Créer une équipe"
#: apps/participation/views.py:43 apps/participation/views.py:89 #: apps/participation/views.py:46 apps/participation/views.py:94
msgid "You don't participate, so you can't create a team." msgid "You don't participate, so you can't create a team."
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
#: apps/participation/views.py:45 apps/participation/views.py:91 #: apps/participation/views.py:48 apps/participation/views.py:96
msgid "You are already in a team." msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe." msgstr "Vous êtes déjà dans une équipe."
#: apps/participation/views.py:82 corres2math/templates/base.html:89 #: apps/participation/views.py:85 corres2math/templates/base.html:89
#: corres2math/templates/base.html:236 #: corres2math/templates/base.html:241
msgid "Join team" msgid "Join team"
msgstr "Rejoindre une équipe" msgstr "Rejoindre une équipe"
#: apps/participation/views.py:142 apps/participation/views.py:334 #: apps/participation/views.py:147 apps/participation/views.py:343
#: apps/participation/views.py:367 #: apps/participation/views.py:376
msgid "You are not in a team." msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe." msgstr "Vous n'êtes pas dans une équipe."
#: apps/participation/views.py:143 apps/participation/views.py:368 #: apps/participation/views.py:148 apps/participation/views.py:377
msgid "You don't participate, so you don't have any team." msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
#: apps/participation/views.py:165 #: apps/participation/views.py:170
#, python-brace-format #, python-brace-format
msgid "Detail of team {trigram}" msgid "Detail of team {trigram}"
msgstr "Détails de l'équipe {trigram}" msgstr "Détails de l'équipe {trigram}"
#: apps/participation/views.py:197 #: apps/participation/views.py:202
msgid "You don't participate, so you can't request the validation of the team." msgid "You don't participate, so you can't request the validation of the team."
msgstr "" msgstr ""
"Vous ne participez pas, vous ne pouvez pas demander la validation de " "Vous ne participez pas, vous ne pouvez pas demander la validation de "
"l'équipe." "l'équipe."
#: apps/participation/views.py:200 #: apps/participation/views.py:205
msgid "The validation of the team is already done or pending." msgid "The validation of the team is already done or pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: apps/participation/views.py:203 #: apps/participation/views.py:208
msgid "" msgid ""
"The team can't be validated: missing email address confirmations, photo " "The team can't be validated: missing email address confirmations, photo "
"authorizations, people or the chosen problem is not set." "authorizations, people or the chosen problem is not set."
@ -725,51 +725,51 @@ msgstr ""
"d'adresse e-mail, soit une autorisation parentale, soit des personnes soit " "d'adresse e-mail, soit une autorisation parentale, soit des personnes soit "
"le problème n'a pas été choisi." "le problème n'a pas été choisi."
#: apps/participation/views.py:222 #: apps/participation/views.py:227
msgid "You are not an administrator." msgid "You are not an administrator."
msgstr "Vous n'êtes pas administrateur." msgstr "Vous n'êtes pas administrateur."
#: apps/participation/views.py:225 #: apps/participation/views.py:230
msgid "This team has no pending validation." msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente." msgstr "L'équipe n'a pas de validation en attente."
#: apps/participation/views.py:249 #: apps/participation/views.py:254
msgid "You must specify if you validate the registration or not." msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non." msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: apps/participation/views.py:277 #: apps/participation/views.py:284
#, python-brace-format #, python-brace-format
msgid "Update team {trigram}" msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}" msgstr "Mise à jour de l'équipe {trigram}"
#: apps/participation/views.py:314 apps/registration/views.py:284 #: apps/participation/views.py:323 apps/registration/views.py:283
#, python-brace-format #, python-brace-format
msgid "Photo authorization of {student}.{ext}" msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}" msgstr "Autorisation de droit à l'image de {student}.{ext}"
#: apps/participation/views.py:318 #: apps/participation/views.py:327
#, python-brace-format #, python-brace-format
msgid "Photo authorizations of team {trigram}.zip" msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
#: apps/participation/views.py:336 #: apps/participation/views.py:345
msgid "The team is already validated or the validation is pending." msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: apps/participation/views.py:380 #: apps/participation/views.py:391
msgid "The team is not validated yet." msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée." msgstr "L'équipe n'est pas encore validée."
#: apps/participation/views.py:390 #: apps/participation/views.py:401
#, python-brace-format #, python-brace-format
msgid "Participation of team {trigram}" msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}" msgstr "Participation de l'équipe {trigram}"
#: apps/participation/views.py:427 #: apps/participation/views.py:438
msgid "Create question" msgid "Create question"
msgstr "Créer une question" msgstr "Créer une question"
#: apps/participation/views.py:527 #: apps/participation/views.py:540
msgid "Calendar update" msgid "Calendar update"
msgstr "Mise à jour du calendrier" msgstr "Mise à jour du calendrier"
@ -789,7 +789,11 @@ msgstr "encadrant"
msgid "This email address is already used." msgid "This email address is already used."
msgstr "Cette adresse e-mail est déjà utilisée." msgstr "Cette adresse e-mail est déjà utilisée."
#: apps/registration/forms.py:77 #: apps/registration/forms.py:78
msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
#: apps/registration/forms.py:80
msgid "The uploaded file must be a PDF, PNG of JPEG file." msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
@ -974,8 +978,8 @@ msgid "Your password has been set. You may go ahead and log in now."
msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter." msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter."
#: apps/registration/templates/registration/password_reset_complete.html:10 #: apps/registration/templates/registration/password_reset_complete.html:10
#: corres2math/templates/base.html:139 corres2math/templates/base.html:246 #: corres2math/templates/base.html:139 corres2math/templates/base.html:251
#: corres2math/templates/base.html:247 #: corres2math/templates/base.html:252
#: corres2math/templates/registration/login.html:7 #: corres2math/templates/registration/login.html:7
#: corres2math/templates/registration/login.html:8 #: corres2math/templates/registration/login.html:8
#: corres2math/templates/registration/login.html:25 #: corres2math/templates/registration/login.html:25
@ -1032,7 +1036,7 @@ msgstr "Réinitialiser mon mot de passe"
#: apps/registration/templates/registration/signup.html:5 #: apps/registration/templates/registration/signup.html:5
#: apps/registration/templates/registration/signup.html:8 #: apps/registration/templates/registration/signup.html:8
#: apps/registration/templates/registration/signup.html:20 #: apps/registration/templates/registration/signup.html:20
#: apps/registration/views.py:33 #: apps/registration/views.py:32
msgid "Sign up" msgid "Sign up"
msgstr "Inscription" msgstr "Inscription"
@ -1109,40 +1113,40 @@ msgid "Update user"
msgstr "Modifier l'utilisateur" msgstr "Modifier l'utilisateur"
#: apps/registration/templates/registration/user_detail.html:77 #: apps/registration/templates/registration/user_detail.html:77
#: apps/registration/views.py:247 #: apps/registration/views.py:246
msgid "Upload photo authorization" msgid "Upload photo authorization"
msgstr "Téléverser l'autorisation de droit à l'image" msgstr "Téléverser l'autorisation de droit à l'image"
#: apps/registration/views.py:41 #: apps/registration/views.py:40
msgid "You can't register now." msgid "You can't register now."
msgstr "Vous ne pouvez pas vous inscrire maintenant." msgstr "Vous ne pouvez pas vous inscrire maintenant."
#: apps/registration/views.py:85 #: apps/registration/views.py:84
msgid "Email validation" msgid "Email validation"
msgstr "Validation de l'adresse mail" msgstr "Validation de l'adresse mail"
#: apps/registration/views.py:87 #: apps/registration/views.py:86
msgid "Validate email" msgid "Validate email"
msgstr "Valider l'adresse mail" msgstr "Valider l'adresse mail"
#: apps/registration/views.py:126 #: apps/registration/views.py:125
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "Échec de la validation de l'adresse mail" msgstr "Échec de la validation de l'adresse mail"
#: apps/registration/views.py:137 #: apps/registration/views.py:136
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "Mail de confirmation de l'adresse mail envoyé" msgstr "Mail de confirmation de l'adresse mail envoyé"
#: apps/registration/views.py:145 #: apps/registration/views.py:144
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "Renvoyé le lien de validation de l'adresse mail" msgstr "Renvoyé le lien de validation de l'adresse mail"
#: apps/registration/views.py:181 #: apps/registration/views.py:180
#, python-brace-format #, python-brace-format
msgid "Detail of user {user}" msgid "Detail of user {user}"
msgstr "Détails de l'utilisateur {user}" msgstr "Détails de l'utilisateur {user}"
#: apps/registration/views.py:211 #: apps/registration/views.py:210
#, python-brace-format #, python-brace-format
msgid "Update user {user}" msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur {user}" msgstr "Mise à jour de l'utilisateur {user}"
@ -1271,7 +1275,11 @@ msgstr ""
msgid "Contact us" msgid "Contact us"
msgstr "Nous contacter" msgstr "Nous contacter"
#: corres2math/templates/base.html:233 #: corres2math/templates/base.html:214
msgid "About"
msgstr "À propos"
#: corres2math/templates/base.html:238
msgid "Search results" msgid "Search results"
msgstr "Résultats de la recherche" msgstr "Résultats de la recherche"

View File

@ -5,6 +5,7 @@ upstream corres2math {
server { server {
listen 80; listen 80;
server_name corres2math; server_name corres2math;
client_max_body_size 50M;
location / { location / {
proxy_pass http://corres2math; proxy_pass http://corres2math;

View File

@ -1,4 +1,4 @@
Django~=3.1 Django~=3.0
django-bootstrap-datepicker-plus~=3.0 django-bootstrap-datepicker-plus~=3.0
django-cas-server~=1.2 django-cas-server~=1.2
django-crispy-forms~=1.9 django-crispy-forms~=1.9