1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-12-25 07:02:22 +00:00

More specific code to ETEAM

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-06-08 00:19:33 +02:00
parent dd45f77a5e
commit 17c7d0ccc3
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
19 changed files with 190 additions and 35 deletions

View File

@ -0,0 +1,17 @@
{
"background_color": "white",
"description": "Chat for ETEAM",
"display": "standalone",
"icons": [
{
"src": "/static/tfjm/img/eteam.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"name": "ETEAM Chat",
"short_name": "ETEAM Chat",
"start_url": "/chat/fullscreen",
"theme_color": "black"
}

View File

@ -6,7 +6,11 @@
{% block extracss %} {% block extracss %}
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #} {# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}"> {% if TFJM.APP == "TFJM" %}
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
{% elif TFJM.APP == "ETEAM" %}
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
{% endif %}
{% endblock %} {% endblock %}
{% block content-title %}{% endblock %} {% block content-title %}{% endblock %}

View File

@ -6,10 +6,13 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title> {% if TFJM.APP == "TFJM" %}
{% trans "TFJM² Chat" %} <title>{% trans "TFJM² Chat" %}</title>
</title> <meta name="description" content="{% trans "TFJM² Chat" %}">
<meta name="description" content="{% trans "TFJM² Chat" %}"> {% elif TFJM.APP == "ETEAM" %}
<title>{% trans "ETEAM Chat" %}</title>
<meta name="description" content="{% trans "ETEAM Chat" %}">
{% endif %}
{# Favicon #} {# Favicon #}
<link rel="shortcut icon" href="{% static "favicon.ico" %}"> <link rel="shortcut icon" href="{% static "favicon.ico" %}">
@ -27,7 +30,11 @@
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script> <script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #} {# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}"> {% if TFJM.APP == "TFJM" %}
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
{% elif TFJM.APP == "ETEAM" %}
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
{% endif %}
</head> </head>
<body class="d-flex w-100 h-100 flex-column"> <body class="d-flex w-100 h-100 flex-column">
{% include "chat/content.html" with fullscreen=True %} {% include "chat/content.html" with fullscreen=True %}

View File

@ -7,9 +7,9 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title> <title>
{% trans "TFJM² Chat" %} - {% trans "Log in" %} {% trans "Chat" %} - {% trans "Log in" %}
</title> </title>
<meta name="description" content="{% trans "TFJM² Chat" %}"> <meta name="description" content="{% trans "Chat" %}">
{# Favicon #} {# Favicon #}
<link rel="shortcut icon" href="{% static "favicon.ico" %}"> <link rel="shortcut icon" href="{% static "favicon.ico" %}">
@ -25,7 +25,11 @@
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script> <script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #} {# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}"> {% if TFJM.APP == "TFJM" %}
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
{% elif TFJM.APP == "ETEAM" %}
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
{% endif %}
</head> </head>
<body class="d-flex w-100 h-100 flex-column"> <body class="d-flex w-100 h-100 flex-column">
<div class="container"> <div class="container">

View File

@ -417,7 +417,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
return error return error
async def process_dice_select_poules(self): async def process_dice_select_poules(self): # noqa: C901
""" """
Called when all teams launched their dice. Called when all teams launched their dice.
Place teams into pools and order their passage. Place teams into pools and order their passage.

View File

@ -111,7 +111,7 @@ class RequestValidationForm(forms.Form):
) )
engagement = forms.BooleanField( engagement = forms.BooleanField(
label=_("I engage myself to participate to the whole TFJM²."), label=_("I engage myself to participate to the whole tournament."),
required=True, required=True,
) )

View File

@ -1,7 +1,8 @@
# Copyright (C) 2020 by Animath # Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import math
from datetime import date, timedelta from datetime import date, timedelta
import math
import os import os
from django.conf import settings from django.conf import settings
@ -27,7 +28,7 @@ def get_motivation_letter_filename(instance, filename):
class Team(models.Model): class Team(models.Model):
""" """
The Team model represents a real team that participates to the TFJM². The Team model represents a real team that participates to the tournament.
This only includes the registration detail. This only includes the registration detail.
""" """
name = models.CharField( name = models.CharField(

View File

@ -15,10 +15,10 @@
{% if payment %} {% if payment %}
<p> <p>
You must now pay your participation fee of € {{ team.participation.amount }}. You must now pay your participation fee of € {{ payment.amount }}.
You can pay by credit card or bank transfer. You'll find information You can pay by credit card or bank transfer. You'll find information
on the payment page which you can find on on the payment page which you can find on
<a href="https://{{ domain }}{% url 'registration:my_account_detail' %}"your account</a>. <a href="https://{{ domain }}{% url 'registration:my_account_detail' %}">your account</a>.
If you have a scholarship, registration is free, but you must submit a justification on the same page. If you have a scholarship, registration is free, but you must submit a justification on the same page.
</p> </p>
{% elif registration.is_coach and team.participation.tournament.price %} {% elif registration.is_coach and team.participation.tournament.price %}

View File

@ -2,8 +2,8 @@ Hello {{registration }},
Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
to work on your problems. You can then upload your solutions to the platform. to work on your problems. You can then upload your solutions to the platform.
{% if team.participation.amount %} {% if payment %}
You must now pay your participation fee of € {{ team.participation.amount }}. You must now pay your participation fee of € {{ payment.amount }}.
You can pay by credit card or bank transfer. You'll find information You can pay by credit card or bank transfer. You'll find information
on the payment page which you can find on your account: on the payment page which you can find on your account:
https://{{ domain }}{% url 'registration:my_account_detail' %} https://{{ domain }}{% url 'registration:my_account_detail' %}

View File

@ -231,7 +231,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
mail_context = dict(team=self.object, domain=Site.objects.first().domain) mail_context = dict(team=self.object, domain=Site.objects.first().domain)
mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context) mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context)
mail_html = render_to_string("participation/mails/request_validation.html", mail_context) mail_html = render_to_string("participation/mails/request_validation.html", mail_context)
send_mail("[TFJM²] Validation d'équipe", mail_plain, settings.DEFAULT_FROM_EMAIL, send_mail(f"[{settings.APP_NAME}] {_('Team validation')}", mail_plain, settings.DEFAULT_FROM_EMAIL,
[self.object.participation.tournament.organizers_email], html_message=mail_html) [self.object.participation.tournament.organizers_email], html_message=mail_html)
return super().form_valid(form) return super().form_valid(form)
@ -255,7 +255,8 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
domain = Site.objects.first().domain domain = Site.objects.first().domain
for registration in self.object.participants.all(): for registration in self.object.participants.all():
if registration.is_student and self.object.participation.tournament.price: if settings.PAYMENT_MANAGEMENT and \
registration.is_student and self.object.participation.tournament.price:
payment = Payment.objects.get(registrations=registration, final=False) payment = Payment.objects.get(registrations=registration, final=False)
else: else:
payment = None payment = None
@ -265,7 +266,8 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
message=form.cleaned_data["message"].replace('\n', '<br>')) message=form.cleaned_data["message"].replace('\n', '<br>'))
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context_plain) mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context_plain)
mail_html = render_to_string("participation/mails/team_validated.html", mail_context_html) mail_html = render_to_string("participation/mails/team_validated.html", mail_context_html)
registration.user.email_user("[TFJM²] Équipe validée", mail_plain, html_message=mail_html) registration.user.email_user(f"[{settings.APP_NAME}] {_('Team validated')}", mail_plain,
html_message=mail_html)
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()
@ -273,8 +275,8 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
mail_context_html = dict(team=self.object, message=form.cleaned_data["message"].replace('\n', '<br>')) mail_context_html = dict(team=self.object, message=form.cleaned_data["message"].replace('\n', '<br>'))
mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context_plain) mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context_plain)
mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context_html) mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context_html)
send_mail("[TFJM²] Équipe non validée", mail_plain, None, [self.object.email], send_mail(f"[{settings.APP_NAME}] {_('Team not validated')}", mail_plain,
html_message=mail_html) None, [self.object.email], html_message=mail_html)
else: else:
form.add_error(None, _("You must specify if you validate the registration or not.")) form.add_error(None, _("You must specify if you validate the registration or not."))
return self.form_invalid(form) return self.form_invalid(form)
@ -1135,7 +1137,7 @@ class PoolJuryView(VolunteerMixin, FormView, DetailView):
user.save() user.save()
# Send welcome mail # Send welcome mail
subject = "[TFJM²] " + str(_("New TFJM² jury account")) subject = f"[{settings.APP_NAME}] " + str(_("New jury account"))
site = Site.objects.first() site = Site.objects.first()
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user, message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
inviter=self.request.user, inviter=self.request.user,

