mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-02-25 17:46:30 +00:00
Compare commits
23 Commits
2f4755ffc7
...
9ec35c917f
Author | SHA1 | Date | |
---|---|---|---|
|
9ec35c917f | ||
|
7919b34d2b | ||
|
c5a8581a80 | ||
|
e031e143c2 | ||
|
3964aaf595 | ||
|
202f979403 | ||
|
cf561c4584 | ||
|
e2679cf5e8 | ||
|
122edeef48 | ||
|
4ff9f44eae | ||
|
5d13d9bc16 | ||
|
121e1da37d | ||
|
8222f3b781 | ||
|
dc56396012 | ||
|
f1d2acdc25 | ||
|
50e95ad3f2 | ||
|
7848a90d5d | ||
|
f08cb229ca | ||
|
b0fbb406f6 | ||
|
0f2f34175c | ||
|
6226f06d97 | ||
|
a853be73c5 | ||
|
93a2e2436d |
@ -2,22 +2,24 @@ stages:
|
||||
- test
|
||||
- quality-assurance
|
||||
|
||||
py310:
|
||||
stage: test
|
||||
image: python:3.10-alpine
|
||||
before_script:
|
||||
- apk add --no-cache libmagic
|
||||
- pip install tox --no-cache-dir
|
||||
script: tox -e py310
|
||||
|
||||
py311:
|
||||
stage: test
|
||||
image: python:3.11-alpine
|
||||
before_script:
|
||||
- apk add --no-cache libmagic
|
||||
- apk add --no-cache git # Useful for django-haystack, remove when the newer versions are in PyPI
|
||||
- pip install tox --no-cache-dir
|
||||
script: tox -e py311
|
||||
|
||||
py312:
|
||||
stage: test
|
||||
image: python:3.12-alpine
|
||||
before_script:
|
||||
- apk add --no-cache libmagic
|
||||
- apk add --no-cache git # Useful for django-haystack, remove when the newer versions are in PyPI
|
||||
- pip install tox --no-cache-dir
|
||||
script: tox -e py312
|
||||
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: python:3-alpine
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.11-alpine
|
||||
FROM python:3.12-alpine
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV DJANGO_ALLOW_ASYNC_UNSAFE 1
|
||||
@ -13,8 +13,6 @@ COPY requirements.txt /code/requirements.txt
|
||||
COPY docs/requirements.txt /code/docs/requirements.txt
|
||||
RUN pip install -r requirements.txt --no-cache-dir
|
||||
RUN pip install -r docs/requirements.txt --no-cache-dir
|
||||
# FIXME Remove this line when all dependencies will be ready
|
||||
RUN pip install "Django>=4.2,<5.0"
|
||||
|
||||
COPY . /code/
|
||||
|
||||
|
@ -54,14 +54,13 @@ SERVER_EMAIL=contact@tfjm.org # Adresse e-mail expéditrice
|
||||
SYMPA_URL=lists.example.com # Serveur Sympa à utiliser
|
||||
SYMPA_EMAIL= # Adresse e-mail du compte administrateur de Sympa
|
||||
SYMPA_PASSWORD= # Mot de passe du compte administrateur de Sympa
|
||||
SYNAPSE_PASSWORD= # Mot de passe du robot Matrix
|
||||
```
|
||||
|
||||
Si le type de base de données sélectionné est SQLite, la variable `DJANGO_DB_HOST` sera utilisée en guise de chemin vers
|
||||
le fichier de base de données (par défaut, `db.sqlite3`).
|
||||
|
||||
En développement, il est recommandé d'utiliser SQLite pour des raisons de simplicité. Les paramètres de mail ne seront
|
||||
pas utilisés, et les mails qui doivent être envoyés seront envoyés dans la console. Les intégrations mail et Matrix
|
||||
pas utilisés, et les mails qui doivent être envoyés seront envoyés dans la console. L'intégration mail
|
||||
seront également désactivées.
|
||||
|
||||
En production, il est recommandé de ne pas utiliser SQLite pour des raisons de performances.
|
||||
|
@ -9,7 +9,7 @@ leurs solutions.
|
||||
Si vous êtes ici, c'est que vous avez des questions sur l'utilisation du site en tant que participant⋅e.
|
||||
|
||||
Les inscriptions ouvrent dans le courant du mois de janvier, généralement une semaine après la publication
|
||||
des problèmes. Pour l'édition 2023, les inscriptions ouvrent le 11 janvier 2023.
|
||||
des problèmes. Pour l'édition 2024, les inscriptions ouvrent le 17 janvier 2024.
|
||||
|
||||
Tout se passe sur le site https://inscription.tfjm.org/.
|
||||
|
||||
|
31
draw/migrations/0002_alter_teamdraw_purposed.py
Normal file
31
draw/migrations/0002_alter_teamdraw_purposed.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-13 16:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("draw", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="teamdraw",
|
||||
name="purposed",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Problem #1"),
|
||||
(2, "Problem #2"),
|
||||
(3, "Problem #3"),
|
||||
(4, "Problem #4"),
|
||||
(5, "Problem #5"),
|
||||
(6, "Problem #6"),
|
||||
(7, "Problem #7"),
|
||||
(8, "Problem #8"),
|
||||
],
|
||||
default=None,
|
||||
null=True,
|
||||
verbose_name="purposed problem",
|
||||
),
|
||||
),
|
||||
]
|
@ -4,6 +4,7 @@ crond -l 0
|
||||
|
||||
python manage.py migrate
|
||||
python manage.py loaddata initial
|
||||
python manage.py update_index
|
||||
|
||||
nginx
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,477 +0,0 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import activate
|
||||
from participation.models import Team, Tournament
|
||||
from registration.models import Registration, VolunteerRegistration
|
||||
from tfjm.matrix import Matrix, RoomPreset, RoomVisibility
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options): # noqa: C901
|
||||
activate("fr")
|
||||
|
||||
async def main():
|
||||
await Matrix.set_display_name("Bot du TFJM²")
|
||||
|
||||
if not os.getenv("SYNAPSE_PASSWORD"):
|
||||
avatar_uri = "plop"
|
||||
else: # pragma: no cover
|
||||
if not os.path.isfile(".matrix_avatar"):
|
||||
avatar_uri = await Matrix.get_avatar()
|
||||
if isinstance(avatar_uri, str):
|
||||
with open(".matrix_avatar", "w") as f:
|
||||
f.write(avatar_uri)
|
||||
else:
|
||||
stat_file = os.stat("tfjm/static/logo.png")
|
||||
with open("tfjm/static/logo.png", "rb") as f:
|
||||
resp = (await Matrix.upload(f, filename="../../../tfjm/static/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)
|
||||
await Matrix.set_avatar(avatar_uri)
|
||||
|
||||
with open(".matrix_avatar", "r") as f:
|
||||
avatar_uri = f.read().rstrip(" \t\r\n")
|
||||
|
||||
# Create basic channels
|
||||
if not await Matrix.resolve_room_alias("#aide-jurys-orgas:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="aide-jurys-orgas",
|
||||
name="Aide jurys & orgas",
|
||||
topic="Pour discuter de propblèmes d'organisation",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#annonces:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="annonces",
|
||||
name="Annonces",
|
||||
topic="Informations importantes du TFJM²",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#bienvenue:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="bienvenue",
|
||||
name="Bienvenue",
|
||||
topic="Bienvenue au TFJM² 2023 !",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#bot:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="bot",
|
||||
name="Bot",
|
||||
topic="Vive les r0b0ts",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#cno:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="cno",
|
||||
name="CNO",
|
||||
topic="Channel des dieux",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#dev-bot:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="dev-bot",
|
||||
name="Bot - développement",
|
||||
topic="Vive le bot",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#faq:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="faq",
|
||||
name="FAQ",
|
||||
topic="Posez toutes vos questions ici !",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#flood:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="flood",
|
||||
name="Flood",
|
||||
topic="Discutez de tout et de rien !",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias("#je-cherche-une-equipe:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias="je-cherche-une-equipe",
|
||||
name="Je cherche une équipe",
|
||||
topic="Le Tinder du TFJM²",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
)
|
||||
|
||||
# Setup avatars
|
||||
await Matrix.set_room_avatar("#aide-jurys-orgas:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#annonces:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#bienvenue:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#bot:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#cno:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#dev-bot:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#faq:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#flood:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar("#je-cherche-une-equipe:tfjm.org", avatar_uri)
|
||||
|
||||
# Read-only channels
|
||||
await Matrix.set_room_power_level_event("#annonces:tfjm.org", "events_default", 50)
|
||||
await Matrix.set_room_power_level_event("#bienvenue:tfjm.org", "events_default", 50)
|
||||
|
||||
# Invite everyone to public channels
|
||||
for r in Registration.objects.all():
|
||||
await Matrix.invite("#annonces:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#bienvenue:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#bot:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#faq:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#flood:tfjm.org", f"@{r.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#je-cherche-une-equipe:tfjm.org",
|
||||
f"@{r.matrix_username}:tfjm.org")
|
||||
self.stdout.write(f"Invite {r} in most common channels...")
|
||||
|
||||
# Volunteers have access to the help channel
|
||||
for volunteer in VolunteerRegistration.objects.all():
|
||||
await Matrix.invite("#aide-jurys-orgas:tfjm.org", f"@{volunteer.matrix_username}:tfjm.org")
|
||||
self.stdout.write(f"Invite {volunteer} in #aide-jury-orgas...")
|
||||
|
||||
# Admins are admins
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
self.stdout.write(f"Invite {admin} in #cno and #dev-bot...")
|
||||
await Matrix.invite("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
|
||||
self.stdout.write(f"Give admin permissions for {admin}...")
|
||||
await Matrix.set_room_power_level("#aide-jurys-orgas:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#annonces:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#bienvenue:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#bot:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#cno:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#dev-bot:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#faq:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#flood:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level("#je-cherche-une-equipe:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
|
||||
# Create tournament-specific channels
|
||||
for tournament in Tournament.objects.all():
|
||||
self.stdout.write(f"Managing tournament of {tournament.name}.")
|
||||
|
||||
name = tournament.name
|
||||
slug = name.lower().replace(" ", "-")
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#annonces-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"annonces-{slug}",
|
||||
name=f"{name} - Annonces",
|
||||
topic=f"Annonces du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#general-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"general-{slug}",
|
||||
name=f"{name} - Général",
|
||||
topic=f"Accueil du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#flood-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"flood-{slug}",
|
||||
name=f"{name} - Flood",
|
||||
topic=f"Discussion libre du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#jury-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"jury-{slug}",
|
||||
name=f"{name} - Jury",
|
||||
topic=f"Discussion entre les orgas et jurys du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#orga-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"orga-{slug}",
|
||||
name=f"{name} - Organisateurs",
|
||||
topic=f"Discussion entre les orgas du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
if not await Matrix.resolve_room_alias(f"#tirage-au-sort-{slug}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"tirage-au-sort-{slug}",
|
||||
name=f"{name} - Tirage au sort",
|
||||
topic=f"Tirage au sort du tournoi de {name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
# Setup avatars
|
||||
await Matrix.set_room_avatar(f"#annonces-{slug}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#flood-{slug}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#general-{slug}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#jury-{slug}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#orga-{slug}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#tirage-au-sort-{slug}:tfjm.org", avatar_uri)
|
||||
|
||||
# Invite admins and give permissions
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
self.stdout.write(f"Invite {admin} in all channels of the tournament {name}...")
|
||||
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
|
||||
self.stdout.write(f"Give permissions to {admin} in all channels of the tournament {name}...")
|
||||
await Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#general-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
|
||||
# Invite organizers and give permissions
|
||||
for orga in tournament.organizers.all():
|
||||
self.stdout.write(f"Invite organizer {orga} in all channels of the tournament {name}...")
|
||||
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org", f"@{orga.matrix_username}:tfjm.org")
|
||||
|
||||
if not orga.is_admin:
|
||||
await Matrix.set_room_power_level(f"#annonces-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#flood-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#general-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#orga-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
|
||||
# Invite participants
|
||||
for participation in tournament.participations.filter(valid=True).all():
|
||||
for participant in participation.team.participants.all():
|
||||
self.stdout.write(f"Invite {participant} in public channels of the tournament {name}...")
|
||||
await Matrix.invite(f"#annonces-{slug}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#flood-{slug}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#general-{slug}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
|
||||
# Create pool-specific channels
|
||||
for pool in tournament.pools.all():
|
||||
self.stdout.write(f"Managing {pool}...")
|
||||
five = pool.participations.count() >= 5
|
||||
for i in range(2 if five else 1):
|
||||
# Fix for five teams-pools
|
||||
suffix = f"-{chr(ord('A') + i)}" if five else ""
|
||||
if not await Matrix.resolve_room_alias(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"poule-{slug}-{pool.id}{suffix}",
|
||||
name=f"{name} - Jour {pool.round} - Poule " +
|
||||
', '.join(participation.team.trigram
|
||||
for participation in pool.participations.all()) + suffix,
|
||||
topic=f"Discussion avec les équipes - {pool}{suffix}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
if not await Matrix.resolve_room_alias(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"poule-{slug}-{pool.id}{suffix}-jurys",
|
||||
name=f"{name} - Jour {pool.round}{suffix} - Jurys poule " +
|
||||
', '.join(participation.team.trigram
|
||||
for participation in pool.participations.all()) + suffix,
|
||||
topic=f"Discussion avec les jurys - {pool}{suffix}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
await Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org", avatar_uri)
|
||||
await Matrix.set_room_avatar(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org", avatar_uri)
|
||||
|
||||
bbb_url = pool.bbb_url.strip()
|
||||
if five and ';' in bbb_url:
|
||||
bbb_url = bbb_url.split(";")[i].strip()
|
||||
url_params = urlencode(dict(url=bbb_url,
|
||||
isAudioConf='false', displayName='$matrix_display_name',
|
||||
avatarUrl='$matrix_avatar_url', userId='$matrix_user_id')) \
|
||||
.replace("%24", "$")
|
||||
await Matrix.add_integration(
|
||||
f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"https://scalar.vector.im/api/widgets/bigbluebutton.html?{url_params}",
|
||||
f"bbb-{slug}-{pool.id}{suffix}", "bigbluebutton", "BigBlueButton", str(pool))
|
||||
await Matrix.add_integration(
|
||||
f"#poule-{slug}-{pool.id}:tfjm.org",
|
||||
f"https://board.tfjm.org/boards/{slug}-{pool.id}", f"board-{slug}-{pool.id}",
|
||||
"customwidget", "Tableau", str(pool))
|
||||
|
||||
# Invite admins and give permissions
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org")
|
||||
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org", 95)
|
||||
|
||||
# Invite organizers and give permissions
|
||||
for orga in tournament.organizers.all():
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org")
|
||||
|
||||
if not orga.is_admin:
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{orga.matrix_username}:tfjm.org", 50)
|
||||
|
||||
# Invite the jury, give good permissions
|
||||
for jury in pool.juries.all():
|
||||
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#general-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#jury-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#orga-{slug}:tfjm.org", f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#tirage-au-sort-{slug}:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org")
|
||||
|
||||
if not jury.is_admin:
|
||||
await Matrix.set_room_power_level(f"#jury-{slug}:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||
await Matrix.set_room_power_level(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
f"@{jury.matrix_username}:tfjm.org", 50)
|
||||
|
||||
# Invite participants to the right pool
|
||||
for participation in pool.participations.all():
|
||||
for participant in participation.team.participants.all():
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
|
||||
# Create private channels for teams
|
||||
for team in Team.objects.all():
|
||||
self.stdout.write(f"Create private channel for {team}...")
|
||||
if not await Matrix.resolve_room_alias(f"#equipe-{team.trigram.lower()}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"equipe-{team.trigram.lower()}",
|
||||
name=f"Équipe {team.trigram}",
|
||||
topic=f"Discussion interne de l'équipe {team.name}",
|
||||
federate=False,
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
for participant in team.participants.all():
|
||||
await Matrix.invite(f"#equipe-{team.trigram.lower}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org")
|
||||
await Matrix.set_room_power_level(f"#equipe-{team.trigram.lower()}:tfjm.org",
|
||||
f"@{participant.matrix_username}:tfjm.org", 50)
|
||||
|
||||
"""
|
||||
# Manage channels to discuss about problems
|
||||
for i in range(9):
|
||||
self.stdout.write(f"Create channel for problem {i}...")
|
||||
if not await Matrix.resolve_room_alias(f"#mec-{i}:tfjm.org"):
|
||||
await Matrix.create_room(
|
||||
visibility=RoomVisibility.public,
|
||||
alias=f"mec-{i}",
|
||||
name=f"Mise en commun - {'Général' if i == 0 else f'Problème {i}'}",
|
||||
topic=f"Discussion autour des problèmes",
|
||||
federate=False,
|
||||
preset=RoomPreset.public_chat,
|
||||
invite=[f"@{registration.matrix_username}:tfjm.org"
|
||||
for registration in Registration.objects.all()],
|
||||
power_level_override={
|
||||
f"@{registration.matrix_username}:tfjm.org": (95 if registration.is_admin else 50)
|
||||
for registration in VolunteerRegistration.objects.all()
|
||||
},
|
||||
)
|
||||
await Matrix.set_room_avatar(f"#mec-{i}:tfjm.org", avatar_uri)
|
||||
|
||||
for registration in Registration.objects.all():
|
||||
await Matrix.invite(f"#mec-{i}:tfjm.org", registration.matrix_username)
|
||||
|
||||
for registration in VolunteerRegistration.objects.all():
|
||||
await Matrix.set_room_power_level(f"#mec-{i}:tfjm.org",
|
||||
f"@{registration.matrix_username}:tfjm.org",
|
||||
95 if registration.is_admin else 50)
|
||||
"""
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
@ -16,7 +16,6 @@ from django.utils.text import format_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from registration.models import VolunteerRegistration
|
||||
from tfjm.lists import get_sympa_client
|
||||
from tfjm.matrix import Matrix, RoomPreset, RoomVisibility
|
||||
|
||||
|
||||
def get_motivation_letter_filename(instance, filename):
|
||||
@ -101,18 +100,9 @@ class Team(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.access_code:
|
||||
# if the team got created, generate the access code, create the contact mailing list
|
||||
# and create a dedicated Matrix room.
|
||||
self.access_code = get_random_string(6)
|
||||
self.create_mailing_list()
|
||||
|
||||
Matrix.create_room(
|
||||
visibility=RoomVisibility.private,
|
||||
name=f"#équipe-{self.trigram.lower()}",
|
||||
alias=f"equipe-{self.trigram.lower()}",
|
||||
topic=f"Discussion de l'équipe {self.name}",
|
||||
preset=RoomPreset.private_chat,
|
||||
)
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
@ -19,12 +19,11 @@ def create_team_participation(instance, created, raw, **_):
|
||||
|
||||
def update_mailing_list(instance: Team, raw, **_):
|
||||
"""
|
||||
When a team name or trigram got updated, update mailing lists and Matrix rooms
|
||||
When a team name or trigram got updated, update mailing lists
|
||||
"""
|
||||
if instance.pk and not raw:
|
||||
old_team = Team.objects.get(pk=instance.pk)
|
||||
if old_team.trigram != instance.trigram:
|
||||
# TODO Rename Matrix room
|
||||
# Delete old mailing list, create a new one
|
||||
old_team.delete_mailing_list()
|
||||
instance.create_mailing_list()
|
||||
|
@ -5,35 +5,9 @@
|
||||
{% block content %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
The chat is located on the dedicated Matrix server:
|
||||
The chat feature is now out of usage. If you feel that having a chat
|
||||
feature between participants is important, for example to build a
|
||||
team, please contact us.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
<div class="alert text-center">
|
||||
<a class="btn btn-success" href="https://element.tfjm.org/#/room/#faq:tfjm.org" 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 %}
|
||||
|
@ -32,7 +32,6 @@ from odf.table import CoveredTableCell, Table, TableCell, TableColumn, TableRow
|
||||
from odf.text import P
|
||||
from registration.models import StudentRegistration, VolunteerRegistration
|
||||
from tfjm.lists import get_sympa_client
|
||||
from tfjm.matrix import Matrix
|
||||
from tfjm.views import AdminMixin, VolunteerMixin
|
||||
|
||||
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
|
||||
@ -68,8 +67,7 @@ class CreateTeamView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
When a team is about to be created, the user automatically
|
||||
joins the team, a mailing list got created and the user is
|
||||
automatically subscribed to this mailing list, and finally
|
||||
a Matrix room is created and the user is invited in this room.
|
||||
automatically subscribed to this mailing list.
|
||||
"""
|
||||
ret = super().form_valid(form)
|
||||
# The user joins the team
|
||||
@ -82,9 +80,6 @@ class CreateTeamView(LoginRequiredMixin, CreateView):
|
||||
get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False,
|
||||
f"{user.first_name} {user.last_name}")
|
||||
|
||||
# Invite the user in the team Matrix room
|
||||
Matrix.invite(f"#equipe-{form.instance.trigram.lower()}:tfjm.org",
|
||||
f"@{user.registration.matrix_username}:tfjm.org")
|
||||
return ret
|
||||
|
||||
|
||||
@ -112,7 +107,7 @@ class JoinTeamView(LoginRequiredMixin, FormView):
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
When a user joins a team, the user is automatically subscribed to
|
||||
the team mailing list,the user is invited in the team Matrix room.
|
||||
the team mailing list.
|
||||
"""
|
||||
self.object = form.instance
|
||||
ret = super().form_valid(form)
|
||||
@ -127,9 +122,6 @@ class JoinTeamView(LoginRequiredMixin, FormView):
|
||||
get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False,
|
||||
f"{user.first_name} {user.last_name}")
|
||||
|
||||
# Invite the user in the team Matrix room
|
||||
Matrix.invite(f"#equipe-{form.instance.trigram.lower()}:tfjm.org",
|
||||
f"@{user.registration.matrix_username}:tfjm.org")
|
||||
return ret
|
||||
|
||||
def get_success_url(self):
|
||||
@ -468,9 +460,6 @@ class TeamLeaveView(LoginRequiredMixin, TemplateView):
|
||||
request.user.registration.team = None
|
||||
request.user.registration.save()
|
||||
get_sympa_client().unsubscribe(request.user.email, f"equipe-{team.trigram.lower()}", False)
|
||||
Matrix.kick(f"#equipe-{team.trigram.lower()}:tfjm.org",
|
||||
f"@{request.user.registration.matrix_username}:tfjm.org",
|
||||
"Équipe quittée")
|
||||
if team.students.count() + team.coaches.count() == 0:
|
||||
team.delete()
|
||||
return redirect(reverse_lazy("index"))
|
||||
|
@ -1,17 +0,0 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
||||
|
||||
|
||||
class CustomAuthUser(DjangoAuthUser): # pragma: no cover
|
||||
"""
|
||||
Override Django Auth User model to define a custom Matrix username.
|
||||
"""
|
||||
|
||||
def attributs(self):
|
||||
d = super().attributs()
|
||||
if self.user:
|
||||
d["matrix_username"] = self.user.registration.matrix_username
|
||||
d["display_name"] = str(self.user.registration)
|
||||
return d
|
@ -1,26 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "cas_server.servicepattern",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"pos": 100,
|
||||
"name": "Plateforme du TFJM²",
|
||||
"pattern": "^https://tfjm.org(:8448)?/.*$",
|
||||
"user_field": "matrix_username",
|
||||
"restrict_users": false,
|
||||
"proxy": true,
|
||||
"proxy_callback": true,
|
||||
"single_log_out": true,
|
||||
"single_log_out_callback": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "cas_server.replaceattributname",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "display_name",
|
||||
"replace": "",
|
||||
"service_pattern": 1
|
||||
}
|
||||
}
|
||||
]
|
@ -107,8 +107,8 @@ class StudentRegistrationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = StudentRegistration
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'health_issues', 'school', 'responsible_name', 'responsible_phone', 'responsible_email',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
'school', 'health_issues', 'housing_constraints', 'responsible_name', 'responsible_phone',
|
||||
'responsible_email', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class PhotoAuthorizationForm(forms.ModelForm):
|
||||
@ -205,8 +205,9 @@ class CoachRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = CoachRegistration
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number', 'health_issues',
|
||||
'professional_activity', 'give_contact_to_animath', 'email_confirmed',)
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'professional_activity', 'health_issues', 'housing_constraints',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class VolunteerRegistrationForm(forms.ModelForm):
|
||||
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-16 21:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registration", "0008_alter_payment_valid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="participantregistration",
|
||||
name="housing_constraints",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="You can fill in something here if you have any housing constraints, e.g. medical problems, scheduling issues, gender issues, or anything else you feel is relevant to the organizers. Leave empty if you have nothing specific to declare.",
|
||||
verbose_name="housing constraints",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="participantregistration",
|
||||
name="health_issues",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="You can indicate here your allergies or anything that is important to know for organizers.",
|
||||
verbose_name="health issues",
|
||||
),
|
||||
),
|
||||
]
|
@ -85,10 +85,6 @@ class Registration(PolymorphicModel):
|
||||
def is_volunteer(self):
|
||||
return isinstance(self, VolunteerRegistration)
|
||||
|
||||
@property
|
||||
def matrix_username(self):
|
||||
return f"tfjm_{self.user.pk}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy("registration:user_detail", args=(self.user_id,))
|
||||
|
||||
@ -161,7 +157,16 @@ class ParticipantRegistration(Registration):
|
||||
health_issues = models.TextField(
|
||||
verbose_name=_("health issues"),
|
||||
blank=True,
|
||||
help_text=_("You can indicate here your allergies or anything that is important to know for organizers"),
|
||||
help_text=_("You can indicate here your allergies or anything that is important to know for organizers."),
|
||||
)
|
||||
|
||||
housing_constraints = models.TextField(
|
||||
verbose_name=_("housing constraints"),
|
||||
blank=True,
|
||||
help_text=_("You can fill in something here if you have any housing constraints, "
|
||||
"e.g. medical problems, scheduling issues, gender issues, "
|
||||
"or anything else you feel is relevant to the organizers. "
|
||||
"Leave empty if you have nothing specific to declare."),
|
||||
)
|
||||
|
||||
photo_authorization = models.FileField(
|
||||
|
@ -53,8 +53,15 @@
|
||||
<dt class="col-sm-6 text-end">{% trans "Phone number:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-end">{% trans "Health issues:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.health_issues|default:any }}</dd>
|
||||
{% if user_object.registration.health_issues %}
|
||||
<dt class="col-sm-6 text-end">{% trans "Health issues:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.health_issues }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if user_object.registration.housing_constraints %}
|
||||
<dt class="col-sm-6 text-end">{% trans "Housing constraints:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.housing_constraints }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-end">{% trans "Photo authorization:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
|
@ -7,7 +7,6 @@ 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.encoding import force_bytes
|
||||
@ -413,18 +412,18 @@ class TestRegistration(TestCase):
|
||||
"""
|
||||
Try to search some things.
|
||||
"""
|
||||
call_command("rebuild_index", "--noinput", "-v", 0)
|
||||
|
||||
response = self.client.get(reverse("haystack_search") + "?q=" + self.user.email)
|
||||
response = self.client.get(reverse("haystack_search") + "?q=" + self.user.email + "&models=auth.user")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(response.context["object_list"])
|
||||
|
||||
response = self.client.get(reverse("haystack_search") + "?q=" +
|
||||
str(self.coach.registration.professional_activity))
|
||||
str(self.coach.registration.professional_activity)
|
||||
+ "&models=registration.CoachRegistration")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(response.context["object_list"])
|
||||
|
||||
response = self.client.get(reverse("haystack_search") + "?q=" +
|
||||
self.student.registration.get_student_class_display())
|
||||
self.student.registration.school + "&models=registration.StudentRegistration")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(response.context["object_list"])
|
||||
|
@ -1,28 +1,26 @@
|
||||
channels[daphne]~=4.0.0
|
||||
channels-redis~=4.0.0
|
||||
crispy-bootstrap5~=0.7
|
||||
Django>=4.1,<5.0
|
||||
django-cas-server~=2.0
|
||||
django-crispy-forms~=1.14
|
||||
django-extensions~=3.2
|
||||
django-filter~=22.1
|
||||
django-haystack~=3.2
|
||||
django-mailer~=2.2
|
||||
django-phonenumber-field~=7.0
|
||||
django-polymorphic~=3.1
|
||||
django-tables2~=2.4
|
||||
djangorestframework~=3.14
|
||||
django-rest-polymorphic~=0.1
|
||||
gunicorn~=20.1
|
||||
matrix-nio~=0.20
|
||||
channels-redis~=4.2.0
|
||||
crispy-bootstrap5~=2023.10
|
||||
Django>=5.0,<6.0
|
||||
django-crispy-forms~=2.1
|
||||
django-extensions~=3.2.3
|
||||
django-filter~=23.5
|
||||
elasticsearch~=7.17.9
|
||||
git+https://github.com/django-haystack/django-haystack.git#v3.3b1
|
||||
django-mailer~=2.3.1
|
||||
django-phonenumber-field~=7.3.0
|
||||
django-polymorphic~=3.1.0
|
||||
django-tables2~=2.7.0
|
||||
djangorestframework~=3.14.0
|
||||
django-rest-polymorphic~=0.1.10
|
||||
gunicorn~=21.2.0
|
||||
odfpy~=1.4.1
|
||||
phonenumbers~=8.12.57
|
||||
psycopg2-binary~=2.9.5
|
||||
pypdf~=3.4
|
||||
ipython~=8.11.0
|
||||
python-magic~=0.4.26
|
||||
requests~=2.28.1
|
||||
phonenumbers~=8.13.27
|
||||
psycopg2-binary~=2.9.9
|
||||
pypdf~=3.17.4
|
||||
ipython~=8.20.0
|
||||
python-magic~=0.4.27
|
||||
requests~=2.31.0
|
||||
sympasoap~=1.1
|
||||
uvicorn~=0.17
|
||||
websockets~=10.4
|
||||
whoosh~=2.7
|
||||
uvicorn~=0.25.0
|
||||
websockets~=12.0
|
@ -4,15 +4,9 @@
|
||||
* * * * * 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
|
||||
|
||||
# Recreate sympa lists
|
||||
*/2 * * * * cd /code && python manage.py fix_sympa_lists &> /dev/null
|
||||
|
||||
# Update matrix channels
|
||||
03 */6 * * * cd /code && python manage.py fix_matrix_channels &> /dev/null
|
||||
|
||||
# Check payments from Hello Asso
|
||||
*/6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null
|
||||
|
||||
|
432
tfjm/matrix.py
432
tfjm/matrix.py
@ -1,432 +0,0 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from enum import Enum
|
||||
import os
|
||||
|
||||
|
||||
class Matrix:
|
||||
"""
|
||||
Utility class to manage interaction with the Matrix homeserver.
|
||||
This log in the @tfjmbot account (must be created before).
|
||||
The access token is then stored.
|
||||
All is done with this bot account, that is a server administrator.
|
||||
Tasks are normally asynchronous, but for compatibility we make
|
||||
them synchronous.
|
||||
"""
|
||||
_token = None
|
||||
_device_id = None
|
||||
|
||||
@classmethod
|
||||
async def _get_client(cls): # pragma: no cover
|
||||
"""
|
||||
Retrieve the bot account.
|
||||
If not logged, log in and store access token.
|
||||
"""
|
||||
if not os.getenv("SYNAPSE_PASSWORD") and not os.getenv("SYNAPSE_TOKEN"):
|
||||
return FakeMatrixClient()
|
||||
|
||||
from nio import AsyncClient
|
||||
client = AsyncClient("https://tfjm.org", "@tfjmbot:tfjm.org")
|
||||
client.user_id = "@tfjmbot:tfjm.org"
|
||||
|
||||
if os.getenv("SYNAPSE_TOKEN"):
|
||||
client.access_token = os.getenv("SYNAPSE_TOKEN")
|
||||
client.device_id = os.getenv("SYNAPSE_DEVICE")
|
||||
return client
|
||||
elif os.path.isfile(".matrix_token"):
|
||||
with open(".matrix_device", "r") as f:
|
||||
cls._device_id = f.read().rstrip(" \t\r\n")
|
||||
client.device_id = cls._device_id
|
||||
with open(".matrix_token", "r") as f:
|
||||
cls._token = f.read().rstrip(" \t\r\n")
|
||||
client.access_token = cls._token
|
||||
return client
|
||||
|
||||
await client.login(password=os.getenv("SYNAPSE_PASSWORD"), device_name="Plateforme")
|
||||
cls._token = client.access_token
|
||||
cls._device_id = client.device_id
|
||||
with open(".matrix_token", "w") as f:
|
||||
f.write(cls._token)
|
||||
with open(".matrix_device", "w") as f:
|
||||
f.write(cls._device_id)
|
||||
return client
|
||||
|
||||
@classmethod
|
||||
async def set_display_name(cls, name: str):
|
||||
"""
|
||||
Set the display name of the bot account.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
return await client.set_displayname(name)
|
||||
|
||||
@classmethod
|
||||
async def set_avatar(cls, avatar_url: str): # pragma: no cover
|
||||
"""
|
||||
Set the display avatar of the bot account.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
return await client.set_avatar(avatar_url)
|
||||
|
||||
@classmethod
|
||||
async def get_avatar(cls): # pragma: no cover
|
||||
"""
|
||||
Set the display avatar of the bot account.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
resp = await client.get_avatar()
|
||||
return resp.avatar_url if hasattr(resp, "avatar_url") else resp
|
||||
|
||||
@classmethod
|
||||
async def upload(
|
||||
cls,
|
||||
data_provider,
|
||||
content_type: str = "application/octet-stream",
|
||||
filename: str = None,
|
||||
encrypt: bool = False,
|
||||
monitor=None,
|
||||
filesize: int = None,
|
||||
): # pragma: no cover
|
||||
"""
|
||||
Upload a file to the content repository.
|
||||
|
||||
Returns a tuple containing:
|
||||
|
||||
- Either a `UploadResponse` if the request was successful, or a
|
||||
`UploadError` if there was an error with the request
|
||||
|
||||
- A dict with file decryption info if encrypt is ``True``,
|
||||
else ``None``.
|
||||
Args:
|
||||
data_provider (Callable, SynchronousFile, AsyncFile): A function
|
||||
returning the data to upload or a file object. File objects
|
||||
must be opened in binary mode (``mode="r+b"``). Callables
|
||||
returning a path string, Path, async iterable or aiofiles
|
||||
open binary file object allow the file data to be read in an
|
||||
asynchronous and lazy way (without reading the entire file
|
||||
into memory). Returning a synchronous iterable or standard
|
||||
open binary file object will still allow the data to be read
|
||||
lazily, but not asynchronously.
|
||||
|
||||
The function will be called again if the upload fails
|
||||
due to a server timeout, in which case it must restart
|
||||
from the beginning.
|
||||
Callables receive two arguments: the total number of
|
||||
429 "Too many request" errors that occured, and the total
|
||||
number of server timeout exceptions that occured, thus
|
||||
cleanup operations can be performed for retries if necessary.
|
||||
|
||||
content_type (str): The content MIME type of the file,
|
||||
e.g. "image/png".
|
||||
Defaults to "application/octet-stream", corresponding to a
|
||||
generic binary file.
|
||||
Custom values are ignored if encrypt is ``True``.
|
||||
|
||||
filename (str, optional): The file's original name.
|
||||
|
||||
encrypt (bool): If the file's content should be encrypted,
|
||||
necessary for files that will be sent to encrypted rooms.
|
||||
Defaults to ``False``.
|
||||
|
||||
monitor (TransferMonitor, optional): If a ``TransferMonitor``
|
||||
object is passed, it will be updated by this function while
|
||||
uploading.
|
||||
From this object, statistics such as currently
|
||||
transferred bytes or estimated remaining time can be gathered
|
||||
while the upload is running as a task; it also allows
|
||||
for pausing and cancelling.
|
||||
|
||||
filesize (int, optional): Size in bytes for the file to transfer.
|
||||
If left as ``None``, some servers might refuse the upload.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
return await client.upload(data_provider, content_type, filename, encrypt, monitor, filesize) \
|
||||
if not isinstance(client, FakeMatrixClient) else None, None
|
||||
|
||||
@classmethod
|
||||
async def create_room(
|
||||
cls,
|
||||
visibility=None,
|
||||
alias=None,
|
||||
name=None,
|
||||
topic=None,
|
||||
room_version=None,
|
||||
federate=True,
|
||||
is_direct=False,
|
||||
preset=None,
|
||||
invite=(),
|
||||
initial_state=(),
|
||||
power_level_override=None,
|
||||
):
|
||||
"""
|
||||
Create a new room.
|
||||
|
||||
Returns either a `RoomCreateResponse` if the request was successful or
|
||||
a `RoomCreateError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
visibility (RoomVisibility): whether to have the room published in
|
||||
the server's room directory or not.
|
||||
Defaults to ``RoomVisibility.private``.
|
||||
|
||||
alias (str, optional): The desired canonical alias local part.
|
||||
For example, if set to "foo" and the room is created on the
|
||||
"example.com" server, the room alias will be
|
||||
"#foo:example.com".
|
||||
|
||||
name (str, optional): A name to set for the room.
|
||||
|
||||
topic (str, optional): A topic to set for the room.
|
||||
|
||||
room_version (str, optional): The room version to set.
|
||||
If not specified, the homeserver will use its default setting.
|
||||
If a version not supported by the homeserver is specified,
|
||||
a 400 ``M_UNSUPPORTED_ROOM_VERSION`` error will be returned.
|
||||
|
||||
federate (bool): Whether to allow users from other homeservers from
|
||||
joining the room. Defaults to ``True``.
|
||||
Cannot be changed later.
|
||||
|
||||
is_direct (bool): If this should be considered a
|
||||
direct messaging room.
|
||||
If ``True``, the server will set the ``is_direct`` flag on
|
||||
``m.room.member events`` sent to the users in ``invite``.
|
||||
Defaults to ``False``.
|
||||
|
||||
preset (RoomPreset, optional): The selected preset will set various
|
||||
rules for the room.
|
||||
If unspecified, the server will choose a preset from the
|
||||
``visibility``: ``RoomVisibility.public`` equates to
|
||||
``RoomPreset.public_chat``, and
|
||||
``RoomVisibility.private`` equates to a
|
||||
``RoomPreset.private_chat``.
|
||||
|
||||
invite (list): A list of user id to invite to the room.
|
||||
|
||||
initial_state (list): A list of state event dicts to send when
|
||||
the room is created.
|
||||
For example, a room could be made encrypted immediatly by
|
||||
having a ``m.room.encryption`` event dict.
|
||||
|
||||
power_level_override (dict): A ``m.room.power_levels content`` dict
|
||||
to override the default.
|
||||
The dict will be applied on top of the generated
|
||||
``m.room.power_levels`` event before it is sent to the room.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
return await client.room_create(
|
||||
visibility, alias, name, topic, room_version, federate, is_direct, preset, invite, initial_state,
|
||||
power_level_override)
|
||||
|
||||
@classmethod
|
||||
async def resolve_room_alias(cls, room_alias: str):
|
||||
"""
|
||||
Resolve a room alias to a room ID.
|
||||
Return None if the alias does not exist.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
resp = await client.room_resolve_alias(room_alias)
|
||||
return resp.room_id if resp and hasattr(resp, "room_id") else None
|
||||
|
||||
@classmethod
|
||||
async def invite(cls, room_id: str, user_id: str):
|
||||
"""
|
||||
Invite a user to a room.
|
||||
|
||||
Returns either a `RoomInviteResponse` if the request was successful or
|
||||
a `RoomInviteError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
room_id (str): The room id of the room that the user will be
|
||||
invited to.
|
||||
user_id (str): The user id of the user that should be invited.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
return await client.room_invite(room_id, user_id)
|
||||
|
||||
@classmethod
|
||||
async def send_message(cls, room_id: str, body: str, formatted_body: str = None,
|
||||
msgtype: str = "m.text", html: bool = True):
|
||||
"""
|
||||
Send a message to a room.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
content = {
|
||||
"msgtype": msgtype,
|
||||
"body": body,
|
||||
"formatted_body": formatted_body or body,
|
||||
}
|
||||
if html:
|
||||
content["format"] = "org.matrix.custom.html"
|
||||
return await client.room_send(
|
||||
room_id=room_id,
|
||||
message_type="m.room.message",
|
||||
content=content,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def add_integration(cls, room_id: str, widget_url: str, state_key: str,
|
||||
widget_type: str = "customwidget", widget_name: str = "Custom widget",
|
||||
widget_title: str = ""):
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
content = {
|
||||
"type": widget_type,
|
||||
"url": widget_url,
|
||||
"name": widget_name,
|
||||
"data": {
|
||||
"curl": widget_url,
|
||||
"title": widget_title,
|
||||
},
|
||||
"creatorUserId": client.user,
|
||||
"roomId": room_id,
|
||||
"id": state_key,
|
||||
}
|
||||
return await client.room_put_state(
|
||||
room_id=room_id,
|
||||
event_type="im.vector.modular.widgets",
|
||||
content=content,
|
||||
state_key=state_key,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def remove_integration(cls, room_id: str, state_key: str):
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
return await client.room_put_state(
|
||||
room_id=room_id,
|
||||
event_type="im.vector.modular.widgets",
|
||||
content={},
|
||||
state_key=state_key,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def kick(cls, room_id: str, user_id: str, reason: str = None):
|
||||
"""
|
||||
Kick a user from a room, or withdraw their invitation.
|
||||
|
||||
Kicking a user adjusts their membership to "leave" with an optional
|
||||
reason.
|
||||
²
|
||||
Returns either a `RoomKickResponse` if the request was successful or
|
||||
a `RoomKickError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
room_id (str): The room id of the room that the user will be
|
||||
kicked from.
|
||||
user_id (str): The user_id of the user that should be kicked.
|
||||
reason (str, optional): A reason for which the user is kicked.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
return await client.room_kick(room_id, user_id, reason)
|
||||
|
||||
@classmethod
|
||||
async def set_room_power_level(cls, room_id: str, user_id: str, power_level: int): # pragma: no cover
|
||||
"""
|
||||
Put a given power level to a user in a certain room.
|
||||
|
||||
Returns either a `RoomPutStateResponse` if the request was successful or
|
||||
a `RoomPutStateError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
room_id (str): The room id of the room where the power level
|
||||
of the user should be updated.
|
||||
user_id (str): The user_id of the user which power level should
|
||||
be updated.
|
||||
power_level (int): The target power level to give.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if isinstance(client, FakeMatrixClient):
|
||||
return None
|
||||
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
resp = await client.room_get_state_event(room_id, "m.room.power_levels")
|
||||
content = resp.content
|
||||
content["users"][user_id] = power_level
|
||||
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
||||
|
||||
@classmethod
|
||||
async def set_room_power_level_event(cls, room_id: str, event: str, power_level: int): # pragma: no cover
|
||||
"""
|
||||
Define the minimal power level to have to send a certain event type
|
||||
in a given room.
|
||||
|
||||
Returns either a `RoomPutStateResponse` if the request was successful or
|
||||
a `RoomPutStateError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
room_id (str): The room id of the room where the power level
|
||||
of the event should be updated.
|
||||
event (str): The event name which minimal power level should
|
||||
be updated.
|
||||
power_level (int): The target power level to give.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if isinstance(client, FakeMatrixClient):
|
||||
return None
|
||||
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
resp = await client.room_get_state_event(room_id, "m.room.power_levels")
|
||||
content = resp.content
|
||||
if event.startswith("m."):
|
||||
content["events"][event] = power_level
|
||||
else:
|
||||
content[event] = power_level
|
||||
return await client.room_put_state(room_id, "m.room.power_levels", content=content, state_key=resp.state_key)
|
||||
|
||||
@classmethod
|
||||
async def set_room_avatar(cls, room_id: str, avatar_uri: str):
|
||||
"""
|
||||
Define the avatar of a room.
|
||||
|
||||
Returns either a `RoomPutStateResponse` if the request was successful or
|
||||
a `RoomPutStateError` if there was an error with the request.
|
||||
|
||||
Args:
|
||||
room_id (str): The room id of the room where the avatar
|
||||
should be changed.
|
||||
avatar_uri (str): The internal avatar URI to apply.
|
||||
"""
|
||||
client = await cls._get_client()
|
||||
if room_id.startswith("#"):
|
||||
room_id = await cls.resolve_room_alias(room_id)
|
||||
return await client.room_put_state(room_id, "m.room.avatar", content={
|
||||
"url": avatar_uri
|
||||
}, state_key="")
|
||||
|
||||
|
||||
if os.getenv("SYNAPSE_PASSWORD"): # pragma: no cover
|
||||
from nio import RoomVisibility, RoomPreset
|
||||
RoomVisibility = RoomVisibility
|
||||
RoomPreset = RoomPreset
|
||||
else:
|
||||
# When running tests, faking matrix-nio classes to don't include the module
|
||||
class RoomVisibility(Enum):
|
||||
private = 'private'
|
||||
public = 'public'
|
||||
|
||||
class RoomPreset(Enum):
|
||||
private_chat = "private_chat"
|
||||
trusted_private_chat = "trusted_private_chat"
|
||||
public_chat = "public_chat"
|
||||
|
||||
|
||||
class FakeMatrixClient:
|
||||
"""
|
||||
Simulate a Matrix client to run tests, if no Matrix homeserver is connected.
|
||||
"""
|
||||
|
||||
def __getattribute__(self, item):
|
||||
async def func(*_, **_2):
|
||||
return None
|
||||
return func
|
@ -24,12 +24,11 @@ PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
ADMINS = [("Emmy D'Anello", "emmy.danello@animath.fr")]
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '6$wl1=ehfoiymin3m3i-wyx5d3t=1h7g4(j2izn*my)*yiq#he'
|
||||
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
@ -38,7 +37,6 @@ SITE_ID = 1
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@ -74,7 +72,6 @@ INSTALLED_APPS = [
|
||||
|
||||
if "test" not in sys.argv: # pragma: no cover
|
||||
INSTALLED_APPS += [
|
||||
'cas_server',
|
||||
'django_extensions',
|
||||
'mailer',
|
||||
]
|
||||
@ -123,7 +120,6 @@ FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||
ASGI_APPLICATION = 'tfjm.asgi.application'
|
||||
WSGI_APPLICATION = 'tfjm.wsgi.application'
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
|
||||
|
||||
@ -147,8 +143,6 @@ PASSWORD_HASHERS = [
|
||||
'django.contrib.auth.hashers.BCryptPasswordHasher',
|
||||
]
|
||||
|
||||
CAS_AUTH_CLASS = 'registration.auth.CustomAuthUser'
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAdminUser'
|
||||
@ -181,7 +175,6 @@ USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
|
||||
@ -206,13 +199,13 @@ DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap5.html'
|
||||
|
||||
HAYSTACK_CONNECTIONS = {
|
||||
'default': {
|
||||
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
|
||||
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
|
||||
'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine'
|
||||
if os.getenv("HAYSTACK_INDEX_NAME", None) else 'haystack.backends.simple_backend.SimpleEngine',
|
||||
'URL': 'http://elasticsearch:9200/',
|
||||
'INDEX_NAME': os.getenv('HAYSTACK_INDEX_NAME', 'inscription-tfjm'),
|
||||
}
|
||||
}
|
||||
|
||||
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
||||
|
||||
_db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower()
|
||||
|
||||
if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': # pragma: no cover
|
||||
@ -246,14 +239,14 @@ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
||||
|
||||
# Custom parameters
|
||||
PROBLEMS = [
|
||||
"Philatélie",
|
||||
"La montagne aux ruisseaux",
|
||||
"Appoint monétaire",
|
||||
"Musique déformée",
|
||||
"Angles entiers",
|
||||
"Tribus hiérarchiques",
|
||||
"Miroirs et lasers",
|
||||
"Graine de deck",
|
||||
"Triominos",
|
||||
"Rassemblements mathématiques",
|
||||
"Tournoi de ping-pong",
|
||||
"Dépollution de la Seine",
|
||||
"Électron libre",
|
||||
"Pièces truquées",
|
||||
"Drôles de cookies",
|
||||
"Création d'un jeu",
|
||||
]
|
||||
FORBIDDEN_TRIGRAMS = [
|
||||
"BIT",
|
||||
@ -280,6 +273,6 @@ CHANNEL_LAYERS = {
|
||||
}
|
||||
|
||||
if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
else:
|
||||
from .settings_dev import * # noqa: F401,F403
|
||||
from .settings_dev import * # noqa: F401,F403
|
||||
|
@ -9,8 +9,6 @@ DEBUG = False
|
||||
# Mandatory !
|
||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'plateforme.tfjm.org']
|
||||
|
||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||
|
||||
# Emails
|
||||
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
||||
MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
@ -39,3 +37,5 @@ CHANNEL_LAYERS = {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
||||
|
6
tfjm/static/bootstrap/css/bootstrap.min.css
vendored
6
tfjm/static/bootstrap/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@
|
||||
{% block content %}
|
||||
<div class="text-justify">
|
||||
<p>
|
||||
La plateforme d'inscription du TFJM² a été développée entre 2019 et 2023
|
||||
La plateforme d'inscription du TFJM² a été développée entre 2019 et 2024
|
||||
par Emmy 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>
|
||||
|
@ -100,9 +100,10 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li class="nav-item active">
|
||||
<li class="nav-item active d-none">
|
||||
<a class="nav-link" href="{% url "participation:chat" %}">
|
||||
<i class="fas fa-comments"></i> {% trans "Chat" %}</a>
|
||||
<i class="fas fa-comments"></i> {% trans "Chat" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if user.registration.is_admin %}
|
||||
<li class="nav-item active">
|
||||
|
@ -5,14 +5,19 @@
|
||||
|
||||
<div class="alert alert-success">
|
||||
<p>
|
||||
Les inscriptions pour la session 2023 sont à présent ouvertes, vous pouvez créer votre compte.
|
||||
Les inscriptions pour la session 2024 sont à présent ouvertes, vous pouvez créer votre compte.
|
||||
Prenez garde toutefois aux dates indiquées qui sont pour l'instant provisoires.
|
||||
</p>
|
||||
<p>
|
||||
Une documentation plus complète est disponible à l'adresse
|
||||
<a href="https://inscription.tfjm.org/doc/">https://inscription.tfjm.org/doc/</a>.
|
||||
<a href="https://inscription.tfjm.org/doc/">https://inscription.tfjm.org/doc/</a>
|
||||
et sera progressivement actualisée.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
Diverses améliorations fonctionnelles pourront être apportées au site au cours des prochaines semaines.
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
@ -55,18 +60,10 @@
|
||||
|
||||
<h2>Je ne trouve pas d'équipe, aidez-moi !</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
En ayant créé un compte sur cette plateforme, vous créez également un compte sur la plateforme de chat dédiée
|
||||
au 𝕋𝔽𝕁𝕄², basée sur le protocole <a href="https://matrix.org/">Matrix</a> et le client
|
||||
<a href="https://element.io">Element</a>. Cette interface de chat vous permet de communiquer avec les membres
|
||||
de votre équipe, mais surtout de pouvoir participer au tirage au sort et discuter avec les autres équipes et
|
||||
les organisateur⋅rices.
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
Ce chat contient également un salon <code>#je-cherche-une-equipe</code> où vous pouvez crier à l'aide pour trouver
|
||||
une équipe, ou compléter la votre s'il vous manque des participant⋅es. C'est un petit coin auprès du feu,
|
||||
ne cherchez pas à être trop formel⋅le.
|
||||
Vous pouvez nous contacter à l'adresse <a href="mailto:contact@tfjm.org">contact@tfjm.org</a> pour que nous
|
||||
puissions vous aider à vous mettre en relation avec d'autres participant⋅es qui cherchent également une équipe.
|
||||
</p>
|
||||
|
||||
<h2>J'ai une question</h2>
|
||||
|
32
tox.ini
32
tox.ini
@ -1,7 +1,7 @@
|
||||
[tox]
|
||||
envlist =
|
||||
py310
|
||||
py311
|
||||
py312
|
||||
|
||||
linters
|
||||
skipsdist = True
|
||||
@ -11,22 +11,22 @@ sitepackages = False
|
||||
deps =
|
||||
coverage
|
||||
channels[daphne]~=4.0.0
|
||||
crispy-bootstrap5~=0.7
|
||||
Django>=4.2,<5.0
|
||||
django-crispy-forms~=1.14
|
||||
django-filter~=22.1
|
||||
django-haystack~=3.2
|
||||
django-phonenumber-field~=7.0
|
||||
django-polymorphic~=3.1
|
||||
django-tables2~=2.4
|
||||
djangorestframework~=3.14
|
||||
django-rest-polymorphic~=0.1
|
||||
crispy-bootstrap5~=2023.10
|
||||
Django>=5.0,<6.0
|
||||
django-crispy-forms~=2.1
|
||||
django-filter~=23.5
|
||||
git+https://github.com/django-haystack/django-haystack.git#v3.3b1
|
||||
django-phonenumber-field~=7.3.0
|
||||
django-polymorphic~=3.1.0
|
||||
django-tables2~=2.7.0
|
||||
djangorestframework~=3.14.0
|
||||
django-rest-polymorphic~=0.1.10
|
||||
odfpy~=1.4.1
|
||||
phonenumbers~=8.12.57
|
||||
pypdf~=3.4
|
||||
python-magic~=0.4.26
|
||||
requests~=2.28.1
|
||||
whoosh~=2.7
|
||||
phonenumbers~=8.13.27
|
||||
pypdf~=3.17.4
|
||||
python-magic~=0.4.27
|
||||
requests~=2.31.0
|
||||
|
||||
commands =
|
||||
coverage run --source=api,draw,logs,participation,registration,tfjm ./manage.py test api/ draw/ logs/ participation/ registration/ tfjm/
|
||||
coverage report -m
|
||||
|
Loading…
x
Reference in New Issue
Block a user