1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-06-21 07:18:25 +02:00

Clone Corres2math platform

This commit is contained in:
Yohann D'ANELLO
2020-12-27 11:49:54 +01:00
parent 3d9bd88a41
commit 03eca29316
151 changed files with 10032 additions and 0 deletions

2
tfjm/__init__.py Normal file
View File

@ -0,0 +1,2 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

19
tfjm/asgi.py Normal file
View File

@ -0,0 +1,19 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
"""
ASGI config for tfjm project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
application = get_asgi_application()

26
tfjm/lists.py Normal file
View File

@ -0,0 +1,26 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os
_client = None
def get_sympa_client():
global _client
if _client is None:
if os.getenv("SYMPA_PASSWORD", None) is not None: # pragma: no cover
from sympasoap import Client
_client = Client("https://" + os.getenv("SYMPA_URL"))
_client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD"))
else:
_client = FakeSympaSoapClient()
return _client
class FakeSympaSoapClient:
"""
Simulate a Sympa Soap client to run tests, if no Sympa instance is connected.
"""
def __getattribute__(self, item):
return lambda *args, **kwargs: None

393
tfjm/matrix.py Normal file
View File

@ -0,0 +1,393 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from enum import Enum
import os
from asgiref.sync import async_to_sync
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"):
return FakeMatrixClient()
from nio import AsyncClient
client = AsyncClient("https://tfjm.org", "@tfjmbot:tfjm.org")
client.user_id = "@tfjmbot:tfjm.org"
if 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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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_to_sync
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

85
tfjm/middlewares.py Normal file
View File

@ -0,0 +1,85 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from threading import local
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
_thread_locals = local()
def _set_current_user_and_ip(user=None, session=None, ip=None):
setattr(_thread_locals, USER_ATTR_NAME, user)
setattr(_thread_locals, SESSION_ATTR_NAME, session)
setattr(_thread_locals, IP_ATTR_NAME, ip)
def get_current_user() -> User:
return getattr(_thread_locals, USER_ATTR_NAME, None)
def get_current_ip() -> str:
return getattr(_thread_locals, IP_ATTR_NAME, None)
def get_current_authenticated_user():
current_user = get_current_user()
return None if isinstance(current_user, AnonymousUser) else current_user
class SessionMiddleware(object):
"""
This middleware get the current user with his or her IP address on each request.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if "_fake_user_id" in request.session:
request.user = User.objects.get(pk=request.session["_fake_user_id"])
user = request.user
ip = request.META.get('HTTP_X_REAL_IP' if 'HTTP_X_REAL_IP' in request.META else 'REMOTE_ADDR')
_set_current_user_and_ip(user, request.session, ip)
response = self.get_response(request)
_set_current_user_and_ip(None, None, None)
return response
class TurbolinksMiddleware(object): # pragma: no cover
"""
Send the `Turbolinks-Location` header in response to a visit that was redirected,
and Turbolinks will replace the browser's topmost history entry.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
is_turbolinks = request.META.get('HTTP_TURBOLINKS_REFERRER')
is_response_redirect = response.has_header('Location')
if is_turbolinks:
if is_response_redirect:
location = response['Location']
prev_location = request.session.pop('_turbolinks_redirect_to', None)
if prev_location is not None:
# relative subsequent redirect
if location.startswith('.'):
location = prev_location.split('?')[0] + location
request.session['_turbolinks_redirect_to'] = location
else:
if request.session.get('_turbolinks_redirect_to'):
location = request.session.pop('_turbolinks_redirect_to')
response['Turbolinks-Location'] = location
return response

227
tfjm/settings.py Normal file
View File

@ -0,0 +1,227 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Django settings for tfjm project.
Generated by 'django-admin startproject' using Django 3.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
import sys
from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
APPS_DIR = os.path.realpath(os.path.join(BASE_DIR, "apps"))
sys.path.append(APPS_DIR)
ADMINS = [("Yohann D'ANELLO", "yohann.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'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
SITE_ID = 1
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.forms',
'bootstrap_datepicker_plus',
'crispy_forms',
'django_tables2',
'haystack',
'logs',
'polymorphic',
'rest_framework',
'rest_framework.authtoken',
'api',
'eastereggs',
'registration',
'participation',
]
if "test" not in sys.argv: # pragma: no cover
INSTALLED_APPS += [
'cas_server',
'django_extensions',
'mailer',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'tfjm.middlewares.SessionMiddleware',
'tfjm.middlewares.TurbolinksMiddleware',
]
ROOT_URLCONF = 'tfjm.urls'
LOGIN_REDIRECT_URL = "index"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'tfjm/templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
WSGI_APPLICATION = 'tfjm.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
CAS_AUTH_CLASS = 'registration.auth.CustomAuthUser'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 50,
}
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en'
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
]
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_L10N = True
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/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "tfjm/static"),
]
STATIC_ROOT = os.path.join(BASE_DIR, "static")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
CRISPY_TEMPLATE_PACK = 'bootstrap4'
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html'
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
}
}
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
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ.get('DJANGO_DB_NAME', 'tfjm'),
'USER': os.environ.get('DJANGO_DB_USER', 'tfjm'),
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
'HOST': os.environ.get('DJANGO_DB_HOST', 'localhost'),
'PORT': os.environ.get('DJANGO_DB_PORT', ''), # Use default port
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, os.getenv('DJANGO_DB_HOST', 'db.sqlite3')),
}
}
if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover
from .settings_prod import * # noqa: F401,F403
else:
from .settings_dev import * # noqa: F401,F403

7
tfjm/settings_dev.py Normal file
View File

@ -0,0 +1,7 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

33
tfjm/settings_prod.py Normal file
View File

@ -0,0 +1,33 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os
# Break it, fix it!
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'
EMAIL_USE_SSL = True
EMAIL_HOST = os.getenv("SMTP_HOST")
EMAIL_PORT = os.getenv("SMTP_PORT")
EMAIL_HOST_USER = os.getenv("SMTP_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("SMTP_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = os.getenv('FROM_EMAIL', 'Contact TFJM² <contact@tfjm.org>')
SERVER_EMAIL = os.getenv('SERVER_EMAIL', 'contact@tfjm.org')
# Security settings
SECURE_CONTENT_TYPE_NOSNIFF = False
SECURE_BROWSER_XSS_FILTER = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
X_FRAME_OPTIONS = 'DENY'
SESSION_COOKIE_AGE = 60 * 60 * 3

View File

@ -0,0 +1,113 @@
\documentclass[a4paper,french,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage[frenchb]{babel}
\usepackage{fancyhdr}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}
%\usepackage{anyfontsize}
\usepackage{fancybox}
\usepackage{eso-pic,graphicx}
\usepackage{xcolor}
% Specials
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
% Page formating
\hoffset -1in
\voffset -1in
\textwidth 180 mm
\textheight 250 mm
\oddsidemargin 15mm
\evensidemargin 15mm
\pagestyle{fancy}
% Headers and footers
\fancyfoot{}
\lhead{}
\rhead{}
\renewcommand{\headrulewidth}{0pt}
\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018}
\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.}
\begin{document}
\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
\vfill
\begin{center}
\LARGE
Autorisation d'enregistrement et de diffusion de l'image ({TOURNAMENT_NAME})
\end{center}
\normalsize
\thispagestyle{empty}
\bigskip
Je soussign\'e {PARTICIPANT_NAME}\\
demeurant au {ADDRESS}
\medskip
Cochez la/les cases correspondantes.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ du {START_DATE} au {END_DATE} {YEAR} à : {PLACE}, \`a me photographier ou \`a me filmer et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit dutiliser mon image sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\
\medskip
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la publication et la diffusion de l'image ainsi que des commentaires l'accompagnant ne portent pas atteinte \`a la vie priv\'ee, \`a la dignit\'e et \`a la r\'eputation de la personne photographiée.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise la diffusion dans les medias (Presse, T\'el\'evision, Internet) de photographies prises \`a l'occasion dune \'eventuelle m\'ediatisation de cet événement.\\
\medskip
Conform\'ement \`a la loi informatique et libert\'es du 6 janvier 1978, vous disposez d'un droit de libre acc\`es, de rectification, de modification et de suppression des donn\'ees qui vous concernent.
Cette autorisation est donc r\'evocable \`a tout moment sur volont\'e express\'ement manifest\'ee par lettre recommand\'ee avec accus\'e de r\'eception adress\'ee \`a Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise Animath à conserver mes données personnelles, dans le cadre défini par la loi n 78-17 du 6 janvier 1978 relative à l'informatique, aux fichiers et aux libertés et les textes la modifiant, pendant une durée de quatre ans à compter de ma dernière participation à un événement organisé par Animath.\\
\medskip
\fbox{\textcolor{white}{A}} J'accepte d'être tenu informé d'autres activités organisées par l'association et ses partenaires.
\bigskip
Signature pr\'ec\'ed\'ee de la mention \og lu et approuv\'e \fg{}
\medskip
\begin{minipage}[c]{0.5\textwidth}
\underline{L'\'el\`eve :}\\
Fait \`a :\\
le
\end{minipage}
\vfill
\vfill
\begin{minipage}[c]{0.5\textwidth}
\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018
\end{minipage}
\begin{minipage}[c]{0.5\textwidth}
\footnotesize
\begin{flushright}
Association agréée par\\le Ministère de l'éducation nationale.
\end{flushright}
\end{minipage}
\end{document}

View File

@ -0,0 +1,122 @@
\documentclass[a4paper,french,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage[frenchb]{babel}
\usepackage{fancyhdr}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}
%\usepackage{anyfontsize}
\usepackage{fancybox}
\usepackage{eso-pic,graphicx}
\usepackage{xcolor}
% Specials
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
% Page formating
\hoffset -1in
\voffset -1in
\textwidth 180 mm
\textheight 250 mm
\oddsidemargin 15mm
\evensidemargin 15mm
\pagestyle{fancy}
% Headers and footers
\fancyfoot{}
\lhead{}
\rhead{}
\renewcommand{\headrulewidth}{0pt}
\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018}
\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.}
\begin{document}
\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
\vfill
\begin{center}
\LARGE
Autorisation d'enregistrement et de diffusion de l'image
({TOURNAMENT_NAME})
\end{center}
\normalsize
\thispagestyle{empty}
\bigskip
Je soussign\'e \dotfill (p\`ere, m\`ere, responsable l\'egal) \\
agissant en qualit\'e de repr\'esentant de {PARTICIPANT_NAME}\\
demeurant au {ADDRESS}
\medskip
Cochez la/les cases correspondantes.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ du {START_DATE} au {END_DATE} {YEAR} à : {PLACE}, \`a photographier ou \`a filmer l'enfant et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit dutiliser l'image de l'enfant sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\
\medskip
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la publication et la diffusion de l'image de l'enfant ainsi que des commentaires l'accompagnant ne portent pas atteinte \`a la vie priv\'ee, \`a la dignit\'e et \`a la r\'eputation de lenfant.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise la diffusion dans les medias (Presse, T\'el\'evision, Internet) de photographies de mon enfant prises \`a l'occasion dune \'eventuelle m\'ediatisation de cet événement.\\
\medskip
Conform\'ement \`a la loi informatique et libert\'es du 6 janvier 1978, vous disposez d'un droit de libre acc\`es, de rectification, de modification et de suppression des donn\'ees qui vous concernent.
Cette autorisation est donc r\'evocable \`a tout moment sur volont\'e express\'ement manifest\'ee par lettre recommand\'ee avec accus\'e de r\'eception adress\'ee \`a Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\
\medskip
\fbox{\textcolor{white}{A}} Autorise Animath à conserver mes données personnelles, dans le cadre défini par la loi n 78-17 du 6 janvier 1978 relative à l'informatique, aux fichiers et aux libertés et les textes la modifiant, pendant une durée de quatre ans à compter de ma dernière participation à un événement organisé par Animath.\\
\medskip
\fbox{\textcolor{white}{A}} J'accepte d'être tenu informé d'autres activités organisées par l'association et ses partenaires.
\bigskip
Signatures pr\'ec\'ed\'ees de la mention \og lu et approuv\'e \fg{}
\medskip
\begin{minipage}[c]{0.5\textwidth}
\underline{Le responsable l\'egal :}\\
Fait \`a :\\
le :
\end{minipage}
\begin{minipage}[c]{0.5\textwidth}
\underline{L'\'el\`eve :}\\
Fait \`a :\\
le
\end{minipage}
\vfill
\vfill
\begin{minipage}[c]{0.5\textwidth}
\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018
\end{minipage}
\begin{minipage}[c]{0.5\textwidth}
\footnotesize
\begin{flushright}
Association agréée par\\le Ministère de l'éducation nationale.
\end{flushright}
\end{minipage}
\end{document}

View File

@ -0,0 +1,66 @@
\documentclass[a4paper,french,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage[french]{babel}
\usepackage{fancyhdr}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}
%\usepackage{anyfontsize}
\usepackage{fancybox}
\usepackage{eso-pic,graphicx}
\usepackage{xcolor}
% Specials
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
% Page formating
\hoffset -1in
\voffset -1in
\textwidth 180 mm
\textheight 250 mm
\oddsidemargin 15mm
\evensidemargin 15mm
\pagestyle{fancy}
% Headers and footers
\fancyfoot{}
\lhead{}
\rhead{}
\renewcommand{\headrulewidth}{0pt}
\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018}
\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.}
\begin{document}
\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
\vfill
\begin{center}
\Large \bf Autorisation parentale pour les mineurs ({TOURNAMENT_NAME})
\end{center}
Je soussigné(e) \hrulefill,\\
responsable légal, demeurant \writingsep\hrulefill\\
\writingsep\hrulefill,\\
\writingsep autorise {PARTICIPANT_NAME},\\
né(e) le {BIRTHDAY},
à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) organisé \`a : {PLACE}, du {START_DATE} au {END_DATE} {YEAR}.
{PRONOUN} se rendra au lieu indiqu\'e ci-dessus le vendredi matin et quittera les lieux l'après-midi du dimanche par ses propres moyens et sous la responsabilité du représentant légal.
\vspace{8ex}
Fait à \vrule width 10cm height 0pt depth 0.4pt, le \phantom{232323}/\phantom{XXX}/{YEAR},
\vfill
\vfill
\end{document}

Binary file not shown.

View File

@ -0,0 +1,194 @@
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[french]{babel}
\usepackage{graphicx}
\usepackage[left=2cm,right=2cm,top=2cm,bottom=2cm]{geometry} % marges
\usepackage{amsthm}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{tikz}
\newcommand{\N}{{\bf N}}
\newcommand{\Z}{{\bf Z}}
\newcommand{\Q}{{\bf Q}}
\newcommand{\R}{{\bf R}}
\newcommand{\C}{{\bf C}}
\newcommand{\A}{{\bf A}}
\newtheorem{theo}{Théorème}
\newtheorem{theo-defi}[theo]{Théorème-Définition}
\newtheorem{defi}[theo]{Définition}
\newtheorem{lemme}[theo]{Lemme}
\newtheorem{slemme}[theo]{Sous-lemme}
\newtheorem{prop}[theo]{Proposition}
\newtheorem{coro}[theo]{Corollaire}
\newtheorem{conj}[theo]{Conjecture}
\title{Note de synthèse}
\begin{document}
\pagestyle{empty}
\begin{center}
\begin{Huge}
$\mathbb{TFJM}^2$
\end{Huge}
\bigskip
\begin{Large}
NOTE DE SYNTHESE
\end{Large}
\end{center}
Tour \underline{~~~~} poule \underline{~~~~}
\medskip
Problème \underline{~~~~} défendu par l'équipe \underline{~~~~~~~~~~~~~~~~~~~~~~~~}
\medskip
Synthèse par l'équipe \underline{~~~~~~~~~~~~~~~~~~~~~~~~} dans le rôle de : ~ $\square$ Opposant ~ $\square$ Rapporteur
\section*{Questions traitées}
\begin{tabular}{r c l}
\begin{tabular}{|c|c|c|c|c|c|}
\hline
Question ~ & ER & ~PR~ & QE & NT \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
\end{tabular}
& ~~ &
\begin{tabular}{|c|c|c|c|c|c|}
\hline
Question ~ & ER & ~PR~ & QE & NT \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
& & & & \\
\hline
\end{tabular} \\
& & \\
ER : entièrement résolue & & PR : partiellement résolue \\
\smallskip
QE : quelques éléments de réponse & & NT : non traitée
\end{tabular}
~
\smallskip
Remarque : il est possible de cocher entre les cases pour un cas intermédiaire.
\section*{Evaluation qualitative de la solution}
Donnez votre avis concernant la solution. Mettez notamment en valeur les points positifs (des idées
importantes, originales, etc.) et précisez ce qui aurait pu améliorer la solution.
\vfill
\textbf{Evaluation générale :} ~ $\square$ Excellente ~ $\square$ Bonne ~ $\square$ Suffisante ~ $\square$ Passable
\newpage
\section*{Erreurs et imprécisions}
Listez ci-dessous les cinq erreurs et/ou imprécisions les plus importantes selon vous, par ordre d'importance, en précisant la
question concernée, la page, le paragraphe et le type de remarque.
\bigskip
1. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~}
$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~}
Description :
\vfill
2. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~}
$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~}
Description :
\vfill
3. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~}
$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~}
Description :
\vfill
4. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~}
$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~}
Description :
\vfill
5. Question \underline{~~~~} Page \underline{~~~~} Paragraphe \underline{~~~~}
$\square$ Erreur majeure ~ $\square$ Erreur mineure ~ $\square$ Imprécision ~ $\square$ Autre : \underline{~~~~~~~~}
Description :
\vfill
\section*{Remarques formelles (facultatif)}
Donnez votre avis concernant la présentation de la solution (lisibilité, etc.).
\vfill
\end{document}

Binary file not shown.

View File

@ -0,0 +1,88 @@
\documentclass[a4paper,french,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage[frenchb]{babel}
\usepackage{fancyhdr}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}
%\usepackage{anyfontsize}
\usepackage{fancybox}
\usepackage{eso-pic,graphicx}
\usepackage{xcolor}
\usepackage{hyperref}
% Specials
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
% Page formating
\hoffset -1in
\voffset -1in
\textwidth 180 mm
\textheight 250 mm
\oddsidemargin 15mm
\evensidemargin 15mm
\pagestyle{fancy}
% Headers and footers
\fancyfoot{}
\lhead{}
\rhead{}
\renewcommand{\headrulewidth}{0pt}
\lfoot{\footnotesize 11 rue Pierre et Marie Curie, 75231 Paris Cedex 05\\ Numéro siret 431 598 366 00018}
\rfoot{\footnotesize Association agréée par\\le Ministère de l'éducation nationale.}
\begin{document}
\includegraphics[height=2cm]{assets/logo_animath.png}\hfill{\fontsize{50pt}{50pt}{$\mathbb{TFJM}^2$}}
\begin{center}
\Large \bf Instructions ({TOURNAMENT_NAME})
\end{center}
\section{Documents}
\subsection{Autorisation parentale}
Elle est nécessaire si l'élève est mineur au moment du tournoi (y compris si son anniversaire est pendant le tournoi).
\subsection{Autorisation de prise de vue}
Si l'élève est mineur \textbf{au moment de la signature}, il convient de remplir l'autorisation pour les mineurs. En revanche, s'il est majeur \textbf{au moment de la signature}, il convient de remplir la fiche pour majeur.
\subsection{Fiche sanitaire}
Elle est nécessaire si l'élève est mineur au moment du tournoi (y compris si son anniversaire est pendant le tournoi).
\section{Paiement}
\subsection{Montant}
Les frais d'inscription sont fixés à {PRICE} euros. Vous devez vous en acquitter \textbf{avant le {END_PAYMENT_DATE} {YEAR}}. Si l'élève est boursier, il en est dispensé, vous devez alors fournir une copie de sa notification de bourse directement sur la plateforme \textbf{avant le {END_PAYMENT_DATE} {YEAR}}.
\subsection{Procédure}
Si le paiement de plusieurs élèves est fait en une seule opération, merci de contacter \href{mailto: contact@tfjm.org}{contact@tfjm.org} \textbf{avant le paiement} pour garantir l'identification de ce dernier
\subsubsection*{Carte bancaire (uniquement les cartes françaises)}
Le paiement s'effectue en ligne via la plateforme à l'adresse : \url{https://www.helloasso.com/associations/animath/evenements/tfjm-2020}
Vous devez impérativement indiquer dans le champ "Référence" la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}.
\subsubsection*{Virement}
\textbf{Si vous ne pouvez pas utiliser le paiement par carte}, vous pouvez faire un virement sur le compte ci-dessous en indiquant bien dans le champ "motif" (ou autre champ propre à votre banque dont le contenu est communiqué au destinataire) la mention "TFJMpu" suivie des noms et prénoms \textbf{de l'élève}.
IBAN FR76 1027 8065 0000 0206 4290 127
BIC CMCIFR2A
\subsubsection*{Autre}
Si aucune de ces procédures n'est possible pour vous, envoyez un mail à \href{mailto: contact@tfjm.org}{contact@tfjm.org} pour que nous trouvions une solution à vos difficultés.
\end{document}

BIN
tfjm/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

114
tfjm/static/logo.svg Normal file
View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="209.843 -2.284 30.311995 9.7779996"
version="1.1"
id="svg27"
sodipodi:docname="logo.svg"
width="30.311995"
height="9.7779999"
style="fill:black"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata31">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1055"
id="namedview29"
showgrid="false"
inkscape:zoom="41.779237"
inkscape:cx="15.215997"
inkscape:cy="4.3644999"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="svg27" />
<defs
id="defs15">
<path
id="b"
d="m 2.58,-3.347 c 0.409,0 1.405,0.02 1.485,1.135 0.01,0.12 0.02,0.25 0.18,0.25 0.168,0 0.168,-0.14 0.168,-0.32 v -2.7 c 0,-0.159 0,-0.318 -0.169,-0.318 -0.13,0 -0.17,0.1 -0.18,0.21 -0.059,1.155 -0.756,1.354 -1.484,1.384 v -2.102 c 0,-0.668 0.19,-0.668 0.429,-0.668 h 0.468 c 1.275,0 1.923,0.688 1.983,1.375 0.01,0.08 0.02,0.23 0.179,0.23 0.17,0 0.17,-0.16 0.17,-0.33 v -1.295 c 0,-0.308 -0.02,-0.328 -0.33,-0.328 h -5 c -0.18,0 -0.34,0 -0.34,0.179 0,0.17 0.19,0.17 0.27,0.17 0.567,0 0.607,0.079 0.607,0.567 v 4.991 c 0,0.469 -0.03,0.568 -0.558,0.568 -0.15,0 -0.319,0 -0.319,0.17 C 0.14,0 0.3,0 0.48,0 h 2.878 c 0.18,0 0.33,0 0.33,-0.18 0,-0.169 -0.17,-0.169 -0.3,-0.169 -0.767,0 -0.807,-0.07 -0.807,-0.597 v -2.401 z m 2.88,-3.129 v 0.469 A 2.557,2.557 0 0 0 4.922,-6.476 Z M 4.065,-3.158 A 1.51,1.51 0 0 0 3.537,-3.547 c 0.189,-0.09 0.388,-0.249 0.528,-0.418 z m -2.7,-2.77 c 0,-0.12 0,-0.368 -0.08,-0.548 h 1.056 c -0.11,0.23 -0.11,0.558 -0.11,0.648 v 4.901 c 0,0.15 0,0.389 0.1,0.578 H 1.285 c 0.08,-0.179 0.08,-0.428 0.08,-0.548 v -5.03 z"
inkscape:connector-curvature="0" />
<path
id="c"
d="m 1.564,-6.824 c -0.18,0 -0.339,0 -0.339,0.179 0,0.17 0.18,0.17 0.29,0.17 0.687,0 0.727,0.069 0.727,0.577 v 5.59 c 0,0.169 0,0.358 -0.17,0.527 -0.08,0.07 -0.239,0.18 -0.478,0.18 -0.07,0 -0.369,0 -0.369,-0.11 0,-0.08 0.04,-0.12 0.09,-0.17 A 0.704,0.704 0 0 0 0.777,-1.057 0.704,0.704 0 0 0 0.06,-0.359 c 0,0.629 0.637,1.106 1.604,1.106 1.106,0 2.042,-0.387 2.192,-1.614 0.01,-0.09 0.01,-0.647 0.01,-0.966 v -4.184 c 0,-0.449 0.139,-0.449 0.707,-0.459 0.09,0 0.17,-0.08 0.17,-0.17 0,-0.178 -0.15,-0.178 -0.33,-0.178 z M 0.867,0.239 C 0.767,0.19 0.408,0.02 0.408,-0.349 c 0,-0.259 0.22,-0.358 0.37,-0.358 0.168,0 0.368,0.12 0.368,0.348 0,0.15 -0.08,0.24 -0.12,0.27 -0.04,0.04 -0.13,0.139 -0.16,0.328 z M 2.59,-5.918 c 0,-0.11 0,-0.378 -0.09,-0.558 h 1.097 c -0.08,0.18 -0.08,0.369 -0.08,0.708 v 4.015 c 0,0.298 0,0.797 -0.01,0.896 C 3.427,-0.349 3.198,0.11 2.44,0.31 2.59,0.08 2.59,-0.109 2.59,-0.288 v -5.629 z"
inkscape:connector-curvature="0" />
<path
id="d"
d="M 4.643,-2.092 2.74,-6.625 c -0.08,-0.2 -0.09,-0.2 -0.359,-0.2 H 0.528 c -0.18,0 -0.329,0 -0.329,0.18 0,0.17 0.18,0.17 0.23,0.17 0.119,0 0.388,0.02 0.607,0.099 v 5.32 c 0,0.21 0,0.648 -0.677,0.707 -0.19,0.02 -0.19,0.16 -0.19,0.17 C 0.17,0 0.33,0 0.51,0 h 1.543 c 0.18,0 0.33,0 0.33,-0.18 0,-0.089 -0.08,-0.159 -0.16,-0.169 -0.767,-0.06 -0.767,-0.478 -0.767,-0.707 v -4.961 l 0.01,-0.01 2.429,5.817 c 0.08,0.18 0.15,0.209 0.21,0.209 0.12,0 0.149,-0.08 0.199,-0.2 l 2.44,-5.827 0.01,0.01 v 4.961 c 0,0.21 0,0.648 -0.677,0.707 -0.19,0.02 -0.19,0.16 -0.19,0.17 0,0.179 0.16,0.179 0.34,0.179 h 2.66 c 0.179,0 0.328,0 0.328,-0.18 C 9.215,-0.27 9.135,-0.34 9.056,-0.35 8.289,-0.41 8.289,-0.828 8.289,-1.057 v -4.712 c 0,-0.21 0,-0.648 0.677,-0.708 0.1,-0.01 0.19,-0.06 0.19,-0.17 0,-0.178 -0.15,-0.178 -0.33,-0.178 H 6.905 c -0.259,0 -0.279,0 -0.369,0.209 z m -0.3,0.18 c 0.08,0.169 0.09,0.178 0.21,0.218 L 4.115,-0.638 H 4.095 L 1.823,-6.058 C 1.773,-6.187 1.693,-6.356 1.554,-6.476 h 0.867 l 1.923,4.563 z M 1.336,-0.35 h -0.17 c 0.02,-0.03 0.04,-0.06 0.06,-0.08 0.01,-0.01 0.01,-0.02 0.02,-0.03 z M 7.104,-6.477 H 8.16 c -0.219,0.25 -0.219,0.508 -0.219,0.688 v 4.752 c 0,0.18 0,0.438 0.23,0.687 H 6.883 c 0.22,-0.249 0.22,-0.508 0.22,-0.687 v -5.44 z"
inkscape:connector-curvature="0" />
<path
id="a"
d="m 4.135,-6.466 c 1.305,0.07 1.793,0.917 1.833,1.385 0.01,0.15 0.02,0.299 0.179,0.299 0.18,0 0.18,-0.17 0.18,-0.359 v -1.325 c 0,-0.348 -0.04,-0.358 -0.34,-0.358 H 0.658 c -0.308,0 -0.328,0.02 -0.328,0.318 V -5.1 c 0,0.16 0,0.319 0.17,0.319 0.17,0 0.178,-0.18 0.178,-0.2 0.04,-0.826 0.788,-1.424 1.834,-1.484 v 5.54 c 0,0.498 -0.04,0.577 -0.668,0.577 -0.12,0 -0.299,0 -0.299,0.17 0,0.179 0.16,0.179 0.339,0.179 h 2.89 C 4.95,0 5.1,0 5.1,-0.18 c 0,-0.169 -0.17,-0.169 -0.28,-0.169 -0.647,0 -0.686,-0.07 -0.686,-0.578 v -5.539 z m -3.458,-0.01 h 0.598 c -0.249,0.15 -0.458,0.349 -0.598,0.518 z m 5.3,0 v 0.528 A 2.606,2.606 0 0 0 5.37,-6.476 H 5.978 Z M 2.77,-0.349 c 0.09,-0.179 0.09,-0.428 0.09,-0.558 v -5.569 h 0.926 v 5.57 c 0,0.129 0,0.378 0.09,0.557 H 2.77 Z"
inkscape:connector-curvature="0" />
<path
id="e"
d="M 3.522,-1.27 H 3.285 c -0.021,0.154 -0.091,0.566 -0.182,0.635 -0.055,0.042 -0.592,0.042 -0.69,0.042 H 1.13 c 0.732,-0.648 0.976,-0.844 1.395,-1.171 0.516,-0.412 0.997,-0.844 0.997,-1.507 0,-0.844 -0.74,-1.36 -1.632,-1.36 -0.865,0 -1.45,0.607 -1.45,1.249 0,0.355 0.3,0.39 0.369,0.39 0.167,0 0.37,-0.118 0.37,-0.37 0,-0.125 -0.05,-0.369 -0.412,-0.369 0.216,-0.495 0.69,-0.649 1.018,-0.649 0.698,0 1.06,0.544 1.06,1.11 0,0.606 -0.432,1.087 -0.655,1.338 l -1.68,1.66 C 0.44,-0.209 0.44,-0.195 0.44,0 h 2.873 z"
inkscape:connector-curvature="0" />
</defs>
<use
x="209.843"
y="6.6110001"
xlink:href="#a"
id="use17"
width="100%"
height="100%"
transform="translate(-0.33000232,0.13600003)" />
<use
x="216.485"
y="6.6110001"
xlink:href="#b"
id="use19"
width="100%"
height="100%"
transform="translate(-0.33000232,0.13600003)" />
<use
x="222.573"
y="6.6110001"
xlink:href="#c"
id="use21"
width="100%"
height="100%"
transform="translate(-0.33000232,0.13600003)" />
<use
x="227.554"
y="6.6110001"
xlink:href="#d"
id="use23"
width="100%"
height="100%"
transform="translate(-0.33000232,0.13600003)" />
<use
x="236.963"
y="2.211"
xlink:href="#e"
id="use25"
width="100%"
height="100%"
transform="translate(-0.33000232,0.13600003)" />
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

8
tfjm/templates/400.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Bad request" %}</h1>
{% blocktrans %}Sorry, your request was bad. Don't know what could be wrong. An email has been sent to webmasters with the details of the error. You can now watch some videos.{% endblocktrans %}
{% endblock %}

13
tfjm/templates/403.html Normal file
View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Permission denied" %}</h1>
{% blocktrans %}You don't have the right to perform this request.{% endblocktrans %}
{% if exception %}
<div>
{% trans "Exception message:" %} {{ exception }}
</div>
{% endif %}
{% endblock %}

13
tfjm/templates/404.html Normal file
View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Page not found" %}</h1>
{% blocktrans %}The requested path <code>{{ request_path }}</code> was not found on the server.{% endblocktrans %}
{% if exception != "Resolver404" %}
<div>
{% trans "Exception message:" %} {{ exception }}
</div>
{% endif %}
{% endblock %}

8
tfjm/templates/500.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Server error" %}</h1>
{% blocktrans %}Sorry, an error occurred when processing your request. An email has been sent to webmasters with the detail of the error, and this will be fixed soon. You can now watch some videos.{% endblocktrans %}
{% endblock %}

40
tfjm/templates/about.html Normal file
View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block contenttitle %}
<h1>À propos</h1>
{% endblock %}
{% block content %}
<p>
La plateforme d'inscription du TFJM² des Jeunes Mathématiciennes a été développée entre 2019 et 2021
par Yohann D'ANELLO, bénévole pour l'association Animath. Elle est vouée à être utilisée par les participants
pour intéragir avec les organisateurs et les autres participants.
</p>
<p>
La plateforme est développée avec le framework <a href="https://www.djangoproject.com/">Django</a> et le code
source est accessible librement sur <a href="https://gitlab.com/animath/si/plateforme-tfjm">Gitlab</a>.
Le code est distribué sous la licence <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPL v3</a>,
qui vous autorise à consulter le code, à le partager, à réutiliser des parties du code et à contribuer.
</p>
<p>
Le site principal présent sur <a href="https://inscription.tfjm.org/">https://inscription.tfjm.org</a>
est hébergé chez <a href="https://www.scaleway.com/fr/">Scaleway</a>.
</p>
<p>
Les données collectées par cette plateforme sont utilisées uniquement dans le cadre du TFJM² et sont
détruites dès l'action touche à sa fin, soit au plus tard 1 an après le début de l'action. Sur autorisation
explicite, des informations de contact peuvent être conservées afin d'être tenu au courant des actions futures
de l'association Animath. Aucune information personnelle n'est collectée à votre insu. Aucune information
personnelle n'est cédée à des tiers.
</p>
<p>
Pour toute demande ou réclammation, merci de nous contacter à l'adresse
<a target="_blank" href="mailto:&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;">
&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;
</a>.
</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
<div class="input-group">
<input class="form-control mx-auto d-block" type="number" min="0" step="0.01"
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}"
{% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}>
<div class="input-group-append">
<span class="input-group-text"></span>
</div>
</div>

View File

@ -0,0 +1,9 @@
<input type="hidden" name="{{ widget.name }}" {% if widget.attrs.model_pk %}value="{{ widget.attrs.model_pk }}"{% endif %} id="{{ widget.attrs.id }}_pk">
<input type="text"
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}_name" autocomplete="off"
{% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}>
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
</ul>

305
tfjm/templates/base.html Normal file
View File

@ -0,0 +1,305 @@
{% load static i18n static calendar %}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html lang="{{ LANGUAGE_CODE|default:"en" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %} class="position-relative h-100">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>
{% block title %}{{ title }}{% endblock title %} - Plateforme du TFJM²
</title>
<meta name="description" content="Plateform d'inscription au TFJM².">
{# Favicon #}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<meta name="theme-color" content="#ffffff">
{% if no_cache %}
<meta name="turbolinks-cache-control" content="no-cache">
{% endif %}
{# Bootstrap CSS #}
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css">
{# JQuery, Bootstrap and Turbolinks JavaScript #}
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
crossorigin="anonymous"></script>
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
{% if form.media %}
{{ form.media }}
{% endif %}
{% block extracss %}{% endblock %}
</head>
<body class="d-flex w-100 h-100 flex-column">
<main class="mb-auto">
<nav class="navbar navbar-expand-md navbar-light bg-light fixed-navbar shadow-sm">
<a class="navbar-brand" href="https://tfjm.org/">
<img src="{% static "logo.svg" %}" style="width: 42px;" alt="Logo TFJM²" id="navbar-logo">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbarNavDropdown" class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item active">
<a href="{% url "index" %}" class="nav-link"><i class="fas fa-home"></i> {% trans "Home" %}</a>
</li>
<li class="nav-item active">
{% if user.registration.is_admin %}
<a href="{% url "participation:calendar" %}" class="nav-link"><i class="fas fa-calendar"></i> {% trans "Calendar" %}</a>
{% else %}
<a href="#" class="nav-link" data-toggle="modal" data-target="#calendarModal"><i class="fas fa-calendar"></i> {% trans "Calendar" %}</a>
{% endif %}
</li>
{% if user.is_authenticated and user.registration.is_admin %}
<li class="nav-item active">
<a href="{% url "registration:user_list" %}" class="nav-link"><i class="fas fa-user"></i> {% trans "Users" %}</a>
</li>
<li class="nav-item active">
<a href="#" class="nav-link" data-toggle="modal" data-target="#teamsModal"><i class="fas fa-users"></i> {% trans "Teams" %}</a>
</li>
{% elif user.is_authenticated and user.registration.participates %}
{% if not user.registration.team %}
<li class="nav-item active">
<a href="#" class="nav-link" data-toggle="modal" data-target="#createTeamModal">
<i class="fas fa-users"></i> {% trans "Create team" %}
</a>
</li>
<li class="nav-item active">
<a href="#" class="nav-link" data-toggle="modal" data-target="#joinTeamModal">
<i class="fas fa-users"></i> {% trans "Join team" %}
</a>
</li>
{% else %}
<li class="nav-item active">
<a href="{% url "participation:my_team_detail" %}" class="nav-link">
<i class="fas fa-users"></i> {% trans "My team" %}
</a>
</li>
<li class="nav-item active">
<a href="{% url "participation:my_participation_detail" %}" class="nav-link">
<i class="fas fa-video"></i> {% trans "My participation" %}
</a>
</li>
{% endif %}
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="{% url "participation:chat" %}">
<i class="fas fa-comments"></i> {% trans "Chat" %}</a>
</li>
{% if user.admin %}
<li class="nav-item active">
<a data-turbolinks="false" class="nav-link" href="{% url "admin:index" %}"><i class="fas fa-cog"></i> {% trans "Administration" %}</a>
</li>
{% endif %}
</ul>
<ul class="navbar-nav ml-auto">
{% if user.registration.is_admin %}
<form class="navbar-form" role="search" onsubmit="event.preventDefault()">
<div class="input-group">
<input type="text" class="form-control" placeholder="{% trans "Search..." %}" name="q" id="search-term" value="{{ request.GET.q }}">
<div class="input-group-btn">
<button class="btn btn-default" data-toggle="modal" data-target="#searchModal"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
{% endif %}
{% if "_fake_user_id" in request.session %}
<li class="nav-item active">
<a class="nav-link" href="{% url "registration:reset_admin" %}?path={{ request.path }}"><i class="fas fa-tools"></i> {% trans "Return to admin view" %}</a>
</li>
{% endif %}
{% if not user.is_authenticated %}
{% if 1|current_phase %}
<li class="nav-item active">
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a>
</li>
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="#" data-toggle="modal" data-target="#loginModal">
<i class="fas fa-sign-in-alt"></i> {% trans "Log in" %}
</a>
</li>
{% else %}
<li class="dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-user"></i> {{ user.first_name }} {{ user.last_name }}
</a>
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="{% url "registration:my_account_detail" %}">
<i class="fas fa-user"></i> {% trans "My account" %}
</a>
<a class="dropdown-item" href="{% url "logout" %}">
<i class="fas fa-sign-out-alt"></i> {% trans "Log out" %}
</a>
</div>
</li>
{% endif %}
</ul>
</div>
</nav>
{% block fullcontent %}
<div class="{% block containertype %}container{% endblock %} my-3">
{% block contenttitle %}{% endblock %}
{% if user.is_authenticated and not user.registration.email_confirmed %}
<div class="alert alert-warning alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
{% url "registration:email_validation_resend" pk=user.pk as send_email_url %}
{% blocktrans trimmed %}
Your email address is not validated. Please click on the link you received by email.
You can resend a mail by clicking on <a href="{{ send_email_url }}">this link</a>.
{% endblocktrans %}
</div>
{% endif %}
<div id="messages"></div>
<div id="content">
{% block content %}
<p>Default content...</p>
{% endblock content %}
</div>
</div>
{% endblock %}
</main>
<footer class="bg-light text-primary mt-auto py-2">
<div class="container-fluid">
<div class="row">
<div class="col-10">
<form action="{% url 'set_language' %}" method="post"
class="form-inline">
<span class="text-muted mr-1">
<a target="_blank" href="mailto:&#99;&#111;&#110;&#116;&#97;&#99;&#116;&#64;&#99;&#111;&#114;&#114;&#101;&#115;&#112;&#111;&#110;&#100;&#97;&#110;&#99;&#101;&#115;&#45;&#109;&#97;&#116;&#104;&#115;&#46;&#102;&#114;"
class="text-muted"><i class="fas fa-envelope"></i> {% trans "Contact us" %}</a>
</span>
{% csrf_token %}
<select title="language" name="language"
class="form-control form-control-sm language"
onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% for lang_code, lang_name in LANGUAGES %}
<option value="{{ lang_code }}"
{% if lang_code == LANGUAGE_CODE %}
selected{% endif %}>
{{ lang_name }} ({{ lang_code }})
</option>
{% endfor %}
</select> &nbsp;
<noscript>
<input type="submit">
</noscript> &nbsp;
<a target="_blank" class="text-muted" href="{% url "about" %}">{% trans "About" %}</a> &nbsp; &mdash; &nbsp;
<a target="_blank" class="text-muted"
href="https://gitlab.com/animath/si/plateforme-tfjm">
<i class="fab fa-gitlab"></i>
</a>
</form>
</div>
<div class="col text-right">
<a href="#" data-turbolinks="false" class="text-muted">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</footer>
{% trans "Calendar" as modal_title %}
{% include "base_modal.html" with modal_id="calendar" modal_additional_class="modal-lg" %}
{% if user.is_authenticated %}
{% trans "All teams" as modal_title %}
{% include "base_modal.html" with modal_id="teams" modal_additional_class="modal-lg" %}
{% trans "Search results" as modal_title %}
{% include "base_modal.html" with modal_id="search" modal_form_method="get" modal_additional_class="modal-lg" %}
{% trans "Join team" as modal_title %}
{% trans "Join" as modal_button %}
{% url "participation:join_team" as modal_action %}
{% include "base_modal.html" with modal_id="joinTeam" %}
{% trans "Create team" as modal_title %}
{% trans "Create" as modal_button %}
{% url "participation:create_team" as modal_action %}
{% include "base_modal.html" with modal_id="createTeam" modal_button_type="success" %}
{% else %}
{% trans "Log in" as modal_title %}
{% trans "Log in" as modal_button %}
{% url "login" as modal_action %}
{% include "base_modal.html" with modal_id="login" %}
{% endif %}
<script>
CSRF_TOKEN = "{{ csrf_token }}";
$(".invalid-feedback").addClass("d-block");
$(document).ready(function () {
$('a[data-target="#calendarModal"]').click(function() {
let modalBody = $("#calendarModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "participation:calendar" %} #form-content")
});
{% if user.is_authenticated and user.registration.is_admin %}
$('a[data-target="#teamsModal"]').click(function() {
let modalBody = $("#teamsModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "participation:team_list" %} #form-content")
});
$('button[data-target="#searchModal"]').click(function() {
let modalBody = $("#searchModal div.modal-body");
let q = encodeURI($("#search-term").val());
modalBody.load("{% url "haystack_search" %}?q=" + q + " #search-results");
});
{% endif %}
{% if not user.is_authenticated %}
$('a[data-target="#loginModal"]').click(function() {
let modalBody = $("#loginModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "login" %} #form-content")
});
{% endif %}
{% if user.is_authenticated and user.registration.participates and not user.registration.team %}
$('a[data-target="#createTeamModal"]').click(function() {
let modalBody = $("#createTeamModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "participation:create_team" %} #form-content");
});
$('a[data-target="#joinTeamModal"]').click(function() {
let modalBody = $("#joinTeamModal div.modal-body");
if (!modalBody.html().trim())
modalBody.load("{% url "participation:join_team" %} #form-content");
});
{% endif %}
});
</script>
{% block extrajavascript %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,24 @@
{% load i18n %}
<div id="{{ modal_id }}Modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog {{ modal_additional_class }}" role="document">
<form id="{{ modal_id }}-form" method="{{ modal_form_method|default:"post" }}" action="{{ modal_action }}" enctype="{{ modal_enctype|default:"application/x-www-form-urlencoded" }}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ modal_title }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">{{ modal_content }}</div>
<div class="modal-footer">
{{ extra_modal_button }}
{% if modal_button %}
<button type="submit" class="btn btn-{{ modal_button_type|default:"primary" }}">{{ modal_button }}</button>
{% endif %}
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</form>
</div>
</div>

80
tfjm/templates/index.html Normal file
View File

@ -0,0 +1,80 @@
{% extends "base.html" %}
{% block content %}
<div>
<div class="jumbotron bg-white">
<div class="row">
<h1 class="display-3">
Bienvenue sur le site d'inscription au <a href="https://tfjm.org/" target="_blank">
TFJM²</a> !
</h1>
</div>
</div>
<div class="row jumbotron bg-white">
<div class="col-sm">
<h3>
Tu souhaites participer au TFJM² ?
<br/>
Ton équipe est déjà formée ?
</h3>
</div>
<div class="col-sm text-right">
<div class="btn-group-vertical">
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">Inscris-toi maintenant !</a>
<a class="btn btn-light btn-lg" href="{% url "login" %}" role="button">J'ai déjà un compte</a>
</div>
</div>
</div>
<div class="alert alert-danger">
Avertissement : certains rencontrent des difficultés à recevoir les mails automatiques. Merci de vérifier
régulièrement votre boîte spam, et à nous contacter en cas de problème.
</div>
<div class="jumbotron">
<h5 class="display-4">Comment ça marche ?</h5>
<p>
Pour participer au TFJM², il suffit de créer un compte sur la rubrique <strong><a href="{% url "registration:signup" %}">Inscription</a></strong>.
Vous devrez ensuite confirmer votre adresse e-mail.
</p>
<p class="text-justify">
Vous pouvez accéder à votre compte via la rubrique <strong><a href="{% url "login" %}">Connexion</a></strong>.
Une fois connecté, vous pourrez créer une équipe ou en rejoindre une déjà créée par l'un de vos camarades
via un code d'accès qui vous aura été transmis. Vous serez ensuite invité à soumettre une autorisation de droit à l'image,
indispensable au bon déroulement du TFJM². Une fois que votre équipe comporte au moins 3 participants (maximum 5)
et un encadrant, vous pourrez demander à valider votre équipe pour être apte à travailler sur le problème de votre choix.
</p>
<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 TFJM², 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 échanger durant la troisième phase du TFJM², et
également de poser vos questions en cas de besoin.
</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 participants. C'est un petit coin auprès du feu, un parc à
jeu où vous pouvez descendre en toboggan (en le désinfectant après utilisation), ne cherchez pas à être trop formel.
</p>
<h2>J'ai une question</h2>
<p class="text-justify">
Pour toute question, vous pouvez soit la poser dans <code>#faq</code> dans l'onglet chat comme indiqué ci-dessus, soit nous
contacter par mail à l'adresse <a href="mailto:contact@tfjm.org">contact@tfjm.org</a>.
</p>
<div class="alert alert-warning">
<strong>Attention aux dates !</strong> Si vous ne finalisez pas votre inscription dans le délai indiqué, vous
ne pourrez malheureusement pas participer au TFJM².
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
<p><a href="{% url 'index' %}">{% trans 'Log in again' %}</a></p>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-2.0-or-later
{% endcomment %}
{% load i18n crispy_forms_filters %}
{% block title %}{% trans "Log in" %}{% endblock %}
{% block contenttitle %}<h1>{% trans "Log in" %}</h1>{% endblock %}
{% block content %}
{% if user.is_authenticated %}
<p class="errornote">
{% blocktrans trimmed %}
You are authenticated as {{ user }}, but are not authorized to
access this page. Would you like to login to a different account?
{% endblocktrans %}
</p>
{% endif %}
<form method="post" id="login-form">
<div id="form-content">
{% csrf_token %}
{{ form | crispy }}
<a href="{% url 'password_reset' %}" class="badge badge-light">{% trans 'Forgotten your password or username?' %}</a>
</div>
<input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary">
</form>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% load crispy_forms_filters highlight i18n search_results_tables django_tables2 %}
{% block content %}
<h2>{% trans "Search" %}</h2>
<form>
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans "Search" %}</button>
</form>
<hr>
<h3>{% trans "Results" %}</h3>
<div id="search-results">
{% regroup object_list by model_name as categories %}
{% for category in categories %}
<h4>{% trans category.grouper|capfirst %}</h4>
{% with table=category.list|search_table %}
{% render_table table %}
{% endwith %}
{% empty %}
<p>{% trans "No results found." %}</p>
{% endfor %}
</div>
{% endblock %}

27
tfjm/tests.py Normal file
View File

@ -0,0 +1,27 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from django.core.handlers.asgi import ASGIHandler
from django.core.handlers.wsgi import WSGIHandler
from django.test import TestCase
class TestLoadModules(TestCase):
"""
Load modules that are not used in development mode in order to increase coverage.
"""
def test_asgi(self):
from tfjm import asgi
self.assertTrue(isinstance(asgi.application, ASGIHandler))
def test_wsgi(self):
from tfjm import wsgi
self.assertTrue(isinstance(wsgi.application, WSGIHandler))
def test_load_production_settings(self):
os.putenv("TFJM_STAGE", "prod")
os.putenv("DJANGO_DB_TYPE", "postgres")
from tfjm import settings_prod
self.assertFalse(settings_prod.DEBUG)

30
tfjm/tokens.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.tokens import PasswordResetTokenGenerator
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
"""
Create a unique token generator to confirm email addresses.
"""
def _make_hash_value(self, user, timestamp):
"""
Hash the user's primary key and some user state that's sure to change
after an account validation to produce a token that invalidated when
it's used:
1. The user.profile.email_confirmed field will change upon an account
validation.
2. The last_login field will usually be updated very shortly after
an account validation.
Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
invalidates the token.
"""
# Truncate microseconds so that tokens are consistent even if the
# database doesn't support microseconds.
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
return str(user.pk) + str(user.email) + str(user.registration.email_confirmed)\
+ str(login_timestamp) + str(timestamp)
email_validation_token = AccountActivationTokenGenerator()

54
tfjm/urls.py Normal file
View File

@ -0,0 +1,54 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
"""tfjm URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
from django.views.generic import TemplateView
from registration.views import PhotoAuthorizationView
from .views import AdminSearchView
urlpatterns = [
path('', TemplateView.as_view(template_name="index.html"), name='index'),
path('about/', TemplateView.as_view(template_name="about.html"), name='about'),
path('i18n/', include('django.conf.urls.i18n')),
path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls, name="admin"),
path('accounts/', include('django.contrib.auth.urls')),
path('search/', AdminSearchView.as_view(), name="haystack_search"),
path('api/', include('api.urls')),
path('participation/', include('participation.urls')),
path('registration/', include('registration.urls')),
path('media/authorization/photo/<str:filename>/', PhotoAuthorizationView.as_view(), name='photo_authorization'),
path('', include('eastereggs.urls')),
]
if 'cas_server' in settings.INSTALLED_APPS: # pragma: no cover
urlpatterns += [
path('cas/', include('cas_server.urls', namespace="cas_server")),
]
handler400 = bad_request
handler403 = permission_denied
handler404 = page_not_found
handler500 = server_error

19
tfjm/views.py Normal file
View File

@ -0,0 +1,19 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from haystack.generic_views import SearchView
class AdminMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if not request.user.registration.is_admin:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class AdminSearchView(AdminMixin, SearchView):
pass

Binary file not shown.

View File

Binary file not shown.

19
tfjm/wsgi.py Normal file
View File

@ -0,0 +1,19 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
"""
WSGI config for tfjm project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
application = get_wsgi_application()