Merge branch 'master' into 'improvements'

# Conflicts:
#   locale/fr/LC_MESSAGES/django.po
This commit is contained in:
Yohann D'ANELLO 2020-11-16 11:01:19 +00:00
commit 982b61fe03
15 changed files with 138 additions and 24 deletions

View File

@ -16,10 +16,8 @@ class Command(BaseCommand):
if not os.path.isfile(".matrix_avatar"): if not os.path.isfile(".matrix_avatar"):
stat_file = os.stat("corres2math/static/logo.png") stat_file = os.stat("corres2math/static/logo.png")
with open("corres2math/static/logo.png", "rb") as f: with open("corres2math/static/logo.png", "rb") as f:
resp, _ = Matrix.upload(f, filename="logo.png", content_type="image/png", resp = Matrix.upload(f, filename="logo.png", content_type="image/png",
filesize=stat_file.st_size) filesize=stat_file.st_size)[0][0]
if not hasattr(resp, "content_uri"):
raise Exception(resp)
avatar_uri = resp.content_uri avatar_uri = resp.content_uri
with open(".matrix_avatar", "w") as f: with open(".matrix_avatar", "w") as f:
f.write(avatar_uri) f.write(avatar_uri)
@ -58,9 +56,20 @@ class Command(BaseCommand):
preset=RoomPreset.public_chat, 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("#annonces:correspondances-maths.fr", avatar_uri)
Matrix.set_room_avatar("#faq: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("#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) Matrix.set_room_power_level_event("#annonces:correspondances-maths.fr", "events_default", 50)
@ -69,9 +78,12 @@ class Command(BaseCommand):
Matrix.invite("#faq:correspondances-maths.fr", f"@{r.matrix_username}:correspondances-maths.fr") Matrix.invite("#faq:correspondances-maths.fr", f"@{r.matrix_username}:correspondances-maths.fr")
Matrix.invite("#je-cherche-une-equipe:correspondances-maths.fr", Matrix.invite("#je-cherche-une-equipe:correspondances-maths.fr",
f"@{r.matrix_username}: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(): for admin in AdminRegistration.objects.all():
Matrix.set_room_power_level("#annonces:correspondances-maths.fr", Matrix.set_room_power_level("#annonces:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95) f"@{admin.matrix_username}:correspondances-maths.fr", 95)
Matrix.set_room_power_level("#faq:correspondances-maths.fr", Matrix.set_room_power_level("#faq:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95) f"@{admin.matrix_username}:correspondances-maths.fr", 95)
Matrix.set_room_power_level("#flood:correspondances-maths.fr",
f"@{admin.matrix_username}:correspondances-maths.fr", 95)

View File

@ -0,0 +1,38 @@
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
class Command(BaseCommand):
def handle(self, *args, **options):
"""
Create Sympa mailing lists and register teams.
"""
sympa = get_sympa_client()
sympa.create_list("equipes", "Équipes des Correspondances", "hotline",
"Liste de diffusion pour contacter toutes les équipes validées des Correspondances.",
"education", raise_error=False)
sympa.create_list("equipes-non-valides", "Équipes des Correspondances", "hotline",
"Liste de diffusion pour contacter toutes les équipes non-validées des Correspondances.",
"education", raise_error=False)
for problem in range(1, 4):
sympa.create_list(f"probleme-{problem}",
f"Équipes des Correspondances participant au problème {problem}", "hotline",
f"Liste de diffusion pour contacter les équipes participant au problème {problem}"
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)
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():
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}")
for coach in CoachRegistration.objects.filter(team__isnull=False).all():
sympa.subscribe(coach.user.email, f"equipe-{coach.team.trigram.lower}", True, f"{coach}")

View File

@ -65,11 +65,22 @@ class Team(models.Model):
"education", "education",
raise_error=False, raise_error=False,
) )
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}")
else:
get_sympa_client().subscribe(self.email, "equipes-non-valides", False)
def delete_mailing_list(self): def delete_mailing_list(self):
""" """
Drop the Sympa mailing list, if the team is empty or if the trigram changed. Drop the Sympa mailing list, if the team is empty or if the trigram changed.
""" """
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:
get_sympa_client().unsubscribe(self.email, "equipes-non-valides", False)
get_sympa_client().delete_list(f"equipe-{self.trigram}") get_sympa_client().delete_list(f"equipe-{self.trigram}")
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -281,10 +292,8 @@ class Phase(models.Model):
qs = Phase.objects.filter(start__lte=timezone.now(), end__gte=timezone.now()) qs = Phase.objects.filter(start__lte=timezone.now(), end__gte=timezone.now())
if qs.exists(): if qs.exists():
return qs.get() return qs.get()
qs = Phase.objects.order_by("phase_number").all() qs = Phase.objects.filter(start__lte=timezone.now()).order_by("phase_number").all()
if timezone.now() < qs.first().start: return qs.last() if qs.exists() else None
return qs.first()
return qs.last()
def __str__(self): 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}")\ return _("Phase {phase_number:d} starts on {start:%Y-%m-%d %H:%M} and ends on {end:%Y-%m-%d %H:%M}")\

View File

@ -2,7 +2,7 @@ from corres2math.lists import get_sympa_client
from participation.models import Participation, Team, Video 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. When a team got created, create an associated team and create Video objects.
""" """
@ -12,6 +12,8 @@ def create_team_participation(instance, **_):
if not participation.synthesis: if not participation.synthesis:
participation.synthesis = Video.objects.create() participation.synthesis = Video.objects.create()
participation.save() participation.save()
if not created:
participation.team.create_mailing_list()
def update_mailing_list(instance: Team, **_): def update_mailing_list(instance: Team, **_):

View File

@ -0,0 +1,12 @@
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)

View File

@ -582,7 +582,7 @@ class TestStudentParticipation(TestCase):
for i in range(1, 5): for i in range(1, 5):
Phase.objects.filter(phase_number=i).update(start=timezone.now() + timedelta(days=2 * i), Phase.objects.filter(phase_number=i).update(start=timezone.now() + timedelta(days=2 * i),
end=timezone.now() + timedelta(days=2 * i + 1)) 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 # We are after the end
for i in range(1, 5): for i in range(1, 5):

View File

@ -232,6 +232,11 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context) mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context)
mail_html = render_to_string("participation/mails/team_validated.html", mail_context) mail_html = render_to_string("participation/mails/team_validated.html", mail_context)
send_mail("[Corres2math] Équipe validée", mail_plain, None, [self.object.email], html_message=mail_html) send_mail("[Corres2math] Équipe validée", mail_plain, None, [self.object.email], html_message=mail_html)
get_sympa_client().subscribe(self.object.email, "equipes", False, f"Equipe {self.object.name}")
get_sympa_client().unsubscribe(self.object.email, "equipes-non-valides", False)
get_sympa_client().subscribe(self.object.email, f"probleme-{self.object.participation.problem}", False,
f"Equipe {self.object.name}")
elif "invalidate" in self.request.POST: elif "invalidate" in self.request.POST:
self.object.participation.valid = None self.object.participation.valid = None
self.object.participation.save() self.object.participation.save()

