mirror of
				https://gitlab.com/animath/si/plateforme-corres2math.git
				synced 2025-10-31 02:59:53 +01:00 
			
		
		
		
	Compare commits
	
		
			38 Commits
		
	
	
		
			10932d1cc5
			...
			improvemen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 35a197be07 | ||
|  | d55671efd1 | ||
|  | bcdbc445c6 | ||
|  | 95a69f35d2 | ||
|  | eec941c816 | ||
|  | 64d471936f | ||
|  | 02c977264d | ||
|  | 8f86ea15c8 | ||
|  | 7628387158 | ||
|  | bf2feb9c35 | ||
|  | 969e53b712 | ||
|  | 84e149e8c9 | ||
|  | 9e6a3eb1ca | ||
|  | 1f9f60d880 | ||
|  | 205760f2e9 | ||
|  | 8f742b8e14 | ||
|  | 996d00c7f0 | ||
|  | 8f09ca5553 | ||
|  | b761670133 | ||
|  | f2ca4b7446 | ||
|  | 3faf1294d8 | ||
|  | dbcf15c4f3 | ||
|  | 10115a0419 | ||
|  | 833f9147ce | ||
|  | 55a727612c | ||
|  | de7f51d8da | ||
|  | c658a9767d | ||
|  | 982b61fe03 | ||
|  | ace1dbdc75 | ||
|  | 731f8711ff | ||
|  | d3e18a8fbb | ||
|  | ece1e800ab | ||
|  | 2e0028c063 | ||
|  | d304c3565c | ||
|  | 0fe1f9c348 | ||
|  | 9867e5e2a8 | ||
|  | 48b34e4362 | ||
|  | b0a3a22f83 | 
| @@ -1,4 +1,4 @@ | ||||
| FROM python:3-alpine | ||||
| FROM python:3.8-alpine | ||||
|  | ||||
| ENV PYTHONUNBUFFERED 1 | ||||
| ENV DJANGO_ALLOW_ASYNC_UNSAFE 1 | ||||
| @@ -16,9 +16,7 @@ RUN pip install -r requirements.txt --no-cache-dir | ||||
| COPY . /code/ | ||||
|  | ||||
| RUN python manage.py collectstatic --noinput && \ | ||||
|         python manage.py compilemessages && \ | ||||
|         python manage.py migrate && \ | ||||
|         python manage.py loaddata initial | ||||
|         python manage.py compilemessages | ||||
|  | ||||
| # Configure nginx | ||||
| RUN mkdir /run/nginx | ||||
|   | ||||
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -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. | ||||
|  | ||||
|     <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 | ||||
|     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 | ||||
| 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 is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'api.apps.APIConfig' | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|   | ||||
| @@ -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 rest_framework import serializers | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from unittest.case import skipIf | ||||
|  | ||||
| from django.conf import settings | ||||
|   | ||||
| @@ -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.urls import include, url | ||||
| from rest_framework import routers | ||||
|   | ||||
| @@ -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_filters.rest_framework import DjangoFilterBackend | ||||
| from rest_framework.filters import SearchFilter | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'eastereggs.apps.EastereggsConfig' | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
| from django.views.generic import TemplateView | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'participation.apps.ParticipationConfig' | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
| from django.db.models.signals import post_save, pre_delete, pre_save | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import re | ||||
|  | ||||
| from bootstrap_datepicker_plus import DateTimePickerInput | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
|  | ||||
| from asgiref.sync import async_to_sync | ||||
| @@ -17,7 +20,7 @@ class Command(BaseCommand): | ||||
|                 stat_file = os.stat("corres2math/static/logo.png") | ||||
|                 with open("corres2math/static/logo.png", "rb") as f: | ||||
|                     resp = 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 | ||||
|                 with open(".matrix_avatar", "w") as f: | ||||
|                     f.write(avatar_uri) | ||||
| @@ -56,9 +59,20 @@ class Command(BaseCommand): | ||||
|                 preset=RoomPreset.public_chat, | ||||
|             ) | ||||
|  | ||||
|         if not async_to_sync(Matrix.resolve_room_alias)("#flood:correspondances-maths.fr"): | ||||
|             Matrix.create_room( | ||||
|                 visibility=RoomVisibility.public, | ||||
|                 alias="flood", | ||||
|                 name="Flood", | ||||
|                 topic="Discutez de tout et de rien !", | ||||
|                 federate=False, | ||||
|                 preset=RoomPreset.public_chat, | ||||
|             ) | ||||
|  | ||||
|         Matrix.set_room_avatar("#annonces:correspondances-maths.fr", avatar_uri) | ||||
|         Matrix.set_room_avatar("#faq:correspondances-maths.fr", avatar_uri) | ||||
|         Matrix.set_room_avatar("#je-cherche-une-equipe:correspondances-maths.fr", avatar_uri) | ||||
|         Matrix.set_room_avatar("#flood:correspondances-maths.fr", avatar_uri) | ||||
|  | ||||
|         Matrix.set_room_power_level_event("#annonces:correspondances-maths.fr", "events_default", 50) | ||||
|  | ||||
| @@ -67,9 +81,12 @@ class Command(BaseCommand): | ||||
|             Matrix.invite("#faq:correspondances-maths.fr", f"@{r.matrix_username}:correspondances-maths.fr") | ||||
|             Matrix.invite("#je-cherche-une-equipe:correspondances-maths.fr", | ||||
|                           f"@{r.matrix_username}:correspondances-maths.fr") | ||||
|             Matrix.invite("#flood:correspondances-maths.fr", f"@{r.matrix_username}:correspondances-maths.fr") | ||||
|  | ||||
|         for admin in AdminRegistration.objects.all(): | ||||
|             Matrix.set_room_power_level("#annonces:correspondances-maths.fr", | ||||
|                                         f"@{admin.matrix_username}:correspondances-maths.fr", 95) | ||||
|             Matrix.set_room_power_level("#faq:correspondances-maths.fr", | ||||
|                                         f"@{admin.matrix_username}:correspondances-maths.fr", 95) | ||||
|             Matrix.set_room_power_level("#flood:correspondances-maths.fr", | ||||
|                                         f"@{admin.matrix_username}:correspondances-maths.fr", 95) | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| from django.db.models import Q | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from corres2math.lists import get_sympa_client | ||||
| from django.core.management import BaseCommand | ||||
| from django.db.models import Q | ||||
| from participation.models import Team | ||||
| from registration.models import CoachRegistration, StudentRegistration | ||||
|  | ||||
| @@ -27,13 +29,15 @@ class Command(BaseCommand): | ||||
|                               f" des Correspondances.", "education", raise_error=False) | ||||
|  | ||||
|         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) | ||||
|  | ||||
|         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) | ||||
|  | ||||
|         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(): | ||||
|             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}") | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from corres2math.matrix import Matrix, RoomVisibility | ||||
| from django.core.management import BaseCommand | ||||
| from participation.models import Participation | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
| import re | ||||
|  | ||||
| @@ -65,7 +68,7 @@ class Team(models.Model): | ||||
|             "education", | ||||
|             raise_error=False, | ||||
|         ) | ||||
|         if self.participation.valid: | ||||
|         if self.pk and self.participation.valid:  # pragma: no cover | ||||
|             get_sympa_client().subscribe(self.email, "equipes", False, f"Equipe {self.name}") | ||||
|             get_sympa_client().subscribe(self.email, f"probleme-{self.participation.problem}", False, | ||||
|                                          f"Equipe {self.name}") | ||||
| @@ -76,7 +79,7 @@ class Team(models.Model): | ||||
|         """ | ||||
|         Drop the Sympa mailing list, if the team is empty or if the trigram changed. | ||||
|         """ | ||||
|         if self.participation.valid: | ||||
|         if self.participation.valid:  # pragma: no cover | ||||
|             get_sympa_client().unsubscribe(self.email, "equipes", False) | ||||
|             get_sympa_client().unsubscribe(self.email, f"probleme-{self.participation.problem}", False) | ||||
|         else: | ||||
| @@ -285,17 +288,15 @@ class Phase(models.Model): | ||||
|     ) | ||||
|  | ||||
|     @classmethod | ||||
|     def current_phase(cls): | ||||
|     def current_phase(cls) -> "Phase": | ||||
|         """ | ||||
|         Retrieve the current phase of this day | ||||
|         """ | ||||
|         qs = Phase.objects.filter(start__lte=timezone.now(), end__gte=timezone.now()) | ||||
|         if qs.exists(): | ||||
|             return qs.get() | ||||
|         qs = Phase.objects.order_by("phase_number").all() | ||||
|         if timezone.now() < qs.first().start: | ||||
|             return qs.first() | ||||
|         return qs.last() | ||||
|         qs = Phase.objects.filter(start__lte=timezone.now()).order_by("phase_number").all() | ||||
|         return qs.last() if qs.exists() else None | ||||
|  | ||||
|     def __str__(self): | ||||
|         return _("Phase {phase_number:d} starts on {start:%Y-%m-%d %H:%M} and ends on {end:%Y-%m-%d %H:%M}")\ | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from haystack import indexes | ||||
|  | ||||
| from .models import Participation, Team, Video | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from corres2math.lists import get_sympa_client | ||||
| from participation.models import Participation, Team, Video | ||||
|  | ||||
|  | ||||
| def create_team_participation(instance, **_): | ||||
| def create_team_participation(instance, created, **_): | ||||
|     """ | ||||
|     When a team got created, create an associated team and create Video objects. | ||||
|     """ | ||||
| @@ -12,6 +15,8 @@ def create_team_participation(instance, **_): | ||||
|     if not participation.synthesis: | ||||
|         participation.synthesis = Video.objects.create() | ||||
|     participation.save() | ||||
|     if not created: | ||||
|         participation.team.create_mailing_list() | ||||
|  | ||||
|  | ||||
| def update_mailing_list(instance: Team, **_): | ||||
|   | ||||
| @@ -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.translation import gettext_lazy as _ | ||||
| import django_tables2 as tables | ||||
| @@ -39,6 +42,9 @@ class TeamTable(tables.Table): | ||||
|         attrs = { | ||||
|             'class': 'table table condensed table-striped', | ||||
|         } | ||||
|         row_attrs = { | ||||
|             'class': lambda record: '' if record.participation.solution.link else 'bg-warning', | ||||
|         } | ||||
|         model = Team | ||||
|         fields = ('name', 'trigram', 'problem',) | ||||
|         template_name = 'django_tables2/bootstrap4.html' | ||||
|   | ||||
| @@ -1,16 +1,39 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block extracss %} | ||||
|     <style> | ||||
|     iframe { | ||||
|         width: 100%; | ||||
|         height: 100vh; | ||||
|         margin: 0; | ||||
|         padding: 0; | ||||
|     } | ||||
|     </style> | ||||
| {% endblock %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block fullcontent %} | ||||
|     <iframe frameborder="0" src="https://element.correspondances-maths.fr/#/room/#faq:correspondances-maths.fr"></iframe> | ||||
| {% block content %} | ||||
|     <div class="alert alert-warning"> | ||||
|     {% blocktrans trimmed %} | ||||
|         The chat is located on the dedicated Matrix server: | ||||
|     {% endblocktrans %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="alert text-center"> | ||||
|     <a class="btn btn-success" href="https://element.correspondances-maths.fr/#/room/#faq:correspondances-maths.fr" target="_blank"> | ||||
|         <i class="fas fa-server"></i> {% trans "Access to the Matrix server" %} | ||||
|     </a> | ||||
|     </div> | ||||
|  | ||||
|     <div class="alert alert-info"> | ||||
|     <p> | ||||
|     {% blocktrans trimmed %} | ||||
|         To connect to the server, you can select "Log in", then use your credentials of this platform to connect | ||||
|         with the central authentication server, then you must trust the connection between the Matrix account and the | ||||
|         platform. Finally, you will be able to access to the chat platform. | ||||
|     {% endblocktrans %} | ||||
|     </p> | ||||
|  | ||||
|     <p> | ||||
|     {% blocktrans trimmed %} | ||||
|         You will be invited in some basic rooms. You must confirm the invitations to join channels. | ||||
|     {% endblocktrans %} | ||||
|     </p> | ||||
|  | ||||
|     <p> | ||||
|     {% blocktrans trimmed %} | ||||
|         If you have any trouble, don't hesitate to contact us :) | ||||
|     {% endblocktrans %} | ||||
|     </p> | ||||
|     </div> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -13,7 +13,9 @@ Bonjour {{ user.registration }}, | ||||
| L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer | ||||
| au {{ team.participation.get_problem_display }} des Correspondances des Jeunes Mathématicien·ne·s. | ||||
| Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe : | ||||
| <a href="{% url "participation:team_detail" pk=team.pk %}">{% url "participation:team_detail" pk=team.pk %}</a> | ||||
| <a href="https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}"> | ||||
|     https://{{ domain }}{% url "participation:team_detail" pk=team.pk %} | ||||
| </a> | ||||
| </p> | ||||
|  | ||||
| <p> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ Bonjour {{ user.registration }}, | ||||
| L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer | ||||
| au {{ team.participation.get_problem_display }} des Correspondances des Jeunes Mathématicien·ne·s. | ||||
| Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe : | ||||
| {% url "participation:team_detail" pk=team.pk %} | ||||
| https://{{ domain }}{% url "participation:team_detail" pk=team.pk %} | ||||
|  | ||||
| Cordialement, | ||||
|  | ||||
|   | ||||
| @@ -194,13 +194,14 @@ | ||||
|         {% 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 %} | ||||
|     {% endif %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if user.registration.participates and current_phase.phase_number == 2 %} | ||||
|     {% trans "Add question" as modal_title %} | ||||
|     {% trans "Add" as modal_button %} | ||||
|     {% url "participation:add_question" pk=participation.pk as modal_action %} | ||||
|     {% include "base_modal.html" with modal_id="addQuestion" modal_button_type="success" %} | ||||
|     {% if current_phase.phase_number == 2 %} | ||||
|         {% trans "Add question" as modal_title %} | ||||
|         {% trans "Add" as modal_button %} | ||||
|         {% url "participation:add_question" pk=participation.pk as modal_action %} | ||||
|         {% include "base_modal.html" with modal_id="addQuestion" modal_button_type="success" %} | ||||
|     {% endif %} | ||||
|  | ||||
|     {% for question in participation.questions.all %} | ||||
|         {% with number_str=forloop.counter|stringformat:"d"%} | ||||
|             {% with modal_id="updateQuestion"|add:number_str %} | ||||
| @@ -260,27 +261,29 @@ | ||||
|                 }); | ||||
|             {% endif %} | ||||
|  | ||||
|             {% if user.registration.participates and current_phase.phase_number == 2 %} | ||||
|             {% if current_phase.phase_number == 2 %} | ||||
|                 $('button[data-target="#addQuestionModal"]').click(function() { | ||||
|                     let modalBody = $("#addQuestionModal div.modal-body"); | ||||
|                     if (!modalBody.html().trim()) | ||||
|                         modalBody.load("{% url "participation:add_question" pk=participation.pk %} #form-content"); | ||||
|                 }); | ||||
|             {% endif %} | ||||
|  | ||||
|                 {% for question in participation.questions.all %} | ||||
|                     $('button[data-target="#updateQuestion{{ forloop.counter }}Modal"]').click(function() { | ||||
|                         let modalBody = $("#updateQuestion{{ forloop.counter }}Modal div.modal-body"); | ||||
|                         if (!modalBody.html().trim()) | ||||
|                             modalBody.load("{% url "participation:update_question" pk=question.pk %} #form-content"); | ||||
|                     }); | ||||
|             {% for question in participation.questions.all %} | ||||
|                 $('button[data-target="#updateQuestion{{ forloop.counter }}Modal"]').click(function() { | ||||
|                     let modalBody = $("#updateQuestion{{ forloop.counter }}Modal div.modal-body"); | ||||
|                     if (!modalBody.html().trim()) | ||||
|                         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() { | ||||
|                         let modalBody = $("#deleteQuestion{{ forloop.counter }}Modal div.modal-body"); | ||||
|                         if (!modalBody.html().trim()) | ||||
|                             modalBody.load("{% url "participation:delete_question" pk=question.pk %} #form-content"); | ||||
|                     }); | ||||
|                 {% endfor %} | ||||
|             {% endif %} | ||||
|                 {% endif %} | ||||
|             {% endfor %} | ||||
|  | ||||
|             $('button[data-target="#uploadSolutionModal"]').click(function() { | ||||
|                 let modalBody = $("#uploadSolutionModal div.modal-body"); | ||||
|   | ||||
							
								
								
									
										13
									
								
								apps/participation/templates/participation/team_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								apps/participation/templates/participation/team_list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% load django_tables2 i18n %} | ||||
|  | ||||
| {% block contenttitle %} | ||||
|     <h1>{% trans "All teams" %}</h1> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|     <div id="form-content"> | ||||
|         {% render_table table %} | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										2
									
								
								apps/participation/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								apps/participation/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
							
								
								
									
										15
									
								
								apps/participation/templatetags/calendar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								apps/participation/templatetags/calendar.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import template | ||||
|  | ||||
| from ..models import Phase | ||||
|  | ||||
|  | ||||
| def current_phase(nb): | ||||
|     phase = Phase.current_phase() | ||||
|     return phase is not None and phase.phase_number == nb | ||||
|  | ||||
|  | ||||
| register = template.Library() | ||||
| register.filter("current_phase", current_phase) | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
| @@ -185,6 +188,13 @@ class TestStudentParticipation(TestCase): | ||||
|         )) | ||||
|         self.assertEqual(response.status_code, 403) | ||||
|  | ||||
|     def test_team_list(self): | ||||
|         """ | ||||
|         Test to display the list of teams. | ||||
|         """ | ||||
|         response = self.client.get(reverse("participation:team_list")) | ||||
|         self.assertTrue(response.status_code, 200) | ||||
|  | ||||
|     def test_no_myteam_redirect_noteam(self): | ||||
|         """ | ||||
|         Test redirection. | ||||
| @@ -402,12 +412,6 @@ class TestStudentParticipation(TestCase): | ||||
|         self.user.registration.team = self.team | ||||
|         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 | ||||
|         self.team.participation.valid = True | ||||
|         self.team.participation.save() | ||||
| @@ -575,7 +579,7 @@ class TestStudentParticipation(TestCase): | ||||
|         for i in range(1, 5): | ||||
|             Phase.objects.filter(phase_number=i).update(start=timezone.now() + timedelta(days=2 * i), | ||||
|                                                         end=timezone.now() + timedelta(days=2 * i + 1)) | ||||
|         self.assertEqual(Phase.current_phase().phase_number, 1) | ||||
|         self.assertEqual(Phase.current_phase(), None) | ||||
|  | ||||
|         # We are after the end | ||||
|         for i in range(1, 5): | ||||
| @@ -662,7 +666,7 @@ class TestStudentParticipation(TestCase): | ||||
|  | ||||
|     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.save() | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
| from django.views.generic import TemplateView | ||||
|  | ||||
| from .views import CalendarView, CreateQuestionView, CreateTeamView, DeleteQuestionView, JoinTeamView, \ | ||||
|     MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, PhaseUpdateView, \ | ||||
|     SetParticipationReceiveParticipationView, SetParticipationSendParticipationView, TeamAuthorizationsView, \ | ||||
|     TeamDetailView, TeamLeaveView, TeamUpdateView, UpdateQuestionView, UploadVideoView | ||||
|     TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, UpdateQuestionView, UploadVideoView | ||||
|  | ||||
|  | ||||
| app_name = "participation" | ||||
| @@ -12,6 +15,7 @@ app_name = "participation" | ||||
| urlpatterns = [ | ||||
|     path("create_team/", CreateTeamView.as_view(), name="create_team"), | ||||
|     path("join_team/", JoinTeamView.as_view(), name="join_team"), | ||||
|     path("teams/", TeamListView.as_view(), name="team_list"), | ||||
|     path("team/", MyTeamDetailView.as_view(), name="my_team_detail"), | ||||
|     path("team/<int:pk>/", TeamDetailView.as_view(), name="team_detail"), | ||||
|     path("team/<int:pk>/update/", TeamUpdateView.as_view(), name="update_team"), | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from io import BytesIO | ||||
| from zipfile import ZipFile | ||||
|  | ||||
| @@ -5,6 +8,7 @@ from corres2math.lists import get_sympa_client | ||||
| from corres2math.matrix import Matrix | ||||
| from corres2math.views import AdminMixin | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.contrib.sites.models import Site | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.core.mail import send_mail | ||||
| from django.db import transaction | ||||
| @@ -23,7 +27,7 @@ from .forms import JoinTeamForm, ParticipationForm, PhaseForm, QuestionForm, \ | ||||
|     ReceiveParticipationForm, RequestValidationForm, SendParticipationForm, TeamForm, \ | ||||
|     UploadVideoForm, ValidateParticipationForm | ||||
| from .models import Participation, Phase, Question, Team, Video | ||||
| from .tables import CalendarTable | ||||
| from .tables import CalendarTable, TeamTable | ||||
|  | ||||
|  | ||||
| class CreateTeamView(LoginRequiredMixin, CreateView): | ||||
| @@ -38,6 +42,8 @@ class CreateTeamView(LoginRequiredMixin, CreateView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         user = request.user | ||||
|         if not user.is_authenticated: | ||||
|             return super().handle_no_permission() | ||||
|         registration = user.registration | ||||
|         if not registration.participates: | ||||
|             raise PermissionDenied(_("You don't participate, so you can't create a team.")) | ||||
| @@ -84,6 +90,8 @@ class JoinTeamView(LoginRequiredMixin, FormView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         user = request.user | ||||
|         if not user.is_authenticated: | ||||
|             return super().handle_no_permission() | ||||
|         registration = user.registration | ||||
|         if not registration.participates: | ||||
|             raise PermissionDenied(_("You don't participate, so you can't create a team.")) | ||||
| @@ -119,6 +127,15 @@ class JoinTeamView(LoginRequiredMixin, FormView): | ||||
|         return reverse_lazy("participation:team_detail", args=(self.object.pk,)) | ||||
|  | ||||
|  | ||||
| class TeamListView(AdminMixin, SingleTableView): | ||||
|     """ | ||||
|     Display the whole list of teams | ||||
|     """ | ||||
|     model = Team | ||||
|     table_class = TeamTable | ||||
|     ordering = ('participation__problem', 'trigram',) | ||||
|  | ||||
|  | ||||
| class MyTeamDetailView(LoginRequiredMixin, RedirectView): | ||||
|     """ | ||||
|     Redirect to the detail of the team in which the user is. | ||||
| @@ -199,7 +216,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView) | ||||
|         self.object.participation.save() | ||||
|  | ||||
|         for admin in AdminRegistration.objects.all(): | ||||
|             mail_context = dict(user=admin.user, team=self.object) | ||||
|             mail_context = dict(user=admin.user, team=self.object, domain=Site.objects.first().domain) | ||||
|             mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context) | ||||
|             mail_html = render_to_string("participation/mails/request_validation.html", mail_context) | ||||
|             admin.user.email_user("[Corres2math] Validation d'équipe", mail_plain, html_message=mail_html) | ||||
| @@ -255,6 +272,8 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         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 and \ | ||||
|                 user.registration.team.pk == kwargs["pk"]: | ||||
| @@ -289,6 +308,8 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         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"]: | ||||
|             return super().dispatch(request, *args, **kwargs) | ||||
|         raise PermissionDenied | ||||
| @@ -323,7 +344,7 @@ class TeamLeaveView(LoginRequiredMixin, TemplateView): | ||||
|             return self.handle_no_permission() | ||||
|         if not request.user.registration.participates or not request.user.registration.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.")) | ||||
|         return super().dispatch(request, *args, **kwargs) | ||||
|  | ||||
| @@ -367,6 +388,8 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         user = request.user | ||||
|         if not user.is_authenticated: | ||||
|             return super().handle_no_permission() | ||||
|         if not self.get_object().valid: | ||||
|             raise PermissionDenied(_("The team is not validated yet.")) | ||||
|         if user.registration.is_admin or user.registration.participates \ | ||||
| @@ -454,6 +477,12 @@ class UpdateQuestionView(LoginRequiredMixin, UpdateView): | ||||
|             return super().dispatch(request, *args, **kwargs) | ||||
|         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): | ||||
|         return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,)) | ||||
|  | ||||
| @@ -476,6 +505,11 @@ class DeleteQuestionView(LoginRequiredMixin, DeleteView): | ||||
|             return super().dispatch(request, *args, **kwargs) | ||||
|         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): | ||||
|         return reverse_lazy("participation:participation_detail", args=(self.object.participation.pk,)) | ||||
|  | ||||
| @@ -491,6 +525,8 @@ class UploadVideoView(LoginRequiredMixin, UpdateView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         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.participation.pk == self.get_object().participation.pk: | ||||
|             return super().dispatch(request, *args, **kwargs) | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'registration.apps.RegistrationConfig' | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
| from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
| from django.db.models.signals import post_save, pre_save | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -13,5 +13,14 @@ | ||||
|             "single_log_out": true, | ||||
|             "single_log_out_callback": "" | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "model": "cas_server.replaceattributname", | ||||
|         "pk": 1, | ||||
|         "fields": { | ||||
|             "name": "display_name", | ||||
|             "replace": "", | ||||
|             "service_pattern": 1 | ||||
|         } | ||||
|     } | ||||
| ] | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import forms | ||||
| from django.contrib.auth.forms import UserCreationForm | ||||
| from django.contrib.auth.models import User | ||||
| @@ -22,6 +25,15 @@ class SignupForm(UserCreationForm): | ||||
|         ], | ||||
|     ) | ||||
|  | ||||
|     def clean_email(self): | ||||
|         """ | ||||
|         Ensure that the email address is unique. | ||||
|         """ | ||||
|         email = self.data["email"] | ||||
|         if User.objects.filter(email=email).exists(): | ||||
|             self.add_error("email", _("This email address is already used.")) | ||||
|         return email | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.fields["first_name"].required = True | ||||
| @@ -55,7 +67,7 @@ class StudentRegistrationForm(forms.ModelForm): | ||||
|     """ | ||||
|     class Meta: | ||||
|         model = StudentRegistration | ||||
|         fields = ('student_class', 'school', 'give_contact_to_animath',) | ||||
|         fields = ('team', 'student_class', 'school', 'give_contact_to_animath', 'email_confirmed',) | ||||
|  | ||||
|  | ||||
| class PhotoAuthorizationForm(forms.ModelForm): | ||||
| @@ -63,10 +75,13 @@ class PhotoAuthorizationForm(forms.ModelForm): | ||||
|     Form to send a photo authorization. | ||||
|     """ | ||||
|     def clean_photo_authorization(self): | ||||
|         file = self.files["photo_authorization"] | ||||
|         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"] | ||||
|         if "photo_authorization" in self.files: | ||||
|             file = self.files["photo_authorization"] | ||||
|             if file.size > 2e6: | ||||
|                 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): | ||||
|         super().__init__(*args, **kwargs) | ||||
| @@ -83,7 +98,7 @@ class CoachRegistrationForm(forms.ModelForm): | ||||
|     """ | ||||
|     class Meta: | ||||
|         model = CoachRegistration | ||||
|         fields = ('professional_activity', 'give_contact_to_animath',) | ||||
|         fields = ('team', 'professional_activity', 'give_contact_to_animath', 'email_confirmed',) | ||||
|  | ||||
|  | ||||
| class AdminRegistrationForm(forms.ModelForm): | ||||
| @@ -92,4 +107,4 @@ class AdminRegistrationForm(forms.ModelForm): | ||||
|     """ | ||||
|     class Meta: | ||||
|         model = AdminRegistration | ||||
|         fields = ('role', 'give_contact_to_animath',) | ||||
|         fields = ('role', 'give_contact_to_animath', 'email_confirmed',) | ||||
|   | ||||
| @@ -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')), | ||||
|                 ('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')), | ||||
|                 ('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={ | ||||
|                 'verbose_name': 'student registration', | ||||
| @@ -63,7 +63,7 @@ class Migration(migrations.Migration): | ||||
|             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')), | ||||
|                 ('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={ | ||||
|                 'verbose_name': 'coach registration', | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -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 django.contrib.sites.models import Site | ||||
| from django.db import models | ||||
| @@ -102,6 +105,7 @@ class StudentRegistration(Registration): | ||||
|         related_name="students", | ||||
|         on_delete=models.PROTECT, | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         default=None, | ||||
|         verbose_name=_("team"), | ||||
|     ) | ||||
| @@ -151,6 +155,7 @@ class CoachRegistration(Registration): | ||||
|         related_name="coachs", | ||||
|         on_delete=models.PROTECT, | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         default=None, | ||||
|         verbose_name=_("team"), | ||||
|     ) | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from haystack import indexes | ||||
|  | ||||
| from .models import Registration | ||||
|   | ||||
| @@ -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.matrix import Matrix | ||||
| from django.contrib.auth.models import User | ||||
| @@ -50,3 +53,4 @@ def invite_to_public_rooms(instance: Registration, created: bool, **_): | ||||
|         Matrix.invite("#faq:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr") | ||||
|         Matrix.invite("#je-cherche-une-equip:correspondances-maths.fr", | ||||
|                       f"@{instance.matrix_username}:correspondances-maths.fr") | ||||
|         Matrix.invite("#flood:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr") | ||||
|   | ||||
| @@ -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 _ | ||||
| import django_tables2 as tables | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,11 @@ SPDX-License-Identifier: GPL-3.0-or-later | ||||
|         </p> | ||||
|         {% else %} | ||||
|         <p> | ||||
|             {% trans "The link was invalid. The token may have expired. Please send us an email to activate your account." %} | ||||
|             {% if user.is_authenticated and user.registration.email_confirmed %} | ||||
|                 {% trans "The link was invalid. The token may have expired, or your account is already activated. However, your account seems to be already valid." %} | ||||
|             {% else %} | ||||
|                 {% trans "The link was invalid. The token may have expired, or your account is already activated. Please send us an email to activate your account." %} | ||||
|             {% endif %} | ||||
|         </p> | ||||
|         {% endif %} | ||||
|      </div> | ||||
|   | ||||
							
								
								
									
										7
									
								
								apps/registration/templates/registration/user_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								apps/registration/templates/registration/user_list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% load django_tables2 %} | ||||
