1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-12-24 00:12:23 +00:00

Merge branch 'beta' into 'master'

Bugs mineurs, documentation

See merge request bde/nk20!162
This commit is contained in:
ourspalois 2021-04-23 19:32:54 +00:00
commit bab394908d
45 changed files with 1989 additions and 52 deletions

View File

@ -10,7 +10,6 @@ DJANGO_SECRET_KEY=CHANGE_ME
DJANGO_SETTINGS_MODULE=note_kfet.settings DJANGO_SETTINGS_MODULE=note_kfet.settings
CONTACT_EMAIL=tresorerie.bde@localhost CONTACT_EMAIL=tresorerie.bde@localhost
NOTE_URL=localhost NOTE_URL=localhost
DOMAIN=localhost
# Config for mails. Only used in production # Config for mails. Only used in production
NOTE_MAIL=notekfet@localhost NOTE_MAIL=notekfet@localhost

View File

@ -279,7 +279,8 @@ Le cahier des charges initial est disponible [sur le Wiki Crans](https://wiki.cr
La documentation des classes et fonctions est directement dans le code et est explorable à partir de la partie documentation de l'interface d'administration de Django. La documentation des classes et fonctions est directement dans le code et est explorable à partir de la partie documentation de l'interface d'administration de Django.
**Commentez votre code !** **Commentez votre code !**
La documentation plus haut niveau sur le développement est disponible sur [le Wiki associé au dépôt Git](https://gitlab.crans.org/bde/nk20/-/wikis/home). La documentation plus haut niveau sur le développement et sur l'utilisation
est disponible sur <https://note.crans.org/doc> et également dans le dossier `docs`.
## FAQ ## FAQ

View File

@ -4,10 +4,14 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework.serializers import ModelSerializer from django.utils import timezone
from rest_framework import serializers
from member.api.serializers import ProfileSerializer, MembershipSerializer
from note.api.serializers import NoteSerializer
from note.models import Alias
class UserSerializer(ModelSerializer): class UserSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Users. REST API Serializer for Users.
The djangorestframework plugin will analyse the model `User` and parse all fields in the API. The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
@ -22,7 +26,7 @@ class UserSerializer(ModelSerializer):
) )
class ContentTypeSerializer(ModelSerializer): class ContentTypeSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Users. REST API Serializer for Users.
The djangorestframework plugin will analyse the model `User` and parse all fields in the API. The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
@ -31,3 +35,42 @@ class ContentTypeSerializer(ModelSerializer):
class Meta: class Meta:
model = ContentType model = ContentType
fields = '__all__' fields = '__all__'
class OAuthSerializer(serializers.ModelSerializer):
"""
Informations that are transmitted by OAuth.
For now, this includes user, profile and valid memberships.
This should be better managed later.
"""
normalized_name = serializers.SerializerMethodField()
profile = ProfileSerializer()
note = NoteSerializer()
memberships = serializers.SerializerMethodField()
def get_normalized_name(self, obj):
return Alias.normalize(obj.username)
def get_memberships(self, obj):
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now()))
class Meta:
model = User
fields = (
'id',
'username',
'normalized_name',
'first_name',
'last_name',
'email',
'is_superuser',
'is_active',
'is_staff',
'profile',
'note',
'memberships',
)

View File

@ -3,6 +3,7 @@
import json import json
from datetime import datetime, date from datetime import datetime, date
from decimal import Decimal
from urllib.parse import quote_plus from urllib.parse import quote_plus
from warnings import warn from warnings import warn
@ -152,6 +153,8 @@ class TestAPI(TestCase):
value = value.isoformat() value = value.isoformat()
elif isinstance(value, ImageFieldFile): elif isinstance(value, ImageFieldFile):
value = value.name value = value.name
elif isinstance(value, Decimal):
value = str(value)
query = json.dumps({field.name: value}) query = json.dumps({field.name: value})
# Create sample permission # Create sample permission

View File

@ -5,6 +5,7 @@ from django.conf import settings
from django.conf.urls import url, include from django.conf.urls import url, include
from rest_framework import routers from rest_framework import routers
from .views import UserInformationView
from .viewsets import ContentTypeViewSet, UserViewSet from .viewsets import ContentTypeViewSet, UserViewSet
# Routers provide an easy way of automatically determining the URL conf. # Routers provide an easy way of automatically determining the URL conf.
@ -47,5 +48,6 @@ app_name = 'api'
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
urlpatterns = [ urlpatterns = [
url('^', include(router.urls)), url('^', include(router.urls)),
url('^me/', UserInformationView.as_view()),
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

20
apps/api/views.py Normal file
View File

@ -0,0 +1,20 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.models import User
from rest_framework.generics import RetrieveAPIView
from .serializers import OAuthSerializer
class UserInformationView(RetrieveAPIView):
"""
These fields are give to OAuth authenticators.
"""
serializer_class = OAuthSerializer
def get_queryset(self):
return User.objects.filter(pk=self.request.user.pk)
def get_object(self):
return self.request.user

17
apps/member/auth.py Normal file
View File

@ -0,0 +1,17 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from cas_server.auth import DjangoAuthUser # pragma: no cover
from note.models import Alias
class CustomAuthUser(DjangoAuthUser): # pragma: no cover
"""
Override Django Auth User model to define a custom Matrix username.
"""
def attributs(self):
d = super().attributs()
if self.user:
d["normalized_name"] = Alias.normalize(self.user.username)
return d

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.19 on 2021-03-13 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0006_create_note_account_bde_membership'),
]
operations = [
migrations.AlterField(
model_name='membership',
name='roles',
field=models.ManyToManyField(related_name='memberships', to='permission.Role', verbose_name='roles'),
),
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2021, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.19 on 2021-03-13 11:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('note', '0004_remove_null_tag_on_charfields'),
]
operations = [
migrations.AlterField(
model_name='alias',
name='note',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.Note'),
),
]

View File

@ -134,8 +134,6 @@ class PermissionBackend(ModelBackend):
return False return False
sess = get_current_session() sess = get_current_session()
if sess is not None and sess.session_key is None:
return False
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42: if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
return True return True

View File

@ -3511,6 +3511,7 @@
56, 56,
57, 57,
58, 58,
135,
137, 137,
143, 143,
147, 147,
@ -3521,8 +3522,7 @@
176, 176,
177, 177,
180, 180,
181, 181
182
] ]
} }
}, },

@ -1 +1 @@
Subproject commit 8ec7d68a169c1072aec427925f3bf2fd54eab5a3 Subproject commit 0c7070aea177e12fae099488e2ea6f8146b97e4d

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2.19 on 2021-03-21 09:34
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('treasury', '0002_invoice_remove_png_extension'),
]
operations = [
migrations.AlterField(
model_name='product',
name='quantity',
field=models.DecimalField(decimal_places=2, max_digits=7, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='specialtransactionproxy',
name='remittance',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_proxies', to='treasury.Remittance', verbose_name='Remittance'),
),
]

View File

@ -5,6 +5,7 @@ from datetime import date
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -131,12 +132,15 @@ class Product(models.Model):
verbose_name=_("Designation"), verbose_name=_("Designation"),
) )
quantity = models.PositiveIntegerField( quantity = models.DecimalField(
verbose_name=_("Quantity") decimal_places=2,
max_digits=7,
verbose_name=_("Quantity"),
validators=[MinValueValidator(0)],
) )
amount = models.IntegerField( amount = models.IntegerField(
verbose_name=_("Unit price") verbose_name=_("Unit price"),
) )
@property @property

View File

@ -1,4 +1,5 @@
{% load escape_tex %} {% load escape_tex i18n %}
{% language fr %}
\documentclass[a4paper,11pt]{article} \documentclass[a4paper,11pt]{article}
\usepackage{fontspec} \usepackage{fontspec}
@ -176,3 +177,4 @@ TVA non applicable, article 293 B du CGI.
\end{center} \end{center}
\end{document} \end{document}
{% endlanguage %}

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.19 on 2021-03-13 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='weiclub',
name='year',
field=models.PositiveIntegerField(default=2021, unique=True, verbose_name='year'),
),
migrations.AlterField(
model_name='weiregistration',
name='information_json',
field=models.TextField(default='{}', help_text='Information about the registration (buses for old members, survey for the new members), encoded in JSON', verbose_name='registration information'),
),
]

BIN
docs/_static/img/create_transaction.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -114,7 +114,7 @@ Les filtres disponibles sont indiqués sur chacune des pages de documentation.
Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés. Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés.
Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données. Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données.
Une requête à l'adresse ``/api/<model>/pk/`` affiche directement les informations du modèle demandé au format JSON. Une requête à l'adresse ``/api/<model>/<pk>/`` affiche directement les informations du modèle demandé au format JSON.
POST POST
~~~~ ~~~~

View File

@ -74,7 +74,7 @@ ne dépende pas de cette application, on procède de cette manière.
Graphe Graphe
~~~~~~ ~~~~~~
.. image:: /_static/img/graphs/activity.svg .. image:: ../_static/img/graphs/activity.svg
:alt: Graphe de l'application activités :alt: Graphe de l'application activités
UI UI

View File

@ -46,7 +46,7 @@ Applications indispensables
Applications packagées Applications packagées
---------------------- ----------------------
* ``polymorphic`` * ``polymorphic``
Utiliser pour la création de models polymorphiques (``Note`` et ``Transaction`` notamment) cf [Note](Note). Utiliser pour la création de models polymorphiques (``Note`` et ``Transaction`` notamment) cf `Note <note>`_.
L'utilisation des models polymorphiques est détaillé sur la documentation du package: L'utilisation des models polymorphiques est détaillé sur la documentation du package:
`<https://django-polymorphic.readthedocs.io/en/stable/>`_ `<https://django-polymorphic.readthedocs.io/en/stable/>`_

View File

@ -48,3 +48,10 @@ Exemple de Changelog, pour la création d'une transaction de 42424242 centimes d
S'il est préférable de passer en console Postgresql pour parcourir les logs, ils sont trouvables via l'API dans S'il est préférable de passer en console Postgresql pour parcourir les logs, ils sont trouvables via l'API dans
``/api/logs``, sous réserve d'avoir les droits suffisants (ie. être respo info). ``/api/logs``, sous réserve d'avoir les droits suffisants (ie. être respo info).
Graphe
~~~~~~
.. image:: ../_static/img/graphs/logs.svg
:alt: Logs graphe

View File

