diff --git a/apps/participation/management/commands/fix_matrix_channels.py b/apps/participation/management/commands/fix_matrix_channels.py index 764b41e..7ce878c 100644 --- a/apps/participation/management/commands/fix_matrix_channels.py +++ b/apps/participation/management/commands/fix_matrix_channels.py @@ -16,10 +16,8 @@ class Command(BaseCommand): if not os.path.isfile(".matrix_avatar"): stat_file = os.stat("corres2math/static/logo.png") with open("corres2math/static/logo.png", "rb") as f: - resp, _ = Matrix.upload(f, filename="logo.png", content_type="image/png", - filesize=stat_file.st_size) - if not hasattr(resp, "content_uri"): - raise Exception(resp) + resp = Matrix.upload(f, filename="logo.png", content_type="image/png", + filesize=stat_file.st_size)[0][0] avatar_uri = resp.content_uri with open(".matrix_avatar", "w") as f: f.write(avatar_uri) @@ -58,9 +56,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) @@ -69,9 +78,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) diff --git a/apps/participation/management/commands/fix_sympa_lists.py b/apps/participation/management/commands/fix_sympa_lists.py new file mode 100644 index 0000000..63152b5 --- /dev/null +++ b/apps/participation/management/commands/fix_sympa_lists.py @@ -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}") diff --git a/apps/participation/models.py b/apps/participation/models.py index 8b6af3e..55e808c 100644 --- a/apps/participation/models.py +++ b/apps/participation/models.py @@ -65,11 +65,22 @@ class Team(models.Model): "education", 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): """ 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}") 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()) 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}")\ diff --git a/apps/participation/signals.py b/apps/participation/signals.py index a7ee7d4..67998d9 100644 --- a/apps/participation/signals.py +++ b/apps/participation/signals.py @@ -2,7 +2,7 @@ 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 +12,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, **_): diff --git a/apps/participation/templatetags/__init__.py b/apps/participation/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/participation/templatetags/calendar.py b/apps/participation/templatetags/calendar.py new file mode 100644 index 0000000..62cc0aa --- /dev/null +++ b/apps/participation/templatetags/calendar.py @@ -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) diff --git a/apps/participation/tests.py b/apps/participation/tests.py index 47d2ad2..b1b7bd2 100644 --- a/apps/participation/tests.py +++ b/apps/participation/tests.py @@ -582,7 +582,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): diff --git a/apps/participation/views.py b/apps/participation/views.py index 7c03eb4..5534278 100644 --- a/apps/participation/views.py +++ b/apps/participation/views.py @@ -232,6 +232,11 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView) mail_plain = render_to_string("participation/mails/team_validated.txt", 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) + + 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: self.object.participation.valid = None self.object.participation.save() diff --git a/apps/registration/fixtures/initial.json b/apps/registration/fixtures/initial.json index 40afb61..209fc21 100644 --- a/apps/registration/fixtures/initial.json +++ b/apps/registration/fixtures/initial.json @@ -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 + } } ] diff --git a/apps/registration/signals.py b/apps/registration/signals.py index 71b761e..0a6edec 100644 --- a/apps/registration/signals.py +++ b/apps/registration/signals.py @@ -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("#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") diff --git a/apps/registration/tests.py b/apps/registration/tests.py index 4c464c9..e43aabc 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -1,3 +1,4 @@ +from datetime import timedelta import os 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.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 from .models import AdminRegistration, CoachRegistration, StudentRegistration @@ -81,6 +84,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) diff --git a/apps/registration/views.py b/apps/registration/views.py index 91d13b6..7fe6707 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -13,6 +13,7 @@ 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 magic import Magic +from participation.models import Phase from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm from .models import StudentRegistration @@ -27,6 +28,15 @@ 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() diff --git a/corres2math/matrix.py b/corres2math/matrix.py index d01e44f..a6265a9 100644 --- a/corres2math/matrix.py +++ b/corres2math/matrix.py @@ -216,7 +216,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 diff --git a/corres2math/templates/base.html b/corres2math/templates/base.html index 33a0211..9a6611e 100644 --- a/corres2math/templates/base.html +++ b/corres2math/templates/base.html @@ -1,4 +1,4 @@ -{% load static i18n static %} +{% load static i18n static calendar %} {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} @@ -126,9 +126,11 @@ {% endif %} {% if not user.is_authenticated %} - + {% if 1|current_phase %} + + {% endif %}