View File

@ -13,5 +13,14 @@
"single_log_out": true, "single_log_out": true,
"single_log_out_callback": "" "single_log_out_callback": ""
} }
},
{
"model": "cas_server.replaceattributname",
"pk": 1,
"fields": {
"name": "display_name",
"replace": "",
"service_pattern": 1
}
} }
] ]

View File

@ -50,3 +50,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("#faq:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr")
Matrix.invite("#je-cherche-une-equip:correspondances-maths.fr", Matrix.invite("#je-cherche-une-equip:correspondances-maths.fr",
f"@{instance.matrix_username}:correspondances-maths.fr") f"@{instance.matrix_username}:correspondances-maths.fr")
Matrix.invite("#flood:correspondances-maths.fr", f"@{instance.matrix_username}:correspondances-maths.fr")

View File

@ -1,3 +1,4 @@
from datetime import timedelta
import os import os
from corres2math.tokens import email_validation_token from corres2math.tokens import email_validation_token
@ -7,8 +8,10 @@ from django.contrib.sites.models import Site
from django.core.management import call_command from django.core.management import call_command
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
from participation.models import Phase
from .models import AdminRegistration, CoachRegistration, StudentRegistration from .models import AdminRegistration, CoachRegistration, StudentRegistration
@ -81,6 +84,13 @@ class TestRegistration(TestCase):
""" """
Ensure that the signup form is working successfully. 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")) response = self.client.get(reverse("registration:signup"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@ -13,6 +13,7 @@ from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
from magic import Magic from magic import Magic
from participation.models import Phase
from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm
from .models import StudentRegistration from .models import StudentRegistration
@ -27,6 +28,15 @@ class SignupView(CreateView):
template_name = "registration/signup.html" template_name = "registration/signup.html"
extra_context = dict(title=_("Sign up")) 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): def get_context_data(self, **kwargs):
context = super().get_context_data() context = super().get_context_data()