|  | ||||
| {% block content %} | ||||
|     {% render_table table %} | ||||
| {% endblock %} | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import template | ||||
| from django_tables2 import Table | ||||
| from participation.models import Participation, Team, Video | ||||
|   | ||||
| @@ -1,14 +1,21 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from datetime import timedelta | ||||
| import os | ||||
|  | ||||
| from corres2math.tokens import email_validation_token | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.contrib.sites.models import Site | ||||
| from django.core.files.uploadedfile import SimpleUploadedFile | ||||
| from django.core.management import call_command | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
| from django.utils import timezone | ||||
| from django.utils.encoding import force_bytes | ||||
| from django.utils.http import urlsafe_base64_encode | ||||
| from participation.models import Phase, Team | ||||
|  | ||||
| from .models import AdminRegistration, CoachRegistration, StudentRegistration | ||||
|  | ||||
| @@ -28,6 +35,28 @@ class TestIndexPage(TestCase): | ||||
|         response = self.client.get(reverse("registration:reset_admin")) | ||||
|         self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:reset_admin"), 302, 200) | ||||
|  | ||||
|         User.objects.create() | ||||
|         response = self.client.get(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): | ||||
|     def setUp(self) -> None: | ||||
| @@ -81,6 +110,13 @@ class TestRegistration(TestCase): | ||||
|         """ | ||||
|         Ensure that the signup form is working successfully. | ||||
|         """ | ||||
|         # After first phase | ||||
|         response = self.client.get(reverse("registration:signup")) | ||||
|         self.assertEqual(response.status_code, 403) | ||||
|  | ||||
|         Phase.objects.filter(phase_number__gte=2).update(start=timezone.now() + timedelta(days=1), | ||||
|                                                          end=timezone.now() + timedelta(days=2)) | ||||
|  | ||||
|         response = self.client.get(reverse("registration:signup")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
| @@ -109,6 +145,20 @@ class TestRegistration(TestCase): | ||||
|         self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200) | ||||
|         self.assertTrue(User.objects.filter(email="toto@example.com").exists()) | ||||
|  | ||||
|         # Email is already used | ||||
|         response = self.client.post(reverse("registration:signup"), data=dict( | ||||
|             last_name="Toto", | ||||
|             first_name="Toto", | ||||
|             email="toto@example.com", | ||||
|             password1="azertyuiopazertyuiop", | ||||
|             password2="azertyuiopazertyuiop", | ||||
|             role="participant", | ||||
|             student_class=12, | ||||
|             school="God", | ||||
|             give_contact_to_animath=False, | ||||
|         )) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         response = self.client.get(reverse("registration:email_validation_sent")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
| @@ -175,6 +225,13 @@ class TestRegistration(TestCase): | ||||
|         response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,))) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|     def test_user_list(self): | ||||
|         """ | ||||
|         Display the list of all users. | ||||
|         """ | ||||
|         response = self.client.get(reverse("registration:user_list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|     def test_update_user(self): | ||||
|         """ | ||||
|         Update the user information, for each type of user. | ||||
| @@ -198,6 +255,8 @@ class TestRegistration(TestCase): | ||||
|                 last_name="Name", | ||||
|                 email="new_" + user.email, | ||||
|                 give_contact_to_animath=True, | ||||
|                 email_confirmed=True, | ||||
|                 team_id="", | ||||
|             )) | ||||
|             self.assertEqual(response.status_code, 200) | ||||
|  | ||||
| @@ -206,6 +265,8 @@ class TestRegistration(TestCase): | ||||
|                 last_name="Name", | ||||
|                 email="new_" + user.email, | ||||
|                 give_contact_to_animath=True, | ||||
|                 email_confirmed=True, | ||||
|                 team_id="", | ||||
|             ) | ||||
|             response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=data) | ||||
|             self.assertRedirects(response, reverse("registration:user_detail", args=(user.pk,)), 302, 200) | ||||
| @@ -229,6 +290,14 @@ class TestRegistration(TestCase): | ||||
|         )) | ||||
|         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", | ||||
|                                             args=(self.student.registration.pk,)), data=dict( | ||||
|             photo_authorization=open("corres2math/static/Autorisation de droit à l'image - majeur.pdf", "rb"), | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \ | ||||
|     UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \ | ||||
|     UserListView, UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \ | ||||
|     UserValidationEmailSentView | ||||
|  | ||||
| app_name = "registration" | ||||
| @@ -18,5 +21,6 @@ urlpatterns = [ | ||||
|     path("user/<int:pk>/upload-photo-authorization/", UserUploadPhotoAuthorizationView.as_view(), | ||||
|          name="upload_user_photo_authorization"), | ||||
|     path("user/<int:pk>/impersonate/", UserImpersonateView.as_view(), name="user_impersonate"), | ||||
|     path("user/list/", UserListView.as_view(), name="user_list"), | ||||
|     path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"), | ||||
| ] | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
|  | ||||
| from corres2math.tokens import email_validation_token | ||||
| from corres2math.views import AdminMixin | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.contrib.auth.models import User | ||||
| @@ -12,10 +16,13 @@ from django.urls import reverse_lazy | ||||
| from django.utils.http import urlsafe_base64_decode | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View | ||||
| from django_tables2 import SingleTableView | ||||
| from magic import Magic | ||||
| from participation.models import Phase | ||||
|  | ||||
| from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm | ||||
| from .models import StudentRegistration | ||||
| from .models import Registration, StudentRegistration | ||||
| from .tables import RegistrationTable | ||||
|  | ||||
|  | ||||
| class SignupView(CreateView): | ||||
| @@ -27,12 +34,26 @@ class SignupView(CreateView): | ||||
|     template_name = "registration/signup.html" | ||||
|     extra_context = dict(title=_("Sign up")) | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         """ | ||||
|         The signup view is available only during the first phase. | ||||
|         """ | ||||
|         current_phase = Phase.current_phase() | ||||
|         if not current_phase or current_phase.phase_number >= 2: | ||||
|             raise PermissionDenied(_("You can't register now.")) | ||||
|         return super().dispatch(request, *args, **kwargs) | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data() | ||||
|  | ||||
|         context["student_registration_form"] = StudentRegistrationForm(self.request.POST or None) | ||||
|         context["coach_registration_form"] = CoachRegistrationForm(self.request.POST or None) | ||||
|  | ||||
|         del context["student_registration_form"].fields["team"] | ||||
|         del context["student_registration_form"].fields["email_confirmed"] | ||||
|         del context["coach_registration_form"].fields["team"] | ||||
|         del context["coach_registration_form"].fields["email_confirmed"] | ||||
|  | ||||
|         return context | ||||
|  | ||||
|     @transaction.atomic | ||||
| @@ -42,6 +63,8 @@ class SignupView(CreateView): | ||||
|             registration_form = StudentRegistrationForm(self.request.POST) | ||||
|         else: | ||||
|             registration_form = CoachRegistrationForm(self.request.POST) | ||||
|         del registration_form.fields["team"] | ||||
|         del registration_form.fields["email_confirmed"] | ||||
|  | ||||
|         if not registration_form.is_valid(): | ||||
|             return self.form_invalid(form) | ||||
| @@ -148,6 +171,8 @@ class UserDetailView(LoginRequiredMixin, DetailView): | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         user = request.user | ||||
|         if not user.is_authenticated: | ||||
|             return self.handle_no_permission() | ||||
|         # Only an admin or the concerned user can see the information | ||||
|         if not user.registration.is_admin and user.pk != kwargs["pk"]: | ||||
|             raise PermissionDenied | ||||
| @@ -159,6 +184,15 @@ class UserDetailView(LoginRequiredMixin, DetailView): | ||||
|         return context | ||||
|  | ||||
|  | ||||
| class UserListView(AdminMixin, SingleTableView): | ||||
|     """ | ||||
|     Display the list of all registered users. | ||||
|     """ | ||||
|     model = Registration | ||||
|     table_class = RegistrationTable | ||||
|     template_name = "registration/user_list.html" | ||||
|  | ||||
|  | ||||
| class UserUpdateView(LoginRequiredMixin, UpdateView): | ||||
|     """ | ||||
|     Update the detail about a user and its registration. | ||||
| @@ -179,6 +213,10 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): | ||||
|         context["title"] = _("Update user {user}").format(user=str(self.object.registration)) | ||||
|         context["registration_form"] = user.registration.form_class(data=self.request.POST or None, | ||||
|                                                                     instance=self.object.registration) | ||||
|         if not self.request.user.registration.is_admin: | ||||
|             if "team" in context["registration_form"].fields: | ||||
|                 del context["registration_form"].fields["team"] | ||||
|             del context["registration_form"].fields["email_confirmed"] | ||||
|         return context | ||||
|  | ||||
|     @transaction.atomic | ||||
| @@ -186,6 +224,11 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): | ||||
|         user = form.instance | ||||
|         registration_form = user.registration.form_class(data=self.request.POST or None, | ||||
|                                                          instance=self.object.registration) | ||||
|         if not self.request.user.registration.is_admin: | ||||
|             if "team" in registration_form.fields: | ||||
|                 del registration_form.fields["team"] | ||||
|             del registration_form.fields["email_confirmed"] | ||||
|  | ||||
|         if not registration_form.is_valid(): | ||||
|             return self.form_invalid(form) | ||||
|  | ||||
|   | ||||
| @@ -3,3 +3,6 @@ | ||||
| *       *       *       *       *       cd /code && python manage.py send_mail -c 1 | ||||
| *       *       *       *       *       cd /code && python manage.py retry_deferred -c 1 | ||||
| 0       0       *       *       *       cd /code && python manage.py purge_mail_log 7 -c 1 | ||||
|  | ||||
| # Rebuild search index | ||||
| 0       *       *       *       *       cd /code && python manage.py update_index -v 0 | ||||
|   | ||||
| @@ -0,0 +1,2 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| """ | ||||
| ASGI config for corres2math project. | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
|  | ||||
| _client = None | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from enum import Enum | ||||
| import os | ||||
|  | ||||
| @@ -216,7 +219,7 @@ class Matrix: | ||||
|         """ | ||||
|         client = await cls._get_client() | ||||
|         resp = await client.room_resolve_alias(room_alias) | ||||
|         return resp.room_id if resp else None | ||||
|         return resp.room_id if resp and hasattr(resp, "room_id") else None | ||||
|  | ||||
|     @classmethod | ||||
|     @async_to_sync | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from threading import local | ||||
|  | ||||
| from django.conf import settings | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| """ | ||||
| Django settings for corres2math project. | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| # Database | ||||
| # https://docs.djangoproject.com/en/3.0/ref/settings/#databases | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
|  | ||||
| # Break it, fix it! | ||||
|   | ||||
							
								
								
									
										40
									
								
								corres2math/templates/about.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								corres2math/templates/about.html
									
									
									
									
									
										Normal 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:contact@correspondances-maths.fr"> | ||||
|         contact@correspondances-maths.fr | ||||
|     </a>. | ||||
|     </p> | ||||
| {% endblock %} | ||||
| @@ -1,4 +1,4 @@ | ||||
| {% load static i18n static %} | ||||
| {% load static i18n static calendar %} | ||||
|  | ||||
| <!DOCTYPE html> | ||||
| {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} | ||||
| @@ -70,7 +70,14 @@ | ||||
|                         <a href="#" class="nav-link" data-toggle="modal" data-target="#calendarModal"><i class="fas fa-calendar"></i> {% trans "Calendar" %}</a> | ||||
|                     {% endif %} | ||||
|                 </li> | ||||
|                 {% if user.is_authenticated and user.registration.participates %} | ||||
|                 {% if user.is_authenticated and user.registration.is_admin %} | ||||
|                     <li class="nav-item active"> | ||||
|                         <a href="{% url "registration:user_list" %}" class="nav-link"><i class="fas fa-user"></i> {% trans "Users" %}</a> | ||||
|                     </li> | ||||
|                     <li class="nav-item active"> | ||||
|                         <a href="#" class="nav-link" data-toggle="modal" data-target="#teamsModal"><i class="fas fa-users"></i> {% trans "Teams" %}</a> | ||||
|                     </li> | ||||
|                 {% elif user.is_authenticated and user.registration.participates %} | ||||
|                     {% if not user.registration.team %} | ||||
|                         <li class="nav-item active"> | ||||
|                             <a href="#" class="nav-link" data-toggle="modal" data-target="#createTeamModal"> | ||||
| @@ -122,9 +129,11 @@ | ||||
|                     </li> | ||||
|                 {% endif %} | ||||
|                 {% if not user.is_authenticated %} | ||||
|                     <li class="nav-item active"> | ||||
|                         <a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a> | ||||
|                     </li> | ||||
|                     {% if 1|current_phase %} | ||||
|                         <li class="nav-item active"> | ||||
|                             <a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a> | ||||
|                         </li> | ||||
|                     {% endif %} | ||||
|                     <li class="nav-item active"> | ||||
|                         <a class="nav-link" href="#" data-toggle="modal" data-target="#loginModal"> | ||||
|                             <i class="fas fa-sign-in-alt"></i> {% trans "Log in" %} | ||||
| @@ -183,7 +192,7 @@ | ||||
|                       class="form-inline"> | ||||
|                     <span class="text-muted mr-1"> | ||||
|                         <a target="_blank" href="mailto:contact@correspondances-maths.fr" | ||||
|                            class="text-muted">{% trans "Contact us" %}</a> — | ||||
|                            class="text-muted"><i class="fas fa-envelope"></i> {% trans "Contact us" %}</a> | ||||
|                     </span> | ||||
|                     {% csrf_token %} | ||||
|                     <select title="language" name="language" | ||||
| @@ -198,10 +207,15 @@ | ||||
|                                 {{ lang_name }} ({{ lang_code }}) | ||||
|                             </option> | ||||
|                         {% endfor %} | ||||
|                     </select> | ||||
|                     </select>   | ||||
|                     <noscript> | ||||
|                         <input type="submit"> | ||||
|                     </noscript> | ||||
|                     </noscript>   | ||||
|                     <a target="_blank" class="text-muted" href="{% url "about" %}">{% trans "About" %}</a>   —   | ||||
|                     <a target="_blank" class="text-muted" | ||||
|                        href="https://gitlab.com/animath/si/plateforme-corres2math"> | ||||
|                         <i class="fab fa-gitlab"></i> | ||||
|                     </a> | ||||
|                 </form> | ||||
|             </div> | ||||
|             <div class="col text-right"> | ||||
| @@ -216,21 +230,28 @@ | ||||
|  | ||||
| {% trans "Calendar" as modal_title %} | ||||
| {% include "base_modal.html" with modal_id="calendar" modal_additional_class="modal-lg" %} | ||||
| {% trans "Search results" as modal_title %} | ||||
| {% include "base_modal.html" with modal_id="search" modal_form_method="get" modal_additional_class="modal-lg" %} | ||||
| {% trans "Log in" as modal_title %} | ||||
| {% trans "Log in" as modal_button %} | ||||
| {% url "login" as modal_action %} | ||||
| {% include "base_modal.html" with modal_id="login" %} | ||||
|  | ||||
| {% if user.is_authenticated %} | ||||
|     {% trans "All teams" as modal_title %} | ||||
|     {% include "base_modal.html" with modal_id="teams" modal_additional_class="modal-lg" %} | ||||
|  | ||||
|     {% trans "Search results" as modal_title %} | ||||
|     {% include "base_modal.html" with modal_id="search" modal_form_method="get" modal_additional_class="modal-lg" %} | ||||
|  | ||||
|     {% trans "Join team" as modal_title %} | ||||
|     {% trans "Join" as modal_button %} | ||||
|     {% url "participation:join_team" as modal_action %} | ||||
|  | ||||
|     {% include "base_modal.html" with modal_id="joinTeam" %} | ||||
|     {% trans "Create team" as modal_title %} | ||||
|     {% trans "Create" as modal_button %} | ||||
|     {% url "participation:create_team" as modal_action %} | ||||
|     {% include "base_modal.html" with modal_id="createTeam" modal_button_type="success" %} | ||||
| {% else %} | ||||
|     {% trans "Log in" as modal_title %} | ||||
|     {% trans "Log in" as modal_button %} | ||||
|     {% url "login" as modal_action %} | ||||
|     {% include "base_modal.html" with modal_id="login" %} | ||||
| {% endif %} | ||||
|  | ||||
| <script> | ||||
| @@ -243,26 +264,39 @@ | ||||
|             if (!modalBody.html().trim()) | ||||
|                 modalBody.load("{% url "participation:calendar" %} #form-content") | ||||
|         }); | ||||
|         $('button[data-target="#searchModal"]').click(function() { | ||||
|             let modalBody = $("#searchModal div.modal-body"); | ||||
|             let q = encodeURI($("#search-term").val()); | ||||
|             modalBody.load("{% url "haystack_search" %}?q=" + q + " #search-results"); | ||||
|         }); | ||||
|         {% if user.is_authenticated and user.registration.is_admin %} | ||||
|             $('a[data-target="#teamsModal"]').click(function() { | ||||
|                 let modalBody = $("#teamsModal div.modal-body"); | ||||
|                 if (!modalBody.html().trim()) | ||||
|                     modalBody.load("{% url "participation:team_list" %} #form-content") | ||||
|             }); | ||||
|             $('button[data-target="#searchModal"]').click(function() { | ||||
|                 let modalBody = $("#searchModal div.modal-body"); | ||||
|                 let q = encodeURI($("#search-term").val()); | ||||
|                 modalBody.load("{% url "haystack_search" %}?q=" + q + " #search-results"); | ||||
|             }); | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if not user.is_authenticated %} | ||||
|         $('a[data-target="#loginModal"]').click(function() { | ||||
|             let modalBody = $("#loginModal div.modal-body"); | ||||
|             if (!modalBody.html().trim()) | ||||
|                 modalBody.load("{% url "login" %} #form-content") | ||||
|         }); | ||||
|         $('a[data-target="#createTeamModal"]').click(function() { | ||||
|             let modalBody = $("#createTeamModal div.modal-body"); | ||||
|             if (!modalBody.html().trim()) | ||||
|                 modalBody.load("{% url "participation:create_team" %} #form-content"); | ||||
|         }); | ||||
|         $('a[data-target="#joinTeamModal"]').click(function() { | ||||
|             let modalBody = $("#joinTeamModal div.modal-body"); | ||||
|             if (!modalBody.html().trim()) | ||||
|                 modalBody.load("{% url "participation:join_team" %} #form-content"); | ||||
|         }); | ||||
|                 let modalBody = $("#loginModal div.modal-body"); | ||||
|                 if (!modalBody.html().trim()) | ||||
|                     modalBody.load("{% url "login" %} #form-content") | ||||
|             }); | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if user.is_authenticated and user.registration.participates and not user.registration.team %} | ||||
|             $('a[data-target="#createTeamModal"]').click(function() { | ||||
|                 let modalBody = $("#createTeamModal div.modal-body"); | ||||
|                 if (!modalBody.html().trim()) | ||||
|                     modalBody.load("{% url "participation:create_team" %} #form-content"); | ||||
|             }); | ||||
|             $('a[data-target="#joinTeamModal"]').click(function() { | ||||
|                 let modalBody = $("#joinTeamModal div.modal-body"); | ||||
|                 if (!modalBody.html().trim()) | ||||
|                     modalBody.load("{% url "participation:join_team" %} #form-content"); | ||||
|             }); | ||||
|         {% endif %} | ||||
|     }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,11 @@ | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|         <div class="alert alert-danger"> | ||||
|             Avertissement : certains rencontrent des difficultés à recevoir les mails automatiques. Merci de vérifier | ||||
|             régulièrement votre boîte spam, et à nous contacter en cas de problème. | ||||
|         </div> | ||||
|  | ||||
|     <div class="jumbotron"> | ||||
|         <h5 class="display-4">Comment ça marche ?</h5> | ||||
|         <p> | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import os | ||||
|  | ||||
| from django.core.handlers.asgi import ASGIHandler | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.tokens import PasswordResetTokenGenerator | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| """corres2math URL Configuration | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 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('admin/doc/', include('django.contrib.admindocs.urls')), | ||||
|     path('admin/', admin.site.urls, name="admin"), | ||||
|   | ||||
| @@ -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.core.exceptions import PermissionDenied | ||||
| from haystack.generic_views import SearchView | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| # Copyright (C) 2020 by Animath | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| """ | ||||
| WSGI config for corres2math project. | ||||
|  | ||||
|   | ||||
| @@ -2,10 +2,13 @@ | ||||
|  | ||||
| crond -l 0 | ||||
|  | ||||
| python manage.py migrate | ||||
| python manage.py loaddata initial | ||||
|  | ||||
| nginx | ||||
|  | ||||
| if [ "$CORRES2MATH_STAGE" = "prod" ]; then | ||||
|     gunicorn -b 0.0.0.0:8000 --workers=2 --threads=4 --worker-class=gthread corres2math.wsgi --access-logfile '-' --error-logfile '-'; | ||||
| else | ||||
|     ./manage.py runserver 0.0.0.0:8000; | ||||
|     python manage.py runserver 0.0.0.0:8000; | ||||
| fi | ||||
|   | ||||
| @@ -7,7 +7,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Corres2math\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2020-11-03 17:32+0100\n" | ||||
| "POT-Creation-Date: 2020-12-22 21:30+0100\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -99,17 +99,17 @@ msgstr "changelogs" | ||||
| msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" | ||||
| msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" | ||||
|  | ||||
| #: apps/participation/admin.py:16 apps/participation/models.py:122 | ||||
| #: apps/participation/admin.py:16 apps/participation/models.py:132 | ||||
| #: apps/participation/tables.py:35 apps/participation/tables.py:62 | ||||
| msgid "problem number" | ||||
| msgstr "numéro de problème" | ||||
|  | ||||
| #: apps/participation/admin.py:21 apps/participation/models.py:128 | ||||
| #: apps/participation/models.py:182 | ||||
| #: apps/participation/admin.py:21 apps/participation/models.py:138 | ||||
| #: apps/participation/models.py:192 | ||||
| msgid "valid" | ||||
| msgstr "valide" | ||||
|  | ||||
| #: apps/participation/forms.py:20 apps/participation/models.py:33 | ||||
| #: apps/participation/forms.py:20 apps/participation/models.py:32 | ||||
| msgid "The trigram must be composed of three uppercase letters." | ||||
| msgstr "Le trigramme doit être composé de trois lettres majuscules." | ||||
|  | ||||
| @@ -155,28 +155,28 @@ msgstr "Cette phase doit commencer après les phases précédentes." | ||||
| msgid "This phase must end after the next phases." | ||||
| msgstr "Cette phase doit finir avant les phases suivantes." | ||||
|  | ||||
| #: apps/participation/models.py:26 apps/participation/tables.py:30 | ||||
| #: apps/participation/models.py:25 apps/participation/tables.py:30 | ||||
| #: apps/participation/tables.py:52 apps/participation/tables.py:78 | ||||
| msgid "name" | ||||
| msgstr "nom" | ||||
|  | ||||
| #: apps/participation/models.py:32 apps/participation/tables.py:57 | ||||
| #: apps/participation/models.py:31 apps/participation/tables.py:57 | ||||
| msgid "trigram" | ||||
| msgstr "trigramme" | ||||
|  | ||||
| #: apps/participation/models.py:40 | ||||
| #: apps/participation/models.py:39 | ||||
| msgid "access code" | ||||
| msgstr "code d'accès" | ||||
|  | ||||
| #: apps/participation/models.py:41 | ||||
| #: apps/participation/models.py:40 | ||||
| msgid "The access code let other people to join the team." | ||||
| msgstr "Le code d'accès permet aux autres participants de rejoindre l'équipe." | ||||
|  | ||||
| #: apps/participation/models.py:45 | ||||
| #: apps/participation/models.py:44 | ||||
| msgid "Grant Animath to publish my video" | ||||
| msgstr "Autoriser Animath à publier ma vidéo" | ||||
|  | ||||
| #: apps/participation/models.py:46 | ||||
| #: apps/participation/models.py:45 | ||||
| msgid "" | ||||
| "Give the authorisation to publish the video on the main website to promote " | ||||
| "the action." | ||||
| @@ -184,96 +184,96 @@ msgstr "" | ||||
| "Donner l'autorisation de publier la vidéo sur le site principal pour " | ||||
| "promouvoir les Correspondances." | ||||
|  | ||||
| #: apps/participation/models.py:97 | ||||
| #: apps/participation/models.py:107 | ||||
| #, python-brace-format | ||||
| msgid "Team {name} ({trigram})" | ||||
| msgstr "Équipe {name} ({trigram})" | ||||
|  | ||||
| #: apps/participation/models.py:100 apps/participation/models.py:115 | ||||
| #: apps/participation/models.py:110 apps/participation/models.py:125 | ||||
| #: apps/registration/models.py:106 apps/registration/models.py:155 | ||||
| msgid "team" | ||||
| msgstr "équipe" | ||||
|  | ||||
| #: apps/participation/models.py:101 | ||||
| #: apps/participation/models.py:111 | ||||
| msgid "teams" | ||||
| msgstr "équipes" | ||||
|  | ||||
| #: apps/participation/models.py:119 | ||||
| #: apps/participation/models.py:129 | ||||
| #, python-brace-format | ||||
| msgid "Problem #{problem:d}" | ||||
| msgstr "Problème n°{problem:d}" | ||||
|  | ||||
| #: apps/participation/models.py:129 apps/participation/models.py:183 | ||||
| #: apps/participation/models.py:139 apps/participation/models.py:193 | ||||
| msgid "The video got the validation of the administrators." | ||||
| msgstr "La vidéo a été validée par les administrateurs." | ||||
|  | ||||
| #: apps/participation/models.py:138 | ||||
| #: apps/participation/models.py:148 | ||||
| msgid "solution video" | ||||
| msgstr "vidéo de solution" | ||||
|  | ||||
| #: apps/participation/models.py:147 | ||||
| #: apps/participation/models.py:157 | ||||
| msgid "received participation" | ||||
| msgstr "participation reçue" | ||||
|  | ||||
| #: apps/participation/models.py:156 | ||||
| #: apps/participation/models.py:166 | ||||
| msgid "synthesis video" | ||||
| msgstr "vidéo de synthèse" | ||||
|  | ||||
| #: apps/participation/models.py:163 | ||||
| #: apps/participation/models.py:173 | ||||
| #, python-brace-format | ||||
| msgid "Participation of the team {name} ({trigram})" | ||||
| msgstr "Participation de l'équipe {name} ({trigram})" | ||||
|  | ||||
| #: apps/participation/models.py:166 apps/participation/models.py:240 | ||||
| #: apps/participation/models.py:176 apps/participation/models.py:250 | ||||
| msgid "participation" | ||||
| msgstr "participation" | ||||
|  | ||||
| #: apps/participation/models.py:167 | ||||
| #: apps/participation/models.py:177 | ||||
| msgid "participations" | ||||
| msgstr "participations" | ||||
|  | ||||
| #: apps/participation/models.py:175 | ||||
| #: apps/participation/models.py:185 | ||||
| msgid "link" | ||||
| msgstr "lien" | ||||
|  | ||||
| #: apps/participation/models.py:176 | ||||
| #: apps/participation/models.py:186 | ||||
| msgid "The full video link." | ||||
| msgstr "Le lien complet de la vidéo." | ||||
|  | ||||
| #: apps/participation/models.py:225 | ||||
| #: apps/participation/models.py:235 | ||||
| #, python-brace-format | ||||
| msgid "Video of team {name} ({trigram})" | ||||
| msgstr "Vidéo de l'équipe {name} ({trigram})" | ||||
|  | ||||
| #: apps/participation/models.py:229 | ||||
| #: apps/participation/models.py:239 | ||||
| msgid "video" | ||||
| msgstr "vidéo" | ||||
|  | ||||
| #: apps/participation/models.py:230 | ||||
| #: apps/participation/models.py:240 | ||||
| msgid "videos" | ||||
| msgstr "vidéos" | ||||
|  | ||||
| #: apps/participation/models.py:245 | ||||
| #: apps/participation/models.py:255 | ||||
| msgid "question" | ||||
| msgstr "question" | ||||
|  | ||||
| #: apps/participation/models.py:259 | ||||
| #: apps/participation/models.py:269 | ||||
| msgid "phase number" | ||||
| msgstr "phase" | ||||
|  | ||||
| #: apps/participation/models.py:264 | ||||
| #: apps/participation/models.py:274 | ||||
| msgid "phase description" | ||||
| msgstr "description" | ||||
|  | ||||
| #: apps/participation/models.py:268 | ||||
| #: apps/participation/models.py:278 | ||||
| msgid "start date of the given phase" | ||||
| msgstr "début de la phase" | ||||
|  | ||||
| #: apps/participation/models.py:273 | ||||
| #: apps/participation/models.py:283 | ||||
| msgid "end date of the given phase" | ||||
| msgstr "fin de la phase" | ||||
|  | ||||
| #: apps/participation/models.py:291 | ||||
| #: apps/participation/models.py:299 | ||||
| msgid "" | ||||
| "Phase {phase_number:d} starts on {start:%Y-%m-%d %H:%M} and ends on {end:%Y-" | ||||
| "%m-%d %H:%M}" | ||||
| @@ -281,21 +281,55 @@ msgstr "" | ||||
| "Phase {phase_number:d} démarrant le {start:%d/%m/%Y %H:%M} et finissant le " | ||||
| "{end:%d/%m/%Y %H:%M}" | ||||
|  | ||||
| #: apps/participation/models.py:295 | ||||
| #: apps/participation/models.py:303 | ||||
| msgid "phase" | ||||
| msgstr "phase" | ||||
|  | ||||
| #: apps/participation/models.py:296 | ||||
| #: apps/participation/models.py:304 | ||||
| msgid "phases" | ||||
| msgstr "phases" | ||||
|  | ||||
| #: apps/participation/templates/participation/chat.html:7 | ||||
| msgid "The chat is located on the dedicated Matrix server:" | ||||
| msgstr "Le chat est situé sur le serveur Matrix dédié aux Correspondances :" | ||||
|  | ||||
| #: apps/participation/templates/participation/chat.html:14 | ||||
| msgid "Access to the Matrix server" | ||||
| msgstr "Accéder au serveur Matrix" | ||||
|  | ||||
| #: apps/participation/templates/participation/chat.html:20 | ||||
| msgid "" | ||||
| "To connect to the server, you can select \"Log in\", then use your " | ||||
| "credentials of this platform to connect with the central authentication " | ||||
| "server, then you must trust the connection between the Matrix account and " | ||||
| "the platform. Finally, you will be able to access to the chat platform." | ||||
| msgstr "" | ||||
| "Pour se connecter au serveur, vous pouvez sélectionner \"Se connecter\", " | ||||
| "puis utiliser vos identifiants de cette plateforme via l'authentication " | ||||
| "centralisée. Vous devrez ensuite approuver la connexion entre le compte " | ||||
| "Matrix et la plateforme. Enfin, vous pourrez accéder à la plateforme de chat." | ||||
|  | ||||
| #: apps/participation/templates/participation/chat.html:28 | ||||
| msgid "" | ||||
| "You will be invited in some basic rooms. You must confirm the invitations to " | ||||
| "join channels." | ||||
| msgstr "" | ||||
| "Vous serez invités dans quelques salons basiques. Vous devez confirmer les " | ||||
| "invitations pour rejoindre les canaux." | ||||
|  | ||||
| #: apps/participation/templates/participation/chat.html:34 | ||||
| msgid "If you have any trouble, don't hesitate to contact us :)" | ||||
| msgstr "" | ||||
| "Si vous rencontrez le moindre problème, n'hésitez surtout pas à nous " | ||||
| "contacter :)" | ||||
|  | ||||
| #: apps/participation/templates/participation/create_team.html:11 | ||||
| #: corres2math/templates/base.html:231 | ||||
| #: corres2math/templates/base.html:247 | ||||
| msgid "Create" | ||||
| msgstr "Créer" | ||||
|  | ||||
| #: apps/participation/templates/participation/join_team.html:11 | ||||
| #: corres2math/templates/base.html:227 | ||||
| #: corres2math/templates/base.html:242 | ||||
| msgid "Join" | ||||
| msgstr "Rejoindre" | ||||
|  | ||||
| @@ -469,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:233 | ||||
| #: apps/participation/views.py:482 | ||||
| #: apps/participation/views.py:510 | ||||
| msgid "Upload video" | ||||
| msgstr "Envoyer la vidéo" | ||||
|  | ||||
| @@ -504,7 +538,7 @@ msgid "Update question" | ||||
| msgstr "Modifier la question" | ||||
|  | ||||
| #: apps/participation/templates/participation/participation_detail.html:217 | ||||
| #: apps/participation/views.py:459 | ||||
| #: apps/participation/views.py:486 | ||||
| msgid "Delete question" | ||||
| msgstr "Supprimer la question" | ||||
|  | ||||
| @@ -514,8 +548,8 @@ msgid "Display synthesis" | ||||
| msgstr "Afficher la synthèse" | ||||
|  | ||||
| #: apps/participation/templates/participation/phase_list.html:10 | ||||
| #: apps/participation/views.py:501 corres2math/templates/base.html:68 | ||||
| #: corres2math/templates/base.html:70 corres2math/templates/base.html:217 | ||||
| #: apps/participation/views.py:531 corres2math/templates/base.html:68 | ||||
| #: corres2math/templates/base.html:70 corres2math/templates/base.html:231 | ||||
| msgid "Calendar" | ||||
| msgstr "Calendrier" | ||||
|  | ||||
| @@ -627,7 +661,7 @@ msgid "Update team" | ||||
| msgstr "Modifier l'équipe" | ||||
|  | ||||
| #: apps/participation/templates/participation/team_detail.html:127 | ||||
| #: apps/participation/views.py:314 | ||||
| #: apps/participation/views.py:337 | ||||
| msgid "Leave team" | ||||
| msgstr "Quitter l'équipe" | ||||
|  | ||||
| @@ -635,49 +669,54 @@ msgstr "Quitter l'équipe" | ||||
| msgid "Are you sure that you want to leave this team?" | ||||
| msgstr "Êtes-vous sûr·e de vouloir quitter cette équipe ?" | ||||
|  | ||||
| #: apps/participation/views.py:36 corres2math/templates/base.html:77 | ||||
| #: corres2math/templates/base.html:230 | ||||
| #: apps/participation/templates/participation/team_list.html:6 | ||||
| #: corres2math/templates/base.html:235 | ||||
| msgid "All teams" | ||||
| msgstr "Toutes les équipes" | ||||
|  | ||||
| #: apps/participation/views.py:37 corres2math/templates/base.html:84 | ||||
| #: corres2math/templates/base.html:246 | ||||
| msgid "Create team" | ||||
| 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." | ||||
| 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." | ||||
| msgstr "Vous êtes déjà dans une équipe." | ||||
|  | ||||
| #: apps/participation/views.py:82 corres2math/templates/base.html:82 | ||||
| #: corres2math/templates/base.html:226 | ||||
| #: apps/participation/views.py:85 corres2math/templates/base.html:89 | ||||
| #: corres2math/templates/base.html:241 | ||||
| msgid "Join team" | ||||
| msgstr "Rejoindre une équipe" | ||||
|  | ||||
| #: apps/participation/views.py:133 apps/participation/views.py:320 | ||||
| #: apps/participation/views.py:353 | ||||
| #: apps/participation/views.py:147 apps/participation/views.py:343 | ||||
| #: apps/participation/views.py:376 | ||||
| msgid "You are not in a team." | ||||
| msgstr "Vous n'êtes pas dans une équipe." | ||||
|  | ||||
| #: apps/participation/views.py:134 apps/participation/views.py:354 | ||||
| #: apps/participation/views.py:148 apps/participation/views.py:377 | ||||
| msgid "You don't participate, so you don't have any team." | ||||
| msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." | ||||
|  | ||||
| #: apps/participation/views.py:156 | ||||
| #: apps/participation/views.py:170 | ||||
| #, python-brace-format | ||||
| msgid "Detail of team {trigram}" | ||||
| msgstr "Détails de l'équipe {trigram}" | ||||
|  | ||||
| #: apps/participation/views.py:188 | ||||
| #: apps/participation/views.py:202 | ||||
| msgid "You don't participate, so you can't request the validation of the team." | ||||
| msgstr "" | ||||
| "Vous ne participez pas, vous ne pouvez pas demander la validation de " | ||||
| "l'équipe." | ||||
|  | ||||
| #: apps/participation/views.py:191 | ||||
| #: apps/participation/views.py:205 | ||||
| msgid "The validation of the team is already done or pending." | ||||
| msgstr "La validation de l'équipe est déjà faite ou en cours." | ||||
|  | ||||
| #: apps/participation/views.py:194 | ||||
| #: apps/participation/views.py:208 | ||||
| msgid "" | ||||
| "The team can't be validated: missing email address confirmations, photo " | ||||
| "authorizations, people or the chosen problem is not set." | ||||
| @@ -686,51 +725,51 @@ msgstr "" | ||||
| "d'adresse e-mail, soit une autorisation parentale, soit des personnes soit " | ||||
| "le problème n'a pas été choisi." | ||||
|  | ||||
| #: apps/participation/views.py:213 | ||||
| #: apps/participation/views.py:227 | ||||
| msgid "You are not an administrator." | ||||
| msgstr "Vous n'êtes pas administrateur." | ||||
|  | ||||
| #: apps/participation/views.py:216 | ||||
| #: apps/participation/views.py:230 | ||||
| msgid "This team has no pending validation." | ||||
| msgstr "L'équipe n'a pas de validation en attente." | ||||
|  | ||||
| #: apps/participation/views.py:235 | ||||
| #: apps/participation/views.py:254 | ||||
| msgid "You must specify if you validate the registration or not." | ||||
| msgstr "Vous devez spécifier si vous validez l'inscription ou non." | ||||
|  | ||||
| #: apps/participation/views.py:263 | ||||
| #: apps/participation/views.py:284 | ||||
| #, python-brace-format | ||||
| msgid "Update team {trigram}" | ||||
| msgstr "Mise à jour de l'équipe {trigram}" | ||||
|  | ||||
| #: apps/participation/views.py:300 apps/registration/views.py:243 | ||||
| #: apps/participation/views.py:323 apps/registration/views.py:283 | ||||
| #, python-brace-format | ||||
| msgid "Photo authorization of {student}.{ext}" | ||||
| msgstr "Autorisation de droit à l'image de {student}.{ext}" | ||||
|  | ||||
| #: apps/participation/views.py:304 | ||||
| #: apps/participation/views.py:327 | ||||
| #, python-brace-format | ||||
| msgid "Photo authorizations of team {trigram}.zip" | ||||
| msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" | ||||
|  | ||||
| #: apps/participation/views.py:322 | ||||
| #: apps/participation/views.py:345 | ||||
| msgid "The team is already validated or the validation is pending." | ||||
| msgstr "La validation de l'équipe est déjà faite ou en cours." | ||||
|  | ||||
| #: apps/participation/views.py:366 | ||||
| #: apps/participation/views.py:391 | ||||
| msgid "The team is not validated yet." | ||||
| msgstr "L'équipe n'est pas encore validée." | ||||
|  | ||||
| #: apps/participation/views.py:376 | ||||
| #: apps/participation/views.py:401 | ||||
| #, python-brace-format | ||||
| msgid "Participation of team {trigram}" | ||||
| msgstr "Participation de l'équipe {trigram}" | ||||
|  | ||||
| #: apps/participation/views.py:413 | ||||
| #: apps/participation/views.py:438 | ||||
| msgid "Create question" | ||||
| msgstr "Créer une question" | ||||
|  | ||||
| #: apps/participation/views.py:510 | ||||
| #: apps/participation/views.py:540 | ||||
| msgid "Calendar update" | ||||
| msgstr "Mise à jour du calendrier" | ||||
|  | ||||
| @@ -746,7 +785,15 @@ msgstr "participant" | ||||
| msgid "coach" | ||||
| msgstr "encadrant" | ||||
|  | ||||
| #: apps/registration/forms.py:68 | ||||
| #: apps/registration/forms.py:31 | ||||
| msgid "This email address is already used." | ||||
| msgstr "Cette adresse e-mail est déjà utilisée." | ||||
|  | ||||
| #: 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." | ||||
| msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." | ||||
|  | ||||
| @@ -848,13 +895,21 @@ msgstr "Votre email a été validé avec succès." | ||||
| msgid "You can now <a href=\"%(login_url)s\">log in</a>." | ||||
| msgstr "Vous pouvez désormais vous <a href=\"%(login_url)s\">connecter</a>." | ||||
|  | ||||
| #: apps/registration/templates/registration/email_validation_complete.html:22 | ||||
| #: apps/registration/templates/registration/email_validation_complete.html:23 | ||||
| msgid "" | ||||
| "The link was invalid. The token may have expired. Please send us an email to " | ||||
| "activate your account." | ||||
| "The link was invalid. The token may have expired, or your account is already " | ||||
| "activated. However, your account seems to be already valid." | ||||
| msgstr "" | ||||
| "Le lien est invalide. Le jeton a peut-être expiré. Merci de nous envoyer un " | ||||
| "mail pour activer votre compte." | ||||
| "Le lien est invalide. Le jeton a peut-être expiré, ou votre compte est déjà " | ||||
| "activé. Toutefois, il semble que votre compte est déjà valide." | ||||
|  | ||||
| #: apps/registration/templates/registration/email_validation_complete.html:25 | ||||
| msgid "" | ||||
| "The link was invalid. The token may have expired, or your account is already " | ||||
| "activated. Please send us an email to activate your account." | ||||
| msgstr "" | ||||
| "Le lien est invalide. Le jeton a peut-être expiré, ou votre compte est déjà " | ||||
| "activé. Merci de nous envoyer un mail pour activer votre compte." | ||||
|  | ||||
| #: apps/registration/templates/registration/email_validation_email_sent.html:10 | ||||
| msgid "Account activation" | ||||
| @@ -923,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." | ||||
|  | ||||
| #: apps/registration/templates/registration/password_reset_complete.html:10 | ||||
| #: corres2math/templates/base.html:130 corres2math/templates/base.html:221 | ||||
| #: corres2math/templates/base.html:222 | ||||
| #: corres2math/templates/base.html:139 corres2math/templates/base.html:251 | ||||
| #: corres2math/templates/base.html:252 | ||||
| #: corres2math/templates/registration/login.html:7 | ||||
| #: corres2math/templates/registration/login.html:8 | ||||
| #: corres2math/templates/registration/login.html:25 | ||||
| @@ -981,7 +1036,7 @@ msgstr "Réinitialiser mon mot de passe" | ||||
| #: apps/registration/templates/registration/signup.html:5 | ||||
| #: apps/registration/templates/registration/signup.html:8 | ||||
| #: apps/registration/templates/registration/signup.html:20 | ||||
| #: apps/registration/views.py:28 | ||||
| #: apps/registration/views.py:32 | ||||
| msgid "Sign up" | ||||
| msgstr "Inscription" | ||||
|  | ||||
| @@ -1058,45 +1113,49 @@ msgid "Update user" | ||||
| msgstr "Modifier l'utilisateur" | ||||
|  | ||||
| #: apps/registration/templates/registration/user_detail.html:77 | ||||
| #: apps/registration/views.py:206 | ||||
| #: apps/registration/views.py:246 | ||||
| msgid "Upload photo authorization" | ||||
| msgstr "Téléverser l'autorisation de droit à l'image" | ||||
|  | ||||
| #: apps/registration/views.py:64 | ||||
| #: apps/registration/views.py:40 | ||||
| msgid "You can't register now." | ||||
| msgstr "Vous ne pouvez pas vous inscrire maintenant." | ||||
|  | ||||
| #: apps/registration/views.py:84 | ||||
| msgid "Email validation" | ||||
| msgstr "Validation de l'adresse mail" | ||||
|  | ||||
| #: apps/registration/views.py:66 | ||||
| #: apps/registration/views.py:86 | ||||
| msgid "Validate email" | ||||
| msgstr "Valider l'adresse mail" | ||||
|  | ||||
| #: apps/registration/views.py:105 | ||||
| #: apps/registration/views.py:125 | ||||
| msgid "Email validation unsuccessful" | ||||
| msgstr "Échec de la validation de l'adresse mail" | ||||
|  | ||||
| #: apps/registration/views.py:116 | ||||
| #: apps/registration/views.py:136 | ||||
| msgid "Email validation email sent" | ||||
| msgstr "Mail de confirmation de l'adresse mail envoyé" | ||||
|  | ||||
| #: apps/registration/views.py:124 | ||||
| #: apps/registration/views.py:144 | ||||
| msgid "Resend email validation link" | ||||
| msgstr "Renvoyé le lien de validation de l'adresse mail" | ||||
|  | ||||
| #: apps/registration/views.py:158 | ||||
| #: apps/registration/views.py:180 | ||||
| #, python-brace-format | ||||
| msgid "Detail of user {user}" | ||||
| msgstr "Détails de l'utilisateur {user}" | ||||
|  | ||||
| #: apps/registration/views.py:179 | ||||
| #: apps/registration/views.py:210 | ||||
| #, python-brace-format | ||||
| msgid "Update user {user}" | ||||
| msgstr "Mise à jour de l'utilisateur {user}" | ||||
|  | ||||
| #: corres2math/settings.py:154 | ||||
| #: corres2math/settings.py:157 | ||||
| msgid "English" | ||||
| msgstr "Anglais" | ||||
|  | ||||
| #: corres2math/settings.py:155 | ||||
| #: corres2math/settings.py:158 | ||||
| msgid "French" | ||||
| msgstr "Français" | ||||
|  | ||||
| @@ -1157,43 +1216,51 @@ msgstr "" | ||||
| msgid "Home" | ||||
| msgstr "Accueil" | ||||
|  | ||||
| #: corres2math/templates/base.html:88 | ||||
| #: corres2math/templates/base.html:75 | ||||
| msgid "Users" | ||||
| msgstr "Utilisateurs" | ||||
|  | ||||
| #: corres2math/templates/base.html:78 | ||||
| msgid "Teams" | ||||
| msgstr "Équipes" | ||||
|  | ||||
| #: corres2math/templates/base.html:95 | ||||
| msgid "My team" | ||||
| msgstr "Mon équipe" | ||||
|  | ||||
| #: corres2math/templates/base.html:93 | ||||
| #: corres2math/templates/base.html:100 | ||||
| msgid "My participation" | ||||
| msgstr "Ma participation" | ||||
|  | ||||
| #: corres2math/templates/base.html:100 | ||||
| #: corres2math/templates/base.html:107 | ||||
| msgid "Chat" | ||||
| msgstr "Chat" | ||||
|  | ||||
| #: corres2math/templates/base.html:104 | ||||
| #: corres2math/templates/base.html:111 | ||||
| msgid "Administration" | ||||
| msgstr "Administration" | ||||
|  | ||||
| #: corres2math/templates/base.html:112 | ||||
| #: corres2math/templates/base.html:119 | ||||
| msgid "Search..." | ||||
| msgstr "Chercher ..." | ||||
|  | ||||
| #: corres2math/templates/base.html:121 | ||||
| #: corres2math/templates/base.html:128 | ||||
| msgid "Return to admin view" | ||||
| msgstr "Retourner à l'interface administrateur" | ||||
|  | ||||
| #: corres2math/templates/base.html:126 | ||||
| #: corres2math/templates/base.html:134 | ||||
| msgid "Register" | ||||
| msgstr "S'inscrire" | ||||
|  | ||||
| #: corres2math/templates/base.html:142 | ||||
| #: corres2math/templates/base.html:151 | ||||
| msgid "My account" | ||||
| msgstr "Mon compte" | ||||
|  | ||||
| #: corres2math/templates/base.html:145 | ||||
| #: corres2math/templates/base.html:154 | ||||
| msgid "Log out" | ||||
| msgstr "Déconnexion" | ||||
|  | ||||
| #: corres2math/templates/base.html:162 | ||||
| #: corres2math/templates/base.html:171 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "Your email address is not validated. Please click on the link you received " | ||||
| @@ -1204,11 +1271,15 @@ msgstr "" | ||||
| "avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a href=" | ||||
| "\"%(send_email_url)s\">ce lien</a>." | ||||
|  | ||||
| #: corres2math/templates/base.html:186 | ||||
| #: corres2math/templates/base.html:195 | ||||
| msgid "Contact us" | ||||
| msgstr "Nous contacter" | ||||
|  | ||||
| #: corres2math/templates/base.html:219 | ||||
| #: corres2math/templates/base.html:214 | ||||
| msgid "About" | ||||
| msgstr "À propos" | ||||
|  | ||||
| #: corres2math/templates/base.html:238 | ||||
| msgid "Search results" | ||||
| msgstr "Résultats de la recherche" | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ upstream corres2math { | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name corres2math; | ||||
|     client_max_body_size 50M; | ||||
|  | ||||
|     location / { | ||||
|             proxy_pass http://corres2math; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| Django~=3.1 | ||||
| Django~=3.0 | ||||
| django-bootstrap-datepicker-plus~=3.0 | ||||
| django-cas-server~=1.2 | ||||
| django-crispy-forms~=1.9 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user