@ -93,7 +93,7 @@ génère en effet automatiquement une transaction de l'utilisateur vers le club
Graphe Graphe
------ ------
.. image:: /_static/img/graphs/member.svg .. image:: ../_static/img/graphs/member.svg
:alt: Graphe de l'application member :alt: Graphe de l'application member
Adhésions Adhésions
@ -109,15 +109,15 @@ de fin d'adhésion.
On peut ajouter une adhésion à un utilisateur dans un club à tout non adhérent de ce club. La personne en charge On peut ajouter une adhésion à un utilisateur dans un club à tout non adhérent de ce club. La personne en charge
d'adhérer quelqu'un choisit l'utilisateur, les rôles au sein du club et la date de début d'adhésion. Cette date de d'adhérer quelqu'un choisit l'utilisateur, les rôles au sein du club et la date de début d'adhésion. Cette date de
début d'adhésion doit se situer entre les champs ``club``.``membership_start`` et ``club``.``membership_end``, début d'adhésion doit se situer entre les champs ``club.membership_start`` et ``club.membership_end``,
si ces champs sont non nuls. Si ``club``.``parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club. si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club.
Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club``.``membership_fee_paid`` Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club.membership_fee_paid``
centimes pour les élèves et ``club``.``membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée
comme ce qui suit : comme ce qui suit :
* Si ``club``.``membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration`` * Si ``club.membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration``
* Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie) * Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie)
* Si ``club``.``membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club``.``membership_end``) * Si ``club.membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club.membership_end``)
Si l'utilisateur n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est Si l'utilisateur n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est
insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type
@ -127,13 +127,14 @@ Réadhésions
~~~~~~~~~~~ ~~~~~~~~~~~
Pour les clubs nécessitant des adhésions (de durée limitée), il est possible de réadhérer au bout d'un an. Dès lors Pour les clubs nécessitant des adhésions (de durée limitée), il est possible de réadhérer au bout d'un an. Dès lors
que le jour actuel est après ``club``.``membership_start`` + 1 an, ``club``.``membership_start`` et que le jour actuel est après ``club.membership_start`` + 1 an, ``club.membership_start`` et
``club``.membership_end`` sont incrémentés d'un an. ``club.membership_end`` sont incrémentés d'un an.
Il est possible de réadhérer si : Il est possible de réadhérer si :
* ``membership``.``date_start`` <= ``today`` <= ``membership``.``date_end`` (l'adhésion en cours est valide)
* ``membership``.``date_start`` < ``club``.``membership_start`` (si la date de début d'adhésion du club est postérieure à la date de début d'adhésion, qui a donc été mise à jour, on a changé d'année) * ``membership.date_start`` <= ``today`` <= ``membership.date_end`` (l'adhésion en cours est valide)
* Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership``.``date_start`` >= ``club``.``membership_start``) * ``membership.date_start`` < ``club.membership_start`` (si la date de début d'adhésion du club est postérieure à la date de début d'adhésion, qui a donc été mise à jour, on a changé d'année)
* Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership.date_start`` >= ``club.membership_start``)
Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées. Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées.
En réadhérant, une nouvelle adhésion est créée pour l'utilisateur avec les mêmes rôles, commençant le lendemain de la En réadhérant, une nouvelle adhésion est créée pour l'utilisateur avec les mêmes rôles, commençant le lendemain de la

View File

@ -9,7 +9,8 @@ Elle est disponible à l'adresse ``/note/consos/``, et l'onglet n'est visible qu
moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4. moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4.
Les boutons que l'utilisateur a le droit de voir sont triés par catégorie. Les boutons que l'utilisateur a le droit de voir sont triés par catégorie.
## Sélection des consommations Sélection des consommations
---------------------------
Lorsque l'utilisateur commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait, Lorsque l'utilisateur commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait,
récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur survole un alias, un appel à la page récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur survole un alias, un appel à la page

View File

@ -19,5 +19,6 @@ transferts/dons entre notes est détaillé sur la page `Transferts <transactions
Graphe Graphe
------ ------
.. image:: /_static/img/graphs/note.svg .. image:: ../../_static/img/graphs/note.svg
:width: 960
:alt: Graphe de l'application note :alt: Graphe de l'application note

View File

@ -19,8 +19,8 @@ Une permission est un Model Django dont les principaux attributs sont :
* ``model`` : Le model sur lequel cette permission va s'appliquer * ``model`` : Le model sur lequel cette permission va s'appliquer
* ``type`` : Les différents types d'interaction sont : voir (``view``), modifier (``change``), ajouter (``add``) * ``type`` : Les différents types d'interaction sont : voir (``view``), modifier (``change``), ajouter (``add``)
et supprimer (``delete``). et supprimer (``delete``).
* ``query`` : Requete sur la cible, encodé en JSON, traduit en un Q object (cf [Query](#compilation-de-la-query) * ``query`` : Requête sur la cible, encodé en JSON, traduit en un Q object (cf `Query <#compilation-de-la-query>`_)
* ``field`` : le champ cible qui pourra etre modifié. (tous les champs si vide) * ``field`` : le champ cible qui pourra être modifié. (tous les champs si vide)
Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre
de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprete comme tout ce qui suit de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprete comme tout ce qui suit
@ -147,5 +147,5 @@ modifiés en comparant l'ancienne et la nouvele instance.
Graphe des modèles Graphe des modèles
------------------ ------------------
.. image:: /_static/img/graphs/permission.svg .. image:: ../_static/img/graphs/permission.svg
:alt: Graphe de l'application permission :alt: Graphe de l'application permission

View File

@ -217,5 +217,6 @@ Exemple de validation de crédit Société générale d'un étudiant non payé "
Diagramme des modèles Diagramme des modèles
--------------------- ---------------------
.. image:: /_static/img/graphs/treasury.svg .. image:: ../_static/img/graphs/treasury.svg
:width: 960
:alt: Graphe de l'application trésorerie :alt: Graphe de l'application trésorerie

View File

@ -113,7 +113,8 @@ Graphe des modèles
Pour une meilleure compréhension, le graphe des modèles de l'application ``member`` ont été ajoutés au schéma. Pour une meilleure compréhension, le graphe des modèles de l'application ``member`` ont été ajoutés au schéma.
.. image:: /_static/img/graphs/wei.svg .. image:: ../_static/img/graphs/wei.svg
:width: 960
:alt: Graphe des modèles de l'application WEI :alt: Graphe des modèles de l'application WEI
Fonctionnement Fonctionnement

View File

@ -18,7 +18,7 @@
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'Note Kfet 2020' project = 'Note Kfet 2020'
copyright = '2020, BDE ENS Paris-Saclay' copyright = '2020-2021, BDE ENS Paris-Saclay'
author = 'BDE ENS Paris-Saclay' author = 'BDE ENS Paris-Saclay'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags

37
docs/documentation.rst Normal file
View File

@ -0,0 +1,37 @@
Documentation
=============
La documentation est gérée grâce à Sphinx. Le thème est le thème officiel de
ReadTheDocs ``sphinx-rtd-theme``.
Générer localement la documentation
-----------------------------------
On commence par se rendre au bon endroit et installer les bonnes dépendances :
.. code:: bash
cd docs
pip install -r requirements.txt
La documentation se génère à partir d'appels à ``make``, selon le type de
documentation voulue.
Par exemple, ``make dirhtml`` construit la documentation web,
``make latexpdf`` construit un livre PDF avec cette documentation.
Documentation automatique
-------------------------
Ansible compile et déploie automatiquement la documentation du projet, dans
le rôle ``8-docs``. Le rôle installe dans le bon environnement les dépendances
nécessaires, puis appelle sphinx pour placer la documentation compilée dans
``/var/www/documentation`` :
.. code:: bash
/var/www/note_kfet/env/bin/sphinx-build -b dirhtml /var/www/note_kfet/docs/ /var/www/documentation/
Ce dossier est exposé par ``nginx`` sur le chemin
`/doc <https://note.crans.org/doc>`_.

View File

@ -0,0 +1,80 @@
Service d'Authentification Centralisé (CAS)
===========================================
Un `CAS <https://fr.wikipedia.org/wiki/Central_Authentication_Service>`_ est
déployé sur la Note Kfet. Il est accessible à l'adresse `<https://note.crans.org/cas/>`_.
Il a pour but uniquement d'authentifier les utilisateurs via la note et ne communique
que peu d'informations.
Configuration
-------------
Le serveur CAS utilisé est implémenté grâce au paquet ``django-cas-server``. Il peut être
installé soit par PIP soit sur une machine Debian via
``apt install python3-django-cas-server``.
On ajoute ensuite ``cas_server`` aux applications Django installées. On n'oublie pas ni
d'appliquer les migrations (``./manage.py migrate``) ni de collecter les fichiers
statiques (``./manage.py collectstatic``).
On enregistre les routes dans ``note_kfet/urls.py`` :
.. code:: python
urlpatterns.append(
path('cas/', include('cas_server.urls', namespace='cas_server'))
)
Le CAS est désormais déjà prêt à être utilisé. Toutefois, puisque l'on utilise un site
Django-admin personnalisé, on n'oublie pas d'enregistrer les pages d'administration :
.. code:: python
if "cas_server" in settings.INSTALLED_APPS:
from cas_server.admin import *
from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
Enfin, on souhaite pouvoir fournir au besoin le pseudo normalisé. Pour cela, on crée une
classe dans ``member.auth`` :
.. code:: python
class CustomAuthUser(DjangoAuthUser):
def attributs(self):
d = super().attributs()
if self.user:
d["normalized_name"] = Alias.normalize(self.user.username)
return d
Puis on source ce fichier dans les paramètres :
.. code:: python
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
Utilisation
-----------
Le service est accessible sur `<https://note.crans.org/cas/>`_. C'est ce lien qu'il faut
donner à votre application.
L'application doit néanmoins être autorisée à accéder au CAS. Pour cela, rendez-vous
dans Django-admin (`<https://note.crans.org/cas/admin/>`_), dans
``Service Central d'Authentification/Motifs de services``, ajoutez une nouvelle entrée.
Choisissez votre position favorite puis le nom de l'application.
Les champs importants sont les deux suivants :
* **Motif :** il s'agit d'une expression régulière qui doit reconnaitre le site voulu.
Par exemple, pour autoriser Belenios (`<https://belenios.crans.org>`_), on rentrera
le motif ``^https?://belenios\.crans\.org/.*$``.
* **Champ d'utilisateur :** C'est le pseudo que renverra le CAS. Par défaut, il s'agira
du nom de note principal, mais il arrive parfois que certains sites supportent mal
d'avoir des caractères UTF-8 dans le pseudo. C'est par exemple le cas de Belenios.
On rentrera alors ``normalized_name`` dans ce champ, qui correspond à la version
normalisée (sans accent ni espace ni aucun caractère non-ASCII) du pseudo, et qui
suffit à identifier une personne.
On peut également utiliser le ``Single log out`` si besoin.