View File

@ -216,7 +216,7 @@ class Matrix:
""" """
client = await cls._get_client() client = await cls._get_client()
resp = await client.room_resolve_alias(room_alias) 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 @classmethod
@async_to_sync @async_to_sync

View File

@ -1,4 +1,4 @@
{% load static i18n static %} {% load static i18n static calendar %}
<!DOCTYPE html> <!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
@ -126,9 +126,11 @@
</li> </li>
{% endif %} {% endif %}
{% if not user.is_authenticated %} {% if not user.is_authenticated %}
<li class="nav-item active"> {% if 1|current_phase %}
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a> <li class="nav-item active">
</li> <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"> <li class="nav-item active">
<a class="nav-link" href="#" data-toggle="modal" data-target="#loginModal"> <a class="nav-link" href="#" data-toggle="modal" data-target="#loginModal">
<i class="fas fa-sign-in-alt"></i> {% trans "Log in" %} <i class="fas fa-sign-in-alt"></i> {% trans "Log in" %}

View File

@ -986,7 +986,7 @@ msgstr "Réinitialiser mon mot de passe"
#: apps/registration/templates/registration/signup.html:5 #: apps/registration/templates/registration/signup.html:5
#: apps/registration/templates/registration/signup.html:8 #: apps/registration/templates/registration/signup.html:8
#: apps/registration/templates/registration/signup.html:20 #: apps/registration/templates/registration/signup.html:20
#: apps/registration/views.py:28 #: apps/registration/views.py:29
msgid "Sign up" msgid "Sign up"
msgstr "Inscription" msgstr "Inscription"
@ -1063,36 +1063,40 @@ msgid "Update user"
msgstr "Modifier l'utilisateur" msgstr "Modifier l'utilisateur"
#: apps/registration/templates/registration/user_detail.html:77 #: apps/registration/templates/registration/user_detail.html:77
#: apps/registration/views.py:206 #: apps/registration/views.py:216
msgid "Upload photo authorization" msgid "Upload photo authorization"
msgstr "Téléverser l'autorisation de droit à l'image" msgstr "Téléverser l'autorisation de droit à l'image"
#: apps/registration/views.py:64 #: apps/registration/views.py:37
msgid "You can't register now."
msgstr "Vous ne pouvez pas vous inscrire maintenant."
#: apps/registration/views.py:74
msgid "Email validation" msgid "Email validation"
msgstr "Validation de l'adresse mail" msgstr "Validation de l'adresse mail"
#: apps/registration/views.py:66 #: apps/registration/views.py:76
msgid "Validate email" msgid "Validate email"
msgstr "Valider l'adresse mail" msgstr "Valider l'adresse mail"
#: apps/registration/views.py:105 #: apps/registration/views.py:115
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "Échec de la validation de l'adresse mail" msgstr "Échec de la validation de l'adresse mail"
#: apps/registration/views.py:116 #: apps/registration/views.py:126
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "Mail de confirmation de l'adresse mail envoyé" msgstr "Mail de confirmation de l'adresse mail envoyé"
#: apps/registration/views.py:124 #: apps/registration/views.py:134
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "Renvoyé le lien de validation de l'adresse mail" msgstr "Renvoyé le lien de validation de l'adresse mail"
#: apps/registration/views.py:158 #: apps/registration/views.py:168
#, python-brace-format #, python-brace-format
msgid "Detail of user {user}" msgid "Detail of user {user}"
msgstr "Détails de l'utilisateur {user}" msgstr "Détails de l'utilisateur {user}"
#: apps/registration/views.py:179 #: apps/registration/views.py:189
#, python-brace-format #, python-brace-format
msgid "Update user {user}" msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur {user}" msgstr "Mise à jour de l'utilisateur {user}"