View File

@ -50,7 +50,7 @@ class Registration(PolymorphicModel):
The account got created or the email got changed. The account got created or the email got changed.
Send an email that contains a link to validate the address. Send an email that contains a link to validate the address.
""" """
subject = "[TFJM²] " + str(_("Activate your TFJM² account")) subject = f"[{settings.APP_NAME}] " + str(_("Activate your account"))
token = email_validation_token.make_token(self.user) token = email_validation_token.make_token(self.user)
uid = urlsafe_base64_encode(force_bytes(self.user.pk)) uid = urlsafe_base64_encode(force_bytes(self.user.pk))
site = Site.objects.first() site = Site.objects.first()
@ -309,7 +309,7 @@ class ParticipantRegistration(Registration):
The team is selected for final. The team is selected for final.
""" """
translation.activate(settings.PREFERRED_LANGUAGE_CODE) translation.activate(settings.PREFERRED_LANGUAGE_CODE)
subject = "[TFJM²] " + str(_("Team selected for the final tournament")) subject = f"[{settings.APP_NAME}] " + str(_("Team selected for the final tournament"))
site = Site.objects.first() site = Site.objects.first()
from participation.models import Tournament from participation.models import Tournament
tournament = Tournament.final_tournament() tournament = Tournament.final_tournament()
@ -803,7 +803,7 @@ class Payment(models.Model):
def send_remind_mail(self): def send_remind_mail(self):
translation.activate(settings.PREFERRED_LANGUAGE_CODE) translation.activate(settings.PREFERRED_LANGUAGE_CODE)
subject = "[TFJM²] " + str(_("Reminder for your payment")) subject = f"[{settings.APP_NAME}] " + str(_("Reminder for your payment"))
site = Site.objects.first() site = Site.objects.first()
for registration in self.registrations.all(): for registration in self.registrations.all():
message = loader.render_to_string('registration/mails/payment_reminder.txt', message = loader.render_to_string('registration/mails/payment_reminder.txt',
@ -814,7 +814,7 @@ class Payment(models.Model):
def send_helloasso_payment_confirmation_mail(self): def send_helloasso_payment_confirmation_mail(self):
translation.activate(settings.PREFERRED_LANGUAGE_CODE) translation.activate(settings.PREFERRED_LANGUAGE_CODE)
subject = "[TFJM²] " + str(_("Payment confirmation")) subject = f"[{settings.APP_NAME}] " + str(_("Payment confirmation"))
site = Site.objects.first() site = Site.objects.first()
for registration in self.registrations.all(): for registration in self.registrations.all():
message = loader.render_to_string('registration/mails/payment_confirmation.txt', message = loader.render_to_string('registration/mails/payment_confirmation.txt',

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<p>
Bonjour {{ user.registration }},
</p>
<p>
Félicitations ! Votre équipe {{ user.registration.team.name }} ({{ user.registration.team.trigram }})
est sélectionnée pour le tournoi final du TFJM² !
</p>
<p>
La finale aura lieu du {{ tournament.date_start|date:"d/m/Y" }} au {{ tournament.date_end|date:"d/m/Y" }}
à : {{ tournament.place }}.
Les organisateurices de la finale vous recontacteront pour plus de détails.
</p>
<p>
D'ores-et-déjà, vous pouvez soumettre votre autorisation de droit à l'image spécifique à la finale sur votre espace personnel :
<a href="https://{{ domain }}{% url 'registration:user_detail' pk=user.pk %}">
https://{{ domain }}{% url 'registration:user_detail' pk=user.pk %}
</a>.
{% if user.registration.is_student and user.registration.under_18_final %}
Vous pouvez également transmettre puisque vous êtes mineur⋅e votre autorisation parentale spécifique pour la finale sur la même page.
{% endif %}
</p>
<p>
{% if tournament.price > 0 %}
{% if user.registration.is_student %}
{% if payment.type == "scholarship" %}
Votre statut de boursièr⋅e déjà enregistré vous exempte à nouveau des frais de participation de la finale.
{% else %}
Vous devez régler les frais de participation à la finale de {{ tournament.price }} €.
Rendez-vous pour cela sur la page du paiement :
<a href="https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}">
https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}
</a>.
{% endif %}
{% else %}
En tant qu'encadrant⋅e, vous n'avez toujours rien à payer, mais veillez bien à ce que les membres de votre équipe
règlent les frais de participation à la finale de {{ tournament.price }} €.
{% endif %}
{% endif %}
</p>
<p>
Conformément au règlement du TFJM², vous pouvez soumettre de nouvelles versions de vos solutions,
pour améliorer vos explications, corriger des erreurs mineures ou la mise en page, ou supprimer
des éléments faux, mais il vous est en revanche interdit d'ajouter des résultats ou des preuves
ou de corriger des erreurs majeures.
</p>
<p>
Pour mettre à jour vos solutions, rendez-vous sur la page de votre équipe :
<a href="https://{{ domain }}{% url 'participation:participation_detail' pk=user.registration.team.participation.pk %}">
https://{{ domain }}{% url 'participation:participation_detail' pk=user.registration.team.participation.pk %}
</a>.
</p>
<p>
Cordialement,
</p>
--
<p>
L'équipe du TFJM²
</p>
</body>
</html>

View File

@ -0,0 +1,38 @@
Bonjour {{ user.registration }},
Félicitations ! Votre équipe {{ user.registration.team.name }} ({{ user.registration.team.trigram }}) est sélectionnée pour le tournoi final du TFJM² !
La finale aura lieu du {{ tournament.date_start|date:"d/m/Y" }} au {{ tournament.date_end|date:"d/m/Y" }} à : {{ tournament.place }}.
Les organisateurices de la finale vous recontacteront pour plus de détails.
D'ores-et-déjà, vous pouvez soumettre votre autorisation de droit à l'image spécifique à la finale sur votre espace personnel :
https://{{ domain }}{% url 'registration:user_detail' pk=user.pk %}
{% if user.registration.is_student and user.registration.under_18_final %}
Vous pouvez également transmettre puisque vous êtes mineur⋅e votre autorisation parentale spécifique pour la finale sur la même page.
{% endif %}
{% if tournament.price > 0 %}
{% if user.registration.is_student %}
{% if payment.type == "scholarship" %}
Votre statut de boursièr⋅e déjà enregistré vous exempte à nouveau des frais de participation de la finale.
{% else %}
Vous devez régler les frais de participation à la finale de {{ tournament.price }} €.
Rendez-vous pour cela sur la page du paiement :
https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}
{% endif %}
{% else %}
En tant qu'encadrant⋅e, vous n'avez toujours rien à payer, mais veillez bien à ce que les membres de votre équipe
règlent les frais de participation à la finale de {{ tournament.price }} €.
{% endif %}
{% endif %}
Conformément au règlement du TFJM², vous pouvez soumettre de nouvelles versions de vos solutions,
pour améliorer vos explications, corriger des erreurs mineures ou la mise en page, ou supprimer
des éléments faux, mais il vous est en revanche interdit d'ajouter des résultats ou des preuves
ou de corriger des erreurs majeures.
Pour mettre à jour vos solutions, rendez-vous sur la page de votre équipe :
https://{{ domain }}{% url 'participation:participation_detail' pk=user.registration.team.participation.pk %}
Cordialement,
--
L'équipe du TFJM²

View File

@ -18,7 +18,7 @@ from django.http import FileResponse, Http404
from django.shortcuts import redirect, resolve_url from django.shortcuts import redirect, resolve_url
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone, translation from django.utils import translation
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.text import format_lazy from django.utils.text import format_lazy
@ -121,7 +121,7 @@ class AddOrganizerView(VolunteerMixin, CreateView):
form.instance.set_password(password) form.instance.set_password(password)
form.instance.save() form.instance.save()
subject = "[TFJM²] " + str(_("New TFJM² organizer account")) subject = f"[{settings.APP_NAME}] " + str(_("New organizer account"))
site = Site.objects.first() site = Site.objects.first()
message = render_to_string('registration/mails/add_organizer.txt', dict(user=registration.user, message = render_to_string('registration/mails/add_organizer.txt', dict(user=registration.user,
inviter=self.request.user, inviter=self.request.user,

View File

@ -1,5 +1,7 @@
from django.conf import settings # Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from participation.models import Tournament from participation.models import Tournament
@ -7,6 +9,7 @@ def tfjm_context(request):
return { return {
'TFJM': { 'TFJM': {
'APP': settings.TFJM_APP, 'APP': settings.TFJM_APP,
'APP_NAME': settings.APP_NAME,
'ML_MANAGEMENT': settings.ML_MANAGEMENT, 'ML_MANAGEMENT': settings.ML_MANAGEMENT,
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT, 'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
'SINGLE_TOURNAMENT': 'SINGLE_TOURNAMENT':

View File

@ -347,6 +347,7 @@ except ImportError:
if TFJM_APP == "TFJM": if TFJM_APP == "TFJM":
PREFERRED_LANGUAGE_CODE = 'fr' PREFERRED_LANGUAGE_CODE = 'fr'
APP_NAME = "TFJM²"
TEAM_CODE_LENGTH = 3 TEAM_CODE_LENGTH = 3
RECOMMENDED_SOLUTIONS_COUNT = 5 RECOMMENDED_SOLUTIONS_COUNT = 5
NB_ROUNDS = 2 NB_ROUNDS = 2
@ -369,6 +370,7 @@ if TFJM_APP == "TFJM":
] ]
elif TFJM_APP == "ETEAM": elif TFJM_APP == "ETEAM":
PREFERRED_LANGUAGE_CODE = 'en' PREFERRED_LANGUAGE_CODE = 'en'
APP_NAME = "TFJM²"
TEAM_CODE_LENGTH = 4 TEAM_CODE_LENGTH = 4
RECOMMENDED_SOLUTIONS_COUNT = 6 RECOMMENDED_SOLUTIONS_COUNT = 6
NB_ROUNDS = 3 NB_ROUNDS = 3

View File

@ -9,11 +9,13 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title> <title>
{# TODO ETEAM Plus d'uniformité #} {% block title %}{{ title }}{% endblock title %} - {{ TFJM.APP_NAME }}
{% block title %}{{ title }}{% endblock title %} - {% trans "ETEAM Platform" %}
</title> </title>
{# TODO ETEAM Plus d'uniformité #} {% if TFJM.APP == "TFJM" %}
<meta name="description" content="{% trans "Registration platform to the ETEAM." %}"> <meta name="description" content="{% trans "Registration platform to the TFJM²." %}">
{% elif TFJM.APP == "ETEAM" %}
<meta name="description" content="{% trans "Registration platform to the ETEAM." %}">
{% endif %}
{# Favicon #} {# Favicon #}
<link rel="shortcut icon" href="{% static "favicon.ico" %}"> <link rel="shortcut icon" href="{% static "favicon.ico" %}">

View File

@ -26,7 +26,7 @@ deps =
pep8-naming pep8-naming
pyflakes pyflakes
commands = commands =
flake8 api/ chat/ss draw/ logs/ participation/ registration/ tfjm/ flake8 api/ chat/ draw/ logs/ participation/ registration/ tfjm/
[flake8] [flake8]
exclude = exclude =