View File

@ -0,0 +1,28 @@
Applications externes
=====================
.. toctree::
:maxdepth: 2
:caption: Applications externes
cas
oauth2
.. warning::
L'utilisation de la note par des services externes est actuellement en beta. Il est
fort à parier que cette utilisation sera revue et améliorée à l'avenir.
Puisque la Note Kfet recense tous les comptes des adhérents BDE, les clubs ont alors
la possibilité de développer leurs propres applications et de les interfacer avec la
note. De cette façon, chaque application peut authentifier ses utilisateurs via la note,
et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts
via l'API.
Deux protocoles d'authentification sont implémentées :
* `CAS <cas>`_
* `OAuth2 <oauth2>`_
À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce
mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent
ces protocoles pour récupérer leurs adhérents.

View File

@ -0,0 +1,133 @@
OAuth2
======
L'authentification `OAuth2 <https://fr.wikipedia.org/wiki/OAuth>`_ est supportée par la
Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateurs, mais aussi
de transmettre des informations à un service tiers tels que des informations personnelles,
le solde de la note ou encore les adhésions de l'utilisateur, en l'avertissant sur
quelles données sont effectivement collectées.
.. danger::
L'implémentation actuelle ne permet pas de choisir quels droits on offre. Se connecter
par OAuth2 offre actuellement exactement les mêmes permissions que l'on n'aurait
normalement, avec le masque le plus haut, y compris en écriture.
Faites alors très attention lorsque vous vous connectez à un service tiers via OAuth2,
et contrôlez bien exactement ce que l'application fait de vos données, à savoir si
elle ignore bien tout ce dont elle n'a pas besoin.
À l'avenir, la fenêtre d'authentification pourra vous indiquer clairement quels
paramètres sont collectés.
Configuration du serveur
------------------------
On utilise ``django-oauth-toolkit``, qui peut être installé grâce à PIP ou bien via APT,
via le paquet ``python3-django-oauth-toolkit``.
On commence par ajouter ``oauth2_provider`` aux applications Django installées. On
n'oublie pas ni d'appliquer les migrations (``./manage.py migrate``) ni de collecter
les fichiers statiques (``./manage.py collectstatic``).
On souhaite que l'API gérée par ``django-rest-framework`` puisse être accessible via
l'authentification OAuth2. On adapte alors la configuration pour permettre cela :
.. code:: python
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
...
],
...
}
On ajoute les routes dans ``urls.py`` :
.. code:: python
urlpatterns.append(
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
)
L'OAuth2 est désormais prêt à être utilisé.
Configuration client
--------------------
Contrairement au `CAS <cas>`_, n'importe qui peut en théorie créer une application OAuth2.
En théorie, car pour l'instant les permissions ne leur permettent pas.
Pour créer une application, il faut se rendre à la page
`/o/applications/ <https://note.crans.org/o/applications/>`_. Dans ``client type``,
rentrez ``public`` (ou ``confidential`` selon vos choix), et vous rentrerez
généralement ``authorization-code`` dans ``Authorization Grant Type``.
Le champ ``Redirect Uris`` contient une liste d'adresses URL autorisées pour des
redirections post-connexion.
Il vous suffit de donner à votre application :
* L'identifiant client (client-ID)
* La clé secrète
* Les scopes : sous-ensemble de ``[read, write]`` (ignoré pour l'instant, cf premier paragraphe)
* L'URL d'autorisation : `<https://note.crans.org/o/authorize/>`_
* L'URL d'obtention de jeton : `<https://note.crans.org/o/token/>`_
* L'URL de récupération des informations de l'utilisateur : `<https://note.crans.org/api/me/>`_
N'hésitez pas à consulter la page `<https://note.crans.org/api/me/>`_ pour s'imprégner
du format renvoyé.
Avec Django-allauth
###################
Si vous utilisez Django-allauth pour votre propre application, vous pouvez utiliser
le module pré-configuré disponible ici :
`<https://gitlab.crans.org/bde/allauth-note-kfet>`_. Pour l'installer, vous
pouvez simplement faire :
.. code:: bash
$ pip3 install git+https://gitlab.crans.org/bde/allauth-note-kfet.git
L'installation du module se fera automatiquement.
Il vous suffit ensuite d'inclure l'application ``allauth_note_kfet`` à vos applications
installées (sur votre propre client), puis de bien ajouter l'application sociale :
.. code:: python
SOCIALACCOUNT_PROVIDERS = {
'notekfet': {
# 'DOMAIN': 'note.crans.org',
},
...
}
Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il
se connectera à ``note.crans.org`` si vous ne renseignez rien.
En créant l'application sur la note, vous pouvez renseigner
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
à adapter selon votre configuration.
Vous devrez ensuite enregistrer l'application sociale dans la base de données.
Vous pouvez passer par Django-admin, mais cela peut nécessiter d'avoir déjà un compte,
alors autant le faire via un shell python :
.. code:: python
from allauth.socialaccount.models import SocialApp
SocialApp.objects.create(
name="Note Kfet",
provider="notekfet",
client_id="VOTRECLIENTID",
secret="VOTRESECRET",
key="",
)
Si vous avez bien configuré ``django-allauth``, vous êtes désormais prêts par à vous
connecter via la note :) Par défaut, nom, prénom, pseudo et adresse e-mail sont
récupérés. Les autres données sont stockées mais inutilisées.

208
docs/faq.rst Normal file
View File

@ -0,0 +1,208 @@
Foire aux questions
===================
Des transactions anormales sont apparues sur mon compte.
--------------------------------------------------------
.. note::
Tu dois immédiatement contacter les trésoriers du BDE (voir ci-dessous) pour
signaler l'incident. Précise bien ton nom de note, l'heure de la transaction
ainsi que l'alias utilisé pour faire la transaction (en plaçant ta souris sur
ton pseudo sur la ligne de transaction, l'alias utilisé apparaît). La raison
la plus courante est que tu as un alias qui est trop proche d'un autre d'une
autre personne. Même si la Note Kfet 2020 essaie d'éviter ça, tu es invité⋅e
à supprimer l'alias problématique, ou tout du moins t'assurer que la confusion
ne puisse plus arriver.
Je souhaite consommer mais le solde de ma note est insuffisant
--------------------------------------------------------------
.. note::
Le BDE ne fait pas crédit à ses adhérents. Il est de ton devoir de t'assurer
d'avoir en permanence un solde positif sur ta note. Les permanenciers à la
Kfet ont la possibilité de refuser une consommation qui fait passer en négatif,
et ont obligation de refuser si la consommation est alcoolisée, en accord avec
la règlementation en vigueur.
Les trésoriers connaissent la liste des personnes en situation irrégulière et
n'hésiteront pas à faire des rappels pour recharger la note.
Comment recharger ma note ?
---------------------------
.. note::
Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre
de l'amicale des élèves de l'ENS Cachan, par carte bancaire via un terminal
de paiement électronique ou encore par virement bancaire, dont les coordonnées
sont à demander auprès des trésoriers BDE.
Les trois premières options sont à faire directement dans la Kfet.
Je pars en stage / en vacances. Puis-je bloquer ma note ?
---------------------------------------------------------
.. note::
Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton
dédié. Ta note ne sera plus affichée par les autres personnes et les transferts
seront impossibles, sauf pour les trésoriers BDE et respo info.
Il est toutefois de ton devoir de rembourser tout ce que tu dois.
Quelle est la limite maximale au nombre d'alias d'une note ?
------------------------------------------------------------
.. note::
Certains parlent d'une dizaine d'alias par note.
Sois conscient⋅e qu'ajouter des alias ne peut qu'augmenter la probabilité de
collisions avec une autre note, et peut aussi retarder la livraison de ta
commande lors d'un perm bouffe.
Je suis trésorier d'un club, qu'ai-je le droit de faire ?
---------------------------------------------------------
.. note::
Être trésorier d'un club donne la responsabilité de gérer la trésorerie du
club, et donc de gérer sa note. Vous obtenez donc le droit d'effectuer
n'importe quelle transaction via la note en provenance ou à destination de
la note de votre club. Vous pouvez également gérer les adhésions de votre club,
en permettant à n'importe quel adhérent BDE de rejoindre votre club, en prélevant
d'éventuels frais d'adhésion. Les paramètres du club peuvent être également modifiés.
.. danger::
Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser.
Chaque opération nécessitant des droits doit être fait pour une bonne raison,
et doit avoir un lien avec votre club. Vous n'avez par exemple pas le droit
d'aller récupérer des informations personnelles d'adhérents pour une raison
personnelle. En revanche, faire le lien entre nom/prénom et nom de note est
bien sûr permis pour faciliter des transferts. Tout abus de droits constaté
pourra mener à des sanctions prises par le bureau du BDE.
.. warning::
Une fonctionnalité pour permettre de gérer plus proprement les remboursements
entre amis est en cours de développement. Temporairement et pour des raisons
de confort, les trésoriers de clubs ont le droit de prélever n'importe quelle
adhérente vers n'importe quelle autre note adhérente, tant que la source ne
descend pas sous ``- 50 €``. Ces droits seront retirés d'ici quelques semaines.
Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions
---------------------------------------------------------------------------------------------------
.. note::
As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ?
Sinon, si tes droits sont tout récents, tu dois te déconnecter et te reconnecter
pour que tes droits soient bien pris en compte.
La Note permet de se connecter avec différents filtres de permission afin de
pouvoir prêter son ordinateur avec une session ouverte pour faire quelques
opérations en empêchant l'accès à des opérations trop sensibles.
Je suis trésorier d'un club. Puis-je créer un bouton ?
------------------------------------------------------
.. note::
Oui bien sûr ! Tant qu'il redirige bien vers la note de ton club.
Pour cela, rends-toi à la page `</note/buttons/>`_ pour afficher la liste des
boutons, puis tu auras accès à l'interface pour créer un bouton. Une fois le
bouton créé, il apparaîtra dans l'onglet ``Consommations``.
Il faut noter que tant qu'il n'y a pas de boutons visibles pour ton club, tu
n'auras pas accès à l'interface de consommations, et tu devras nécessairement
cliquer sur le lien ci-dessus pour accéder à l'interface d'édition des boutons.
Une fois qu'un bouton pour ton club est visible, l'interface consommations
devient accessible.
Après passation, je suis trésorier d'un club. Comment récupérer mes droits note ?
---------------------------------------------------------------------------------
.. note::
Tu dois pour cela contacter les trésoriers BDE (voir ci-dessous). Ils vous
expliqueront en détails vos droits et vos interdits et vous donneront les
droits requis.
Je souhaite contacter un trésorier
----------------------------------
.. note::
Pour contacter un trésorier, il te suffit d'envoyer un mail à l'adresse
`tresorerie.bde@lists.crans.org <tresorerie.bde@lists.crans.org>`_. Pense bien
à donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta
demande concerne un virement entre le compte du BDE et ton propre compte.
J'ai trouvé un bug, comment le signaler ?
-----------------------------------------
.. note::
La Note Kfet est développée bénévolement par des membres du BDE. Malgré tous nos
efforts pour fournir une plateforme sans erreur et la plus ergonomique possible.
Toutefois, il n'est évidemment pas exclu que des bugs soient présents :)
Pour nous soumettre un bug, tu peux envoyer un mail à
`notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_
Tu peux sinon ouvrir une issue sur `Gitlab <https://gitlab.crans.org/bde/nk20>`_.
N'hésite pas à venir en discuter avec nous à la Kfet, ou bien sur
`l'IRC du Crans <https://irc.crans.org/web>`_ sur le canal ``#note`` !
Le même processus s'applique en cas de demande de fonctionnalités.
Je souhaite contribuer
----------------------
.. note::
La Note Kfet est essentiellement développée par des responsables informatiques du
BDE de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez
bien sûr le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est
distribuée. Pour cela, si vous êtes adhérent Crans, vous pouvez proposer une
demande de fusion de votre code. Si ce n'est pas le cas, vous pouvez envoyer un
mail à `notekfet2020@lists.crans.org <notekfet2020@lists.crans.org>`_.
Dans les deux cas, merci de rejoindre le canal ``#note`` sur IRC :)
Contributeurs
-------------
.. note::
La version 2020 de la Note Kfet a été développée sous le mandat de la
Saper[list]popette. Son développement a commencé à l'été 2019. Après un mois de beta
à l'été 2020, son déploiement en production s'est fait le samedi 5 septembre 2020.
Elle succède à presque 6 années de la
`Note Kfet 2015 <https://wiki.crans.org/NoteKfet/NoteKfet2015>`_, alors en production
depuis le 6 octobre 2014.
Liste des contributeurs majeurs, par ordre alphabétique :
* Pierre-André « PAC » COMBY
* Yohann « ÿnérant » D'ANELLO
* Benjamin « esum » GRAILLOT
* Alexandre « erdnaxe » IOOSS
Hébergement
-----------
.. note::
En accord entre de l'ENS Paris-Saclay et le Crans, l'instance de production présente
sur `<https://note.crans.org/>`_ est hébergée sur l'un des serveurs du Crans.
Les données sont hébergées à l'adresse :
.. code::
Association Crans
ENS Paris-Saclay
4 Avenue des Sciences
91190 Gif-sur-Yvette

