mirror of
https://gitlab.com/animath/si/plateforme-corres2math.git
synced 2025-07-01 14:41:16 +02: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
|
||||
@ -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 %}
|
||||
{% 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,12 +261,13 @@
|
||||
});
|
||||
{% 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() {
|
||||
@ -274,13 +276,14 @@
|
||||
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 %}
|
||||
{% 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,7 +75,10 @@ class PhotoAuthorizationForm(forms.ModelForm):
|
||||
Form to send a photo authorization.
|
||||
"""
|
||||
def clean_photo_authorization(self):
|
||||
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"]
|
||||
@ -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 %}
|
||||
{% 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,16 +264,28 @@
|
||||
if (!modalBody.html().trim())
|
||||
modalBody.load("{% url "participation:calendar" %} #form-content")
|
||||
});
|
||||
{% 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")
|
||||
});
|
||||
{% 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())
|
||||
@ -263,6 +296,7 @@
|
||||
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