115
docs/getting_started.rst Normal file
View File

@ -0,0 +1,115 @@
La note, c'est quoi ?
=====================
La Note Kfet est un porte-monnaie virtuel proposé gratuitement à tous les adhérents BDE.
C'est le moyen de paiement privilégié au sein du campus, que ce soit pour payer des
activités du BDE, ou bien pour faire des remboursements entre amis. La note contient
également la base d'adhérents du BDE et contient de nombreux outils facilitant la vie
des différents bureaux de clubs, en particulier les trésoriers et surtout les trésoriers
BDE.
La Note Kfet est accessible à l'adresse `<https://note.crans.org>`_. La version actuelle
a été développée par le BDE 2020-2021, et est maintenue par les respos infos du BDE de
l'ENS Paris-Saclay.
Fonctionnement général de la note
---------------------------------
C'est quoi une note ?
~~~~~~~~~~~~~~~~~~~~~
Chaque adhérent⋅e BDE dispose d'un compte appelé « note », créé lors de son adhésion.
Une note est associée à un solde en euros, et à un pseudo appelé « nom de note ».
Le solde associé à la note correspond au solde de l'adhérent⋅e, avec lequel il est
possible de payer tout ce qui est en lien avec le BDE. Le nom de note suffit à
identifier une personne, et il suffit par exemple de donner ce nom à un permanencier
à la Kfet pour acheter un produit.
Faire une transaction
~~~~~~~~~~~~~~~~~~~~~
Tout⋅e adhérent⋅e peut faire une transaction de sa note vers n'importe quelle note
d'un⋅e autre adhérent⋅e, pourvu que le montant de la transaction n'excède pas son
propre solde. Pour cela, il suffit d'aller sur la page de transferts, qui est la
page par défaut après connexion : `<https://note.crans.org/note/transfer/>`_
Le formulaire pour effectuer un transfert apparaît alors. En cliquant sur le bouton
« Je suis l'émetteur », le champ « Émetteurs » est directement complété avec votre
propre note. Il vous suffit alors de remplir le champ « Destinataires » avec le ou
les noms de note que vous voulez créditer, de spécifier le montant et la raison de
votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes,
si votre solde est suffisant et que la transaction est acceptée, un message de
confirmation apparaîtra et la transaction apparaîtra dans l'historique ci-dessous.
.. image:: _static/img/create_transaction.png
:alt: Créer une transaction
Consulter ses données personnelles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
La Note Kfet sert non seulement à faire des transactions d'argent virtuel au sein
du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérents.
À cet effet, diverses informations personnelles sont collectées par la Note.
Pour accéder à votre compte, une fois connecté⋅e, rendez-vous dans le menu en haut
à droite où se trouve votre pseudo. Votre solde est par ailleurs affiché à côté
de votre pseudo.
L'interface est divisée en trois parties :
* Vos données personnelles dans la partie de gauche
* Vos adhésions actives (si existantes) dans la partie haute
* Vos transactions récentes dans la partie basse
Vous pouvez modifier vos données personnelles que la Note possède sur vous en
cliquant à tout moment sur le bouton « Modifier le profil ». Les informations
présentes sont :
* Nom
* Prénom
* Pseudo
* Adresse e-mail
* Numéro de téléphone
* Section
* Département
* Promotion
* Adresse
* Élève/étudiant
* Inscription aux listes de diffusion du BDE, du BDA et du BDS
Les trois premières informations sont obligatoires pour pouvoir vous contacter
et pouvoir tenir un registre d'adhérent.
Le numéro de téléphone et l'adresse ne sont utilisés uniquement en cas d'urgence,
notamment en cas de WEI, et ne sont pas des champs obligatoires. De plus,
promotion, département et section ne sont utilisés sérieusement que en cas
de WEI, et ne sont pas utilisés sinon.
La distinction élève/étudiant permet de distinguer les achats qui peuvent avoir
des prix différents pour les élèves et pour les étudiants, notamment en cas de
transferts d'argent, de WEI ou d'adhésion à certains clubs.
Hormis le pseudo, toutes ces informations ont un caractère confidentiel qui les
rend privées et inaccessible aux utilisateurs. Les trésoriers BDE et les respos
info ont accès à toutes les informations (en cas de besoin uniquement, le cas
contraire est considéré comme de l'abus de droit qui est punissable), et les
trésoriers de club ont accès au nom et au prénom afin de faciliter l'association
à un nom de note.
Dans la partie informations personnelles, il est également possible de modifier
la liste de ses alias (voir la section sur les alias), et de voir son propre
solde. Il est enfin possible de modifier l'image associée à la note, qui n'a
quant à elle pas de caractère privée puisqu'elle est affichée à chaque recherche
de nom de note.
Le tableau des adhésions actives sur la partie du haut contient l'ensemble des
clubs auxquels vous êtes adhérent⋅es, avec les dates de début, de fin, votre
rôle au sein du club (généralement simple membre, mais cela peut également
être trésorier⋅ère par exemple) ainsi que la cotisation que vous avez éventuellement
payée au club.
L'historique des transactions contient l'ensemble des transactions qui ont fait
intervenir votre note, triées par date décroissante. Il est possible en cliquant
sur le lien « Historique des transactions » d'avoir un meilleur aperçu, notamment
pour pouvoir filtrer des transactions.

View File

@ -2,8 +2,9 @@ Documentation de la Note Kfet 2020
================================== ==================================
Bienvenue sur le Wiki de la NoteKfet2020. Ce wiki est plutot orienté vers un public de développeur, qui souhaitent Bienvenue sur la documentation de la Note Kfet 2020. Cette documentation est à la fois
contribuer au développement, mais expliquent également aux plus curieux comment fonctionne la NoteKfet2020 sous le capot. destinée aux adhérents BDE pour découvrir le fonctionnement de la note, mais aussi aux
respos info qui souhaitent découvrir comment fonctionne la note sous le capot.
Des informations complémentaires sont également disponibles sur le `Wiki Crans <https://wiki.crans.org/NoteKfet/NoteKfet2020/>`_. Des informations complémentaires sont également disponibles sur le `Wiki Crans <https://wiki.crans.org/NoteKfet/NoteKfet2020/>`_.
@ -11,4 +12,11 @@ Des informations complémentaires sont également disponibles sur le `Wiki Crans
:maxdepth: 2 :maxdepth: 2
:caption: Développement de la NK20 :caption: Développement de la NK20
getting_started
apps/index apps/index
install-dev
install
documentation
scripts
faq
external_services/index

169
docs/install-dev.rst Normal file
View File

@ -0,0 +1,169 @@
Installer la Note Kfet sur sa propre machine
============================================
Jamais en production tu ne coderas.
Pour pouvoir développer sur la Note, il est donc essentiel de pouvoir installer
localement une instance de la note, avec une base de données vierge et indépendante
de celle utilisée en production.
Toutes les dépendances Python seront installées dans un environnement virtuel,
afin de ne pas polluer sa machine de dépendances de la note.
Dépendances de base
-------------------
On a néanmoins besoin de dépendances de base.
Sur un Ubuntu/Debian :
.. code:: bash
$ sudo apt update
$ sudo apt install --no-install-recommends -y \
python3-setuptools python3-venv python3-dev \
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
Pour Arch Linux :
.. code:: bash
$ sudo pacman -Sy python-setuptools python-virtualenv \
texlive-most gettext git
Bootstrap 4 n'est pas dans les paquets officiels de Arch Linux, mais peut-être
trouvé dans l'AUR grâce au paquet ``bootstrap``. Néanmoins, il faut pour l'instant
penser à créer un lien symbolique :
``sudo ln -s /usr/share/javascript/bootstrap /usr/share/javascript/bootstrap4``.
Néanmoins, font-awesome n'est pas disponible dans les paquets Arch Linux, mais non
essentiel en développement (n'ajoute que les symbole à côté des boutons).
À noter que bootstrap et texlive sont optionnels également selon vos besoins.
Téléchargement de la note
-------------------------
On récupère le dépôt Git de la note :
.. code:: bash
$ git clone git@gitlab.crans.org:bde/nk20.git
Clonage dans 'nk20'...
remote: Enumerating objects: 203, done.
remote: Counting objects: 100% (203/203), done.
remote: Compressing objects: 100% (125/125), done.
remote: Total 13438 (delta 98), reused 170 (delta 74), pack-reused 13235
Réception d\'objets: 100% (13438/13438), 9.39 Mio | 5.50 Mio/s, fait.
Résolution des deltas: 100% (9028/9028), fait.
$ cd nk20
.. note::
Pour apprendre à utiliser Git, on peut regarder la page wiki dédiée :
`<https://wiki.crans.org/WikiInformatique/HowToGit>`_,
ou bien regarder les séminaires Crans dédiés :
`<https://wiki.crans.org/CransTechnique/CransApprentis/SeminairesTechniques>`_
Création d'un environnement virtuel Python
------------------------------------------
Une fois le projet cloné, on peut créer un environnement virtuel qui contiendra
toutes les dépendances Python.
Pour cela, on peut simplement faire :
.. code:: bash
$ python3 -m venv env
$ source env/bin/activate
(env) $
À noter que ``source`` peut s'abbréger par ``.`` uniquement.
Vous êtes donc dans un environnement virtuel Python. Pour installer les dépendances
de la note :
.. code:: bash
(env) $ pip install -r requirements.txt
Les dépendances s'installeront ensuite dans le dossier ``env``.
Au besoin, l'environnement peut être quitté en tapant ``deactivate`` et rejoint en
resourçant ``env/bin/activate``.
Plus d'informations sur les environnements virtuels dans la documentation officielle
de Python : `<https://docs.python.org/fr/3/tutorial/venv.html>`_.
Lancement de la note
--------------------
La partie Python (qui peut s'appliquer au développement de n'importe quel projet
Python) est terminée, on peut commencer à s'occuper de Django.
Pour rappel, Django est un cadre de développement web open source en Python extrêment
puissant. Il a pour but de rendre le développement web simple et rapide.
La documentation officielle de Django, complète, excellente et même en français,
peut être trouvée ici : `<https://docs.djangoproject.com/fr/>`_.
Pour lancer un serveur de développement, on peut donc commencer par compiler les
traductions :
.. code:: bash
(env) $ ./manage.py compilemessages
On applique les migrations de la base de données (de test, qui sera créée) :
.. code:: bash
(env) $ ./manage.py migrate
On importe quelques données de base et utiles :
.. code:: bash
(env) $ ./manage.py loaddata initial
.. note::
Ces données sont stockées au format JSON dans les différents fichiers
``apps/{app}/fixtures/initial.json``.
Enfin, on peut lancer le serveur web de développement :
.. code:: bash
(env) $ ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 15, 2021 - 15:14:37
Django version 2.2.20, using settings 'note_kfet.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Ouvrez votre navigateur, tapez `<http://localhost:8000/>`_, enjoy :)
.. note::
En lançant le serveur web de la sorte, Django va recevoir un signal dès lors qu'un
fichier a été modifié. Vous n'avez donc pas besoin de redémarrer le serveur après
chaque modification, sauf erreurs.
Attention : ce serveur n'est destiné qu'à des fins de développement et n'est pas
optimisé pour recevoir des requêtes en parallèle ou être utilisé en production.
Créer un super-utilisateur
--------------------------
La commande ``./manage.py createsuperuser`` vous permettra de créer un super-utilisateur
initial.

640
docs/install.rst Normal file
View File

@ -0,0 +1,640 @@
Installer la Note Kfet en production
====================================
Cette page détaille comment installer la Note Kfet sur un serveur de production,
dédié uniquement à l'utilisation de la note. On supposera que le serveur tourne
avec un Debian Buster à jour.
Ajout des dépôts buster-backports
---------------------------------
Debian c'est bien, c'est stable, mais les paquets sont vite obsolètes.
En particulier, la version stable de Django dans la version stable de Debian est la
version 1.11, qui n'est plus maintenue par Django depuis déjà quelques mois.
Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye
utilisant la version 2.2.
Afin de permettre à ses utilisateurs d'utiliser les dernières fonctionnalités de
certains paquets, Debian a créé une distribution particulière, appelée
``buster-backports``. Cette distribution contient des paquets de la distribution
à venir « ``testing`` » (``bullseye`` à l'heure où cette documentation est écrite)
recompilés et réadapter pour fonctionner avec les paquets de la distribution stable
(``buster``). Ce qui nous intéresse est de pouvoir récupérer Django 2.2 depuis
cette distribution, et avoir donc une version maintenue de Django.
Plus de détails sur le wiki de Debian : `<https://wiki.debian.org/fr/Backports>`_.
Pour activer les backports, il suffit d'ajouter dans le fichier ``/etc/apt/sources.list`` :
.. code::
deb $MIRROR/debian buster-backports main contrib
``$MIRROR`` est votre miroir Debian favori, comme ``http://ftp.debian.org`` ou
``http://mirror.crans.org`` pour ce qui est de la version utilisée en production au BDE
sur les serveurs du Crans. Il suffit ensuite de faire un ``sudo apt update``.
Vérifiez que les paquets sont bien récupérés, en cherchant cette ligne :
.. code::
Get:4 http://mirror.crans.org/debian buster-backports InRelease [46.7 kB]
.. warning::
Avis aux futurs respos info : pensez à bien actualiser cette documentation lorsque
Debian Bullseye sera sorti. En particulier, il ne sera pas déconnant de continuer
à utiliser non pas buster-backports mais bullseye-backports pour installer la
note avec Django 3.2 et non Django 2.2.
Bien sûr, vous testerez sur un serveur qui n'est pas celui utilisé avant :)
Installation des dépendances nécessaires
----------------------------------------
On s'efforce pour récupérer le plus possible de dépendances via les paquets Debian
plutôt que via ``pip`` afin de faciliter les mises à jour et avoir une installation
plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-backports :
.. code:: bash
$ sudo apt update
$ sudo apt install -t buster-backports --no-install-recommends \
gettext git ipython3 \ # Dépendances basiques
fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
python3-djangorestframework python3-memcache python3-phonenumbers \
python3-pil python3-pip python3-psycopg2 python3-setuptools python3-venv \
texlive-xetex memcached
Ces paquets fournissent une bonne base sur laquelle travailler.
Pour les mettre à jour, il suffit de faire ``sudo apt update`` puis ``sudo apt upgrade``.
Téléchargement de la note
-------------------------
Tout comme en développement, on utilise directement le Gitlab du Crans pour récupérer
les sources.
On suppose que l'on veut cloner le projet dans le dossier ``/var/www/note_kfet``.
On clone donc le dépôt en tant que ``www-data`` :
.. code:: bash
$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git /var/www/note_kfet
Par défaut, le dépôt est configuré pour suivre la branche ``master``, qui est la branche
stable, notamment installée sur `<https://note.crans.org/>`_. Pour changer de branche,
notamment passer sur la branche ``beta`` sur un serveur de pré-production (un peu comme
`<https://note-dev.crans.org/>`_), on peut faire :
.. code:: bash
$ sudo -u www-data git checkout beta
.. warning::
Avis aux successeurs : notamment pour le serveur de production derrière
`<https://note.crans.org>`_, il peut être intéressant de créer un paquet Python
de sorte à pouvoir installer la note en faisant directement
``pip install git+https://gitlab.crans.org/bde/nk20.git``, et avoir ainsi une
installation plus propre en décourageant le développement en production.
Voir par exemple comment le futur site d'inscription du Crans, Constellation,
gère cela : `<https://gitlab.crans.org/nounous/constellation>`_.
Installation des dépendances Python non présentes dans les dépôts APT
---------------------------------------------------------------------
Même s'il est préférable d'installer les dépendances Python via les paquets APT,
tous ne sont malheureusement pas disponibles.
On doit donc récupérer les dépendances manquantes via pip.
Tout comme en développement, on préfère avoir un environnement virtuel dédié,
les ``sudo pip`` étant rarement compatibles avec les dépendances APT.
On construit donc un environnement virtuel et on installe les dépendances manquantes
dans cet environnement :
.. code:: bash
$ cd /var/www/note_kfet
$ python3 -m venv env
$ . env/bin/activate
(env) $ pip install -r requirements.txt
Normalement, seules les dépendances manquantes sont installées, les autres sont trouvées
globalement.
Plus d'informations sur les environnements virtuels dans la documentation officielle
de Python : `<https://docs.python.org/fr/3/tutorial/venv.html>`_.
Configuration de la note
------------------------
La configuration de la note se gère essentiellement via des paramètres d'environnement.
Ceux-ci sont lus via le fichier ``.env`` s'il existe, qui doit être placé à la racine
du projet cloné (donc dans ``/var/www/note_kfet/.env``). Un fichier d'exemple est situé
dans le fichier ``.env_example``, on peut donc faire un ``sudo cp .env_example .env``.
Attention aux permissions : le fichier doit être lu par ``www-data`` et écrit (rien
n'empêche de l'écrire en tant que root).
Le contenu de ce fichier :
.. code:: env
DJANGO_APP_STAGE=prod
DJANGO_DEV_STORE_METHOD=sqlite
DJANGO_DB_HOST=localhost
DJANGO_DB_NAME=note_db
DJANGO_DB_USER=note
DJANGO_DB_PASSWORD=CHANGE_ME
DJANGO_DB_PORT=
DJANGO_SECRET_KEY=CHANGE_ME
DJANGO_SETTINGS_MODULE=note_kfet.settings
CONTACT_EMAIL=tresorerie.bde@localhost
NOTE_URL=localhost
NOTE_MAIL=notekfet@localhost
EMAIL_HOST=smtp.localhost
EMAIL_PORT=25
EMAIL_USER=notekfet@localhost
EMAIL_PASSWORD=CHANGE_ME
WIKI_USER=NoteKfet2020
WIKI_PASSWORD=
Le paramètre ``DJANGO_APP_STAGE`` accepte comme valeur ``dev`` ou ``prod``.
En développement, les mails ne sont pas envoyés mais affichés dans les logs du
serveur. Les messages d'erreur sont directement affichés au lieu d'être envoyés
par mail. Les paramètres d'envoi de mail n'ont donc aucun effet. En développement,
il est également possible de choisir si l'on souhaite une base de données sqlite
(par défaut) ou si on veut se connecter à une base de données PostgreSQL (rentrer
``postgres`` dans ``DJANGO_DEV_STORE_METHOD``), auquel cas les paramètres de
base de données seront interprétés.
Les champs ``DJANGO_DB_`` sont relatifs à la connexion à la base de données PostgreSQL.
Le champ ``DJANGO_SECRET_KEY`` est utilisé pour la protection CSRF (voir la documentation
`<https://docs.djangoproject.com/fr/3.2/ref/csrf/>`_ pour plus de détails). Il s'agit d'une
clé sous forme de chaîne de caractère suffisamment longue (64 caractères paraît bien)
qui n'est pas à transmettre et qui évite d'autres sites malveillants de faire des requêtes
directement sur la note.
Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅e⋅s peuvent contacter
en cas de problème. C'est là où le champ ``Nous contacter`` redirigera.
Le champ ``NOTE_URL`` correspond au nom de domaine autorisé à accéder au site. C'est également
le nom de domaine qui sera utilisé dans l'envoi de mails pour générer des liens. En
production, cela vaut ``note.crans.org``.
Le champ ``NOTE_MAIL`` correspond au champ expéditeur des mails envoyés, que ce soit
pour les rapports quotidiens / hebdomadaires / mensuels ou les mails d'erreur.
En production, ce champ vaut ``notekfet2020@crans.org``.
Les champs ``EMAIL_`` sont relatifs à la connexion au serveur SMTP pour l'envoi de mails.
En production, ``EMAIL_HOST`` vaut ``smtp.crans.org``, ``EMAIL_PORT`` vaut 25 (on reste sur
le réseau interne du Crans) et ``EMAIL_USER`` et ``EMAIL_PASSWORD`` sont vides (ce qui est
valide car la note est sur le réseau du Crans, qui est déjà pré-autorisé à envoyer des mails).
Les champs ``WIKI_USER`` et ``WIKI_PASSWORD`` servent à s'authentifier sur le compte Wiki
Crans ``NoteKfet2020``, pour notamment exporter automatiquement la liste des activités sur
le wiki.
Pour configurer la note, il est également possible de créer un fichier
``note_kfet/settings/secrets.py`` qui redéfinit certains paramètres, notamment la
liste des administrateurs ou certaines applications optionnelles, ou encore certains
éventuels mots de passe.
En production, ce fichier contient :
.. code:: python
OPTIONAL_APPS = [
'cas_server',
# 'debug_toolbar'
]
# When a server error occured, send an email to these addresses
ADMINS = (
('Note Kfet', 'notekfet2020@lists.crans.org'),
)
Configuration des tâches récurrentes
------------------------------------
Certaines opérations se font périodiquement, comme le rappel hebdomadaire pour les
personnes en négatif. On utilise pour cela un cron. Il suffit pour cela de copier
le fichier ``note.cron`` vers ``/etc/cron.d/note``, en veillant à ce qu'il appartienne
bien à ``root``.
Ce fichier contient l'ensemble des tâches récurrentes associées à la note.
Une page de documentation dédiée fera bientôt son apparition.
Sur un serveur de pré-production, on peut ne pas souhaiter activer ces tâches récurrentes.
Installation de la base de données PostgreSQL
---------------------------------------------
En production, on utilise une vraie base de données PostgreSQL et non un fichier
sqlite. Beaucoup plus facile pour faire éventuellement des requêtes (bien que pas
adapté pour Django) mais surtout bien mieux optimisé pour un serveur de production.
Pour installer la base de données, on commence par installer PostgreSQL :
.. code:: bash
$ sudo apt install --no-install-recommends postgresql postgresql-contrib
PostgreSQL est désormais installé et lancé. On crée un compte ``note``, avec un
bon mot de passe (le même que donné à Django) :
.. code:: bash
$ sudo -u postgres createuser -P note
Et on crée enfin une base de données nommée ``note_db`` appartenant à ``note``.
.. code:: bash
$ sudo -u postgres createdb note_db -O note
La base de données est désormais prête à être utilisée.
Finir l'installation de Django
------------------------------
On commence par construire la base de données à partir des migrations enregistrées :
.. code:: bash
$ ./manage.py migrate
On doit compiler les traductions (pour pouvoir les lire plus vite par la suite) :
.. code:: bash
$ ./manage.py compilemessages
Les fichiers statiques (fiches de style, fichiers Javascript, images, ...) doivent
être exportées dans le dossier ``static`` :
.. code:: bash
$ ./manage.py collectstatic
Et on peut enfin importer certaines données de base :
.. code:: bash
$ ./manage.py loaddata initial
La note est désormais prête à être utilisée. Ne reste qu'à configurer un serveur Web.
Configuration de UWSGI
----------------------
On dispose d'une instance de la note fonctionnelle et bien configurée. Cependant, nous
n'avons pas encore de socket permettant d'intéragir avec le serveur. C'est le travail
de UWSGI.
On rappelle que la commande ``./manage.py runserver`` n'est pas conçue pour des serveurs
de production, contrairement à UWSGI.
On commence par installer UWSGI :
.. code:: bash
$ sudo apt install --no-install-recommends uwsgi uwsgi-plugin-python3
On place ensuite le fichier de configuration UWSGI dans les applications installées.
Un fichier de configuration est présent à la racine du projet, contenant :
.. code:: ini
[uwsgi]
uid = www-data
gid = www-data
# Django-related settings
# the base directory (full path)
chdir = /var/www/note_kfet
# the virtualenv (full path)
home = /var/www/note_kfet/env
wsgi-file = /var/www/note_kfet/note_kfet/wsgi.py
plugin = python3
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe
socket = /var/www/note_kfet/note_kfet.sock
# ... with appropriate permissions - may be needed
chmod-socket = 664
# clear environment on exit
vacuum = true
# Touch reload
touch-reload = /var/www/note_kfet/note_kfet/settings/__init__.py
# Enable threads
enable-threads = true
Il suffit donc de créer le lien symbolique :
.. code:: bash
$ sudo ln -s /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/uwsgi_note.ini
On peut désormais relancer UWSGI :
.. code:: bash
$ sudo systemctl restart uwsgi
Configuration de NGINX
----------------------
Nous avons désormais un socket qui nous permet de faire des connexions au serveur web,
placé dans ``/var/www/note_kfet/note_kfet.sock``. Cependant, ce socket n'est pas accessible
au reste du monde, et ne doit pas l'être : on veut un serveur Web Nginx qui s'occupe des
connexions entrantes et qui peut servir de reverse-proxy, notamment utile pour desservir
les fichiers statiques ou d'autres sites sur le même serveur.
On commence donc par installer Nginx :
.. code:: bash
$ sudo apt install nginx
On place ensuite dans ``/etc/nginx/sites-available/nginx_note.conf`` le fichier de
configuration Nginx qui va bien, en remplaçant ``note.crans.org`` par ce qu'il faut :
.. code::
# the upstream component nginx needs to connect to
upstream note {
server unix:///var/www/note_kfet/note_kfet.sock; # file socket
}
# Redirect HTTP to nk20 HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
return 301 https://note.crans.org$request_uri;
}
}
# Redirect all HTTPS to nk20 HTTPS
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
location / {
return 301 https://note.crans.org$request_uri;
}
ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
# configuration of the server
server {
listen 443 ssl;
listen [::]:443 ssl;
# the port your site will be served on
# the domain name it will serve for
server_name note.crans.org;
charset utf-8;
# max upload size
client_max_body_size 75M;
# Django media
location /media {
alias /var/www/note_kfet/media;
}
location /static {
alias /var/www/note_kfet/static;
}
location /doc {
alias /var/www/documentation;
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass note;
include /etc/nginx/uwsgi_params;
}
ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
On peut enfin activer le site :
.. code::
$ sudo ln -s /etc/nginx/sites-available/nginx_note.conf /etc/nginx/sites-enabled/nginx_note.conf
Si on peut se dire que recharger Nginx suffira, il n'en est rien : voir paragraphe suivant.
Génération d'un certificat SSL
------------------------------
Nginx va essayer de lire les certificats présents dans
``/etc/letsencrypt/live/note.crans.org/``, mais ce dossier n'existe pas encore.
On doit donc générer un certificat pour permettre les connexions HTTPS. Cela est permis
grâce à ``certbot``, qu'on s'empresse d'installer :
.. code:: bash
$ sudo apt install certbot python3-certbot-nginx
Le plugin pour nginx permet de certifier que le serveur a bien les droits pour
``note.crans.org`` grâce à Nginx, le BDE n'ayant a priori aucune raison de pouvoir
gérer le nom de domaine ``crans.org``.
On place dans le dossier ``/etc/letsencrypt/conf.d`` (qu'on crée au besoin) un fichier
nommé ``nk20.ini`` :
.. code:: ini
# To generate the certificate, please use the following command
# certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly
# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096
# Always use the staging/testing server
# server = https://acme-staging.api.letsencrypt.org/directory
# Uncomment and update to register with the specified e-mail address
email = notekfet2020@lists.crans.org
# Uncomment to use a text interface instead of ncurses
text = True
# Use Nginx challenge
authenticator = nginx
En exécutant ``certbot``, il va lire les fichiers de configuration Nginx et générer les
certificats qu'il faut en créant un point d'entrée pour le serveur.
Il faut néanmoins que la configuration soit valide. Les certificats n'existant pas encore,
la configuration nginx est donc pour l'instant invalide. Il faut alors temporairement
commenter les parties de la configuration qui traitent des certificats et relancer ``nginx``
(``sudo systemctl reload nginx``).
On peut ensuite exécuter ``certbot`` :
.. code:: bash
$ certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly
L'instruction ``certonly`` indique à ``certbot`` qu'il se contente de générer le certificat,
sans chercher à l'installer. Si tout s'est bien passé, l'installation se fait simplement
en décommentant les lignes préalablement commentées.
Un certificat généré de la sorte expire au bout de 3 mois. Néanmoins, certbot tourne
régulièrement pour renouveler les certificats actifs automatiquement. Il n'y a donc
plus rien à faire.
Après avoir rechargé la configuration de ``Nginx``, rendez-vous sur
`<https://note.crans.org>`_ (ou votre site) pour vérifier que tout fonctionne correctement :)
Mettre à jour la note
---------------------
Pour mettre à jour la note, il suffit a priori, après avoir mis à jour les paquets APT,
de faire un ``git pull`` dans le dossier ``/var/www/note_kfet``.
Les éventuelles nouvelles migrations de la base de données doivent être appliquées :
.. code:: bash
$ ./manage.py migrate
Les nouvelles traductions compilées :
.. code:: bash
$ ./manage.py compilemessages
Les nouveaux fichiers statiques collectés :
.. code:: bash
$ ./manage.py collectstatic
Et enfin les nouvelles fixtures installées :
.. code:: bash
$ ./manage.py loaddata initial
Une fois tout cela fait, il suffit de relancer le serveur UWSGI :
.. code:: bash
$ sudo systemctl restart uwsgi
Avec Ansible
------------
Tout ce travail peut sembler très laborieux et peut mériter d'être automatisé.
Toutefois, il est essentiel de bien comprendre comment chaque étape de l'installation
fonctionne.
Un playbook Ansible a été écrit permettant de réaliser toutes les tâches décrites ci-dessus.
Il se trouve dans le dossier ``ansible``.
Ansible s'installe sur votre propre machine (et non sur le serveur) en installant simplement
le paquet ``ansible``.
Pour déployer la note sur un serveur vierge, commencez par copier le fichier ``hosts_example``
en le nommant ``hosts``. Ajoutez votre propre serveur, dans la section correspondante.
Dans le dossier ``host_vars``, créez un fichier dont le nom est l'adresse du serveur, avec
l'extension ``.yml``.
Dans ce fichier, remplissez :
.. code:: yaml
---
note:
server_name: note.crans.org
git_branch: master
cron_enabled: true
email: notekfet2020@lists.crans.org
en adaptant à votre configuration.
Il suffit ensuite de lancer ``./base.yml -l urldevotreserveur``.
Pour une première installation, vous devrez renseigner le mot de passe de la base de données
pour créer le compte ``note``. Vous devrez ensuite également refaire quelques ajustements
pour générer le certificat, voir la partie ``certbot``. La configuration du fichier
``.env`` sera également à faire à la main.
Cependant, pour mettre à jour, lancer cette commande suffit.
Copier une base de données
--------------------------
On peut vouloir périodiquement copier la base de données de production vers le serveur
de développement, afin de travailler avec des données à jour.
On aura besoin de pouvoir accéder aux deux bases de données. On commence donc si ce n'est
pas déjà fait par créer un utilisateur sur les deux serveurs :
.. code:: bash
ynerant@bde-note:~$ sudo -u postgres createuser -l ynerant
On réinitialise **sur le serveur de développement** la base de données présente, en
éteignant tout d'abord le serveur Web :
.. code:: bash
ynerant@bde-note-dev:~$ sudo systemctl stop uwsgi
ynerant@bde-note-dev:~$ sudo -u postgres dropdb note_db
ynerant@bde-note-dev:~$ sudo -u postgres createdb -O note note_db
Et on copie enfin la base de données, en une seule ligne via SSH :
.. code:: bash
ynerant@bde-note:~$ pg_dump note_db | ssh note-dev.crans.org "psql note_db -f -"
On peut enfin redémarrer le serveur Web. Les données ont bien été copiées.
.. caution::
On ne copiera **jamais** des données d'adhérent⋅e⋅s sur une machine personnelle.
Ce type d'opération doit s'effectuer impérativement entre des serveurs du BDE.

308
docs/scripts.rst Normal file
View File

@ -0,0 +1,308 @@
Les scripts de la Note Kfet 2020
================================
Django permet la création de scripts permettant l'interaction en ligne de commande
avec le site. Les scripts sont gérés dans un projet à part :
`nk20-scripts <https://gitlab.crans.org/bde/nk20-scripts>`_.
Il s'agit d'un module Python à part contenant tous les scripts interagissant avec la note.
Pour l'installer, il suffit d'importer le sous module (``git submodule init apps/scripts``)
ou bien d'avoir ajouté l'option ``--recursive`` lorsque le dépôt a été cloné. Il faut
ensuite ajouter le module au tableau ``INSTALLED_APPS`` dans les paramètres.
Scripts Python
##############
Structure d'un script
---------------------
Un script est un fichier Python placé dans ``apps/scripts/management/commands``. Le nom
de fichier sans l'extension ``.py`` caractérise le nom du script. On supposera dans la suite
qu'on aura créé le script ``toto.py``. Pour lancer le script, il suffira de lancer
``./manage.py toto``. Django se charge de trouver l'emplacement du script. Un simple
``./manage.py`` affichera par ailleurs la liste des scripts par application.
Ce fichier Python doit contenir une classe nommée ``Command`` héritant de
``django.core.management.base.BaseCommand``.
Il suffit enfin de créer une fonction ``handle(self, *args, **options) -> None``.
C'est cette fonction qui servira à l'exécution du script. ``options`` contient
l'ensemble des options transmises.
Pour gérer les options, créez une fonction ``add_arguments(self, parser)``.
``parser`` vous permet de gérer vos arguments. Plus d'informations dans la documentation
du module ``argparse`` de Python : https://docs.python.org/fr/3/library/argparse.html
.. warning::
Bonne pratique : si votre script doit écrire sur la base de données, pensez à
ajouter un décorateur ``@transaction.atomic`` (du module ``django.db``) sur
votre fonction ``handle``. Cela aura pour effet de ne pas effectuer les modifications
indiquées en cas d'erreur dans le script. Cela évite de vous retrouver avec une base
de données dans un état instable en cas d'erreur dans le script, ou en développement
(sur un serveur de développement bien sûr).
.. warning::
Par défaut, chaque commande dispose d'un certain nombre d'options, dont ``--help`` qui
affiche l'aide et les options disponibles, et ``--verbosity {0, 1, 2, 3}`` qui permet
de contrôler la verbosité du script. Il est important d'en tenir compte, en particulier
un niveau de verbosité nul ne devrait afficher rien d'autre que des erreurs.
Plus d'informations dans la documentation officielle :
https://docs.djangoproject.com/fr/2.2/howto/custom-management-commands/
Anonymisation de données
------------------------
Le script s'appelle ``anonymize_data``.
Ce script était utilisé lors de la beta de la Note Kfet 2020, afin d'anonymiser certaines
données personnelles et limiter le risque de fuites de données. Ainsi, les noms, prénoms,
adresses électroniques, numéros de téléphone et adresses étaient normalisées.
Ce script étant dangereux, l'option ``--force`` est requise.
Vérification de l'intégrité des données
---------------------------------------
Le script s'appelle ``check_consistency``.
Son but est de s'assurer que la somme des montants de toutes les notes vaut bien zéro,
et que pour chaque note, la somme des transactions donne bien le solde de la note.
En cas de problème, les erreurs détectées sont affichées.
Le script prend plusieurs options :
* ``--sum-all, -s`` : vérifie si la somme des notes vaut bien 0
* ``--check-all, -a`` : vérifie si le solde de toutes les notes est cohérent
* ``--check, -c`` : si l'option précédente n'est pas présente, indique les identifiants
des notes à vérifier
* ``--fix, -f`` : Rétablit le solde des notes à la somme des transactions. À n'utiliser
qu'après avoir identifié le problème.
Ce script est appelé tous les jours à 4h du matin avec les options ``--sum-all --check-all``,
et en cas d'erreur un mail est envoyé.
Compilation des messages JavaScript
-----------------------------------
Le script s'appelle ``compilejsmessages``.
Django gère nativement la traduction des chaînes de caractères, avec notamment les scripts
``makemessages`` et ``compilemessages``. Django permet également de gérer la traduction
dans les fichiers statiques JavaScript, comme l'indique la documentation officielle :
https://docs.djangoproject.com/fr/2.2/topics/i18n/translation/#internationalization-in-javascript-code
Comme l'indique cette documentation, cela revient à ajouter un fichier Javascript contenant
l'ensemble des traductions et le navigateur s'occupe de récupérer les bonnes traductions.
Cependant, la façon standard de gérer cela est d'avoir une vue dédiée qui générera le bon
fichier Javascript. Les traductions ne changeant pas souvent (à chaque mise à jour
uniquement), il n'est pas essentiel de les recompiler à chaque chargement de page.
Le protocole choisi est donc de générer des fichiers statiques, qui seront donc directement
servis par Nginx (et éventuellement mis en cache par le client) et non recalculés à chaque
fois. On optimise donc les requêtes.
Pour rappel, pour générer les fichiers de traduction Javascript :
.. code:: bash
./manage.py makemessages -i env -e js -d djangojs
Et on peut donc appeler ce script ``compilejsmessages`` pour créer les fichiers Javascript
correspondant et le placer dans le dossier des fichiers statiques.
Extraction des listes de diffusion
----------------------------------
Le script s'appelle ``extract_ml_registrations``.
Il a pour but d'extraire une liste d'adresses mail pour les inclure directement dans les listes
de diffusion utiles.
Il prend 2 options :
* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``,
``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérents
actuels (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour
``clubs@lists.crans.org``), des personnes à abonner à ``evenements@lists.crans.org``,
à ``all.bda@lists.crans.org`` et enfin à ``bds@lists.crans.org``.
* ``--lang``, qui prend en argument ``fr`` ou ``en``. N'est utile que pour la ML événements,
qui a pour projet d'être disponible à la fois en anglais et en français.
Le script sort sur la sortie standard la liste des adresses mails à inscrire.
Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est
malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives.
À terme, il pourrait être envisageable de synchroniser automatiquement les listes avec la note.
Suppression d'un utilisateur
----------------------------
Le script s'appelle ``force_delete_user``.
.. caution::
Ce script est dangereux. À n'utiliser qu'avec de très grosses pincettes si vous savez
ce que vous faites. Seul cas d'usage pour l'instant recensé : supprimer des comptes en
double qui se sont malencontreusement retrouvés validés pour raison de Sogé et de mauvaise
communication au sein des trésorier⋅ère⋅s.
Il n'est certainement pas prévu de supprimer des vrais comptes existants via ce script.
On ne supprime pas l'historique. Si jamais quelqu'un demanderait à supprimer son compte,
on se contente de l'anonymiser, et non de le supprimer.
Ce script est utile lorsqu'il faut supprimer un compte créer par erreur. Tant que la validation
n'est pas faite, il suffit en général de cliquer sur le bouton « Supprimer le compte » sur
l'interface de validation. Cela supprimera l'utilisateur et le profil associé, sans toucher
à une quelconque note puisqu'elle ne sera pas créée.
Ce script supprime donc un compte ainsi que toutes les données associées (note, alias,
transactions). Il n'est donc pas à prendre à la légère, et vous devez savoir ce que vous
faites.
Il prend en arguments les identifiants numériques ou un alias de la ou des personnes à
supprimer.
Sans rien ajouter, il affichera uniquement les éléments qui seront supprimés.
Avec l'option ``--force``, il commencera à créer une transaction dans la base de données
pour supprimer tout ce qu'il faut, et une validation manuelle sera requise pour confirmer
la suppression. L'option ``--doit`` évite cette confirmation manuelle.
**Vous n'avez jamais à utiliser cette option en théorie.**
À la fin du processus, un mail est envoyé aux administrateurs pour les prévenir des
élements supprimés.
Des données réelles jamais tu ne supprimeras.
Importation de la Note Kfet 2015
--------------------------------
Les scripts commençant par ``import_`` sont destinés à l'import des données depuis
la Note Kfet 2015.
.. warning::
TODO: Pour la postérité et la conservation d'archives, documenter comment l'import
s'est déroulé.
Ajouter un super-utilisateur
----------------------------
Le script s'appelle ``make_su``.
Il prend en argument un pseudo.
Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur,
et obtiens donc les pleins pouvoirs sur la note. À ne donner qu'aux respos info.
Avec l'option ``--STAFF, -s``, la personne avec ce pseudo acquiert le statut équipe,
et obtiens l'accès à django-admin. À ne donner qu'aux respos info.
Rafraîchissement des activités
------------------------------
Le script s'appelle ``refresh_activities``.
Il a pour but de mettre à jour le Wiki du Crans automatiquement en ajoutant les
activités de la Note Kfet sur le calendrier, à savoir les pages
`<https://wiki.crans.org/VieBde/PlanningSoirees>`_ et
`<https://wiki.crans.org/VieBde/PlanningSoirees/LeCalendrier>`_.
Il prend diverses options :
* ``--human, -H`` : met à jour la version lisible de la page des activités
* ``--raw, -r`` : met à jour la version brute de la page des activités, interprétable
par le calendrier
* ``--comment, -c`` : définit le commentaire à ajouter à la modification du wiki
* ``--stdout, -o`` : affiche la page sur la sortie standard
* ``--wiki, -w`` : applique effectivement les modifications sur le wiki
Ce script est appelé tous les jours à 5h30 avec les options
``--raw --human --comment refresh --wiki``.
Rafraîchissement des boutons mis en avant
-----------------------------------------
Le script s'appelle ``refresh_highlighted_buttons``.
Il permet d'actualiser la liste des boutons mis en avant sur la page de consommations
qui servent de raccourcis.
Ce script récupère la liste des 10 boutons les plus cliqués les 30 derniers jours et
les met en avant.
Envoi des rappels de négatif
----------------------------
Le script s'appelle ``send_mail_to_negative_balances``.
Il sert à rappeler aux adhérent⋅e⋅s et clubs en négatif qu'ils le sont, mais également
à envoyer aux trésorier⋅ère⋅s et respos info la liste des adhérent⋅e⋅s en négatif.
Il prend les options suivantes :
* ``--spam, -s`` : envoie à chaque adhérent⋅e en négatif un rappel par mail pour recharger
* ``--report, -r`` : envoie le rapport aux trésorier⋅ère⋅s et respos info
* ``--negative-amount,-n`` : définit le solde maximal en-dessous duquel les notes
apparaitront sur le rapport / seront spammées
* ``--add-years, -y`` : ajoute également les adhérent⋅e⋅s des ``n`` dernières années
Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅rice⋅s en
négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅e⋅s ou de
vieux/vieilles adhérent⋅e⋅s de moins d'un an sous -10 €.
Envoi des rapports
------------------
Le script s'appelle ``send_reports``.
Les utilisateurs ont la possibilité de recevoir sur demande un rapport à la fréquence de
leur choix (en jours) des transactions effectuées sur leur note.
Le script prend 2 options :
* ``--notes, -n`` : sélectionne les notes auxquelles envoyer un rapport. Si non spécifié,
envoie un mail à toutes les notes demandant un rapport.
* ``--debug, -d`` : affiche les rapports dans la sortie standard sans les envoyer par mail.
Les dates de dernier rapport ne sont pas actualisées.
Ce script est appelé tous les jours à 6h55.
Scripts bash
############
À quelques fins utiles, certains scripts bash sont également présents, dans le dossier
``scripts/shell``.
Sauvegardes de la base de données
---------------------------------
Le script ``backup_db`` permet de faire une sauvegarde de la base de données PostgreSQL
et l'envoie sur ``club-bde@zamok.crans.org``. Le script doit être lancé en tant que root.
Tabularasa
----------
Ce script n'a un intérêt qu'en développement, afin de détruire la base de données et la
recréer en appliquant les migrations et en chargeant les données initiales.

View File

@ -5,7 +5,7 @@
if [ -z ${NOTE_URL+x} ]; then if [ -z ${NOTE_URL+x} ]; then
echo "Warning: your env files are not configurated." echo "Warning: your env files are not configurated."
else else
sed -i -e "s/example.com/$DOMAIN/g" /var/www/note_kfet/apps/member/fixtures/initial.json sed -i -e "s/example.com/$NOTE_URL/g" /var/www/note_kfet/apps/member/fixtures/initial.json
sed -i -e "s/localhost/$NOTE_URL/g" /var/www/note_kfet/note_kfet/fixtures/initial.json sed -i -e "s/localhost/$NOTE_URL/g" /var/www/note_kfet/note_kfet/fixtures/initial.json
sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /var/www/note_kfet/note_kfet/fixtures/cas.json sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /var/www/note_kfet/note_kfet/fixtures/cas.json
sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /var/www/note_kfet/note_kfet/fixtures/cas.json sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /var/www/note_kfet/note_kfet/fixtures/cas.json

View File

@ -1,24 +1,27 @@
# {{ ansible_managed }} # {{ ansible_managed }}
# Les cronjobs dont a besoin la Note Kfet # Les cronjobs dont a besoin la Note Kfet
# Envoi des mails aux respos info
MAILTO=notekfet2020@lists.crans.org
# m h dom mon dow user command # m h dom mon dow user command
# Envoyer les mails en attente # Envoyer les mails en attente
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 * * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -v 0
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 * * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -v 0
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -c 1 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0
# Faire une sauvegarde de la base de données # Faire une sauvegarde de la base de données
00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db 00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db
# Vérifier la cohérence de la base et mailer en cas de problème # Vérifier la cohérence de la base et mailer en cas de problème
00 4 * * * root cd /var/www/note_kfet && env/bin/python manage.py check_consistency --sum-all --check-all --mail 00 4 * * * root cd /var/www/note_kfet && env/bin/python manage.py check_consistency --sum-all --check-all --mail -v 0
# Mettre à jour le wiki (modification sans (dé)validation, activités passées) # Mettre à jour le wiki (modification sans (dé)validation, activités passées)
30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki 30 5 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_activities --raw --human --comment refresh --wiki -v 0
# Spammer les gens en négatif # Spammer les gens en négatif
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 0 -v 0
# Envoyer le rapport mensuel aux trésoriers et respos info # Envoyer le rapport mensuel aux trésoriers et respos info
00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report 00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0
# Envoyer les rapports aux gens # Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0
# Mettre à jour les boutons mis en avant # Mettre à jour les boutons mis en avant
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons 00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0
# Vider les tokens Oauth2 # Vider les tokens Oauth2
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens 00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0

View File

@ -52,3 +52,9 @@ if "rest_framework" in settings.INSTALLED_APPS:
from rest_framework.authtoken.admin import * from rest_framework.authtoken.admin import *
from rest_framework.authtoken.models import * from rest_framework.authtoken.models import *
admin_site.register(Token, TokenAdmin) admin_site.register(Token, TokenAdmin)
if "cas_server" in settings.INSTALLED_APPS:
from cas_server.admin import *
from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)

View File

@ -12,7 +12,7 @@ def read_env():
directory. directory.
""" """
try: try:
with open('.env') as f: with open(os.path.join(BASE_DIR, '.env')) as f:
content = f.read() content = f.read()
except IOError: except IOError:
content = '' content = ''
@ -30,6 +30,7 @@ def read_env():
# Try to load environment variables from project .env # Try to load environment variables from project .env
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
read_env() read_env()
# Load base settings # Load base settings

View File

@ -239,6 +239,7 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [ 'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
], ],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20, 'PAGE_SIZE': 20,
@ -273,3 +274,6 @@ PIC_RATIO = 1
# Custom phone number format # Custom phone number format
PHONENUMBER_DB_FORMAT = 'NATIONAL' PHONENUMBER_DB_FORMAT = 'NATIONAL'
PHONENUMBER_DEFAULT_REGION = 'FR' PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'

View File

@ -45,6 +45,11 @@ if "oauth2_provider" in settings.INSTALLED_APPS:
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')) path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
) )
if "cas_server" in settings.INSTALLED_APPS:
urlpatterns.append(
path('cas/', include('cas_server.urls', namespace='cas_server'))
)
if "debug_toolbar" in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [

View File

@ -1,6 +1,7 @@
beautifulsoup4~=4.7.1 beautifulsoup4~=4.7.1
Django~=2.2.15 Django~=2.2.15
django-bootstrap-datepicker-plus~=3.0.5 django-bootstrap-datepicker-plus~=3.0.5
django-cas-server~=1.2.0
django-colorfield~=0.3.2 django-colorfield~=0.3.2
django-crispy-forms~=1.7.2 django-crispy-forms~=1.7.2
django-extensions~=2.1.4 django-extensions~=2.1.4