Rewrite documentation with MkDocs

This commit is contained in:
thomas girod
2024-07-16 18:39:54 +02:00
parent a1296dc7af
commit 07b625d4aa
181 changed files with 3322 additions and 3361 deletions

59
docs/howto/direnv.md Normal file
View File

@ -0,0 +1,59 @@
Pour éviter d'avoir à sourcer l'environnement
à chaque fois qu'on rentre dans le projet,
il est possible d'utiliser l'utilitaire [direnv](https://direnv.net/).
Comme pour beaucoup de choses, il faut commencer par l'installer :
=== "Linux"
=== "Debian/Ubuntu"
```bash
sudo apt install direnv
```
=== "Arch Linux"
```bash
sudo pacman -S direnv
```
=== "macOS"
```bash
brew install direnv
```
Puis on configure :
=== "bash"
```bash
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
exit # On redémarre le terminal
```
=== "zsh"
```zsh
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
exit # On redémarre le terminal
```
=== "nu"
Désolé, par `direnv hook` pour `nu`
Une fois le terminal redémarré, dans le répertoire du projet :
```bash
direnv allow .
```
Une fois que cette configuration a été appliquée,
aller dans le dossier du site applique automatiquement
l'environnement virtuel.
Ça peut faire gagner pas mal de temps.
Direnv est un utilitaire très puissant
et qui peut s'avérer pratique dans bien des situations,
n'hésitez pas à aller vous renseigner plus en détail sur celui-ci.

354
docs/howto/migrations.md Normal file
View File

@ -0,0 +1,354 @@
## Qu'est-ce qu'une migration ?
Une migration est un fichier Python qui contient
des instructions pour modifier la base de données.
Une base de données évolue au cours du temps,
et les migrations permettent de garder une trace
de ces modifications.
Grâce à elles, on peut également apporter des modifications
à la base de données sans être obligées de la recréer.
On applique seulement les modifications nécessaires.
## Appliquer les migrations
Pour appliquer les migrations, exécutez la commande suivante :
```bash
python ./manage.py migrate
```
Vous remarquerez peut-être que cette commande
a été utilisée dans la section
[Installation](../tutorial/install.md).
En effet, en partant d'une base de données vierge
et en appliquant toutes les migrations, on arrive
à l'état actuel de la base de données.
Logique.
Si vous utilisez cette commande sur une base de données
sur laquelle toutes les migrations ont été appliquées,
elle ne fera rien.
Si vous utilisez cette commande sur une base de données
sur laquelle seule une partie des migrations ont été appliquées,
seules les migrations manquantes seront appliquées.
## Créer une migration
Pour créer une migration, exécutez la commande suivante :
```bash
python ./manage.py makemigrations
```
Cette commande comparera automatiquement le contenu
des classes de modèles et le comparera avec les
migrations déjà appliquées.
A partir de cette comparaison, elle générera
automatiquement une nouvelle migration.
!!! note
La commande `makemigrations` ne fait que
générer les fichiers de migration.
Elle ne modifie pas la base de données.
Pour appliquer la migration, n'oubliez pas la
commande `migrate`.
Un fichier de migration ressemble à ça :
```python
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
# liste des autres migrations à appliquer avant celle-ci
]
operations = [
# liste des opérations à appliquer sur la db
]
```
Grâce à la liste des dépendances, Django sait dans
quel ordre les migrations doivent être appliquées.
Grâce à la liste des opérations, Django sait quelles
sont les opérations à appliquer durant cette migration.
## Revenir à une migration antérieure
Lorsque vous développez, il peut arriver que vous vouliez
revenir à une migration antérieure.
Pour cela, il suffit d'appliquer la commande `migrate`
en spécifiant le nom de la migration à laquelle vous
voulez revenir :
```bash
python ./manage.py migrate <application> <numéro de la migration>
```
Par exemple, si vous voulez revenir à la migration `0001_initial`
de l'application `customer`, vous pouvez exécuter la commande suivante :
```bash
python ./manage.py migrate customer 0001
```
## Customiser une migration
Il peut arriver que vous ayez besoin de modifier
le fichier de migration généré par Django.
Par exemple, si vous voulez exécuter un script Python
lors de l'application de la migration.
Dans ce cas, vous pouvez trouver les fichiers de migration
dans le dossier `migrations` de chaque application.
Vous pouvez modifier le fichier Python correspondant
à la migration que vous voulez modifier.
Ajoutez l'opération que vous voulez effectuer
dans l'attribut `operations` de la classe `Migration`.
Par exemple :
```python
from django.db import migrations
def forwards_func(apps, schema_editor):
print("Appplication de la migration")
def reverse_func(apps, schema_editor):
print("Annulation de la migration")
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
```
!!! warning "Script d'annulation de la migration"
Lorsque vous incluez un script Python dans une migration,
incluez toujours aussi un script d'annulation,
sinon Django ne pourra pas annuler la migration
après son application.
Vous ne pourrez donc pas revenir à un état antérieur
de la db, à moins de la recréer de zéro.
## Fusionner des migrations
Quand on travaille sur une fonctionnalité
qui nécessite une modification de la base de données,
les fichiers de migration sont comme toute chose :
on peut se rendre compte que les changements
apportés pourraient être meilleurs.
Par exemple, supposons que nous voulons créer un modèle
représentant une UE suivie par un étudiant
(ne demandez pas pourquoi on voudrait faire ça,
c'est juste pour l'exemple).
Un tel modèle aurait besoin des informations suivantes :
- l'utilisateur
- le code de l'UE
On écrirait donc, dans l'application `pedagogy` :
```python
from django.db import models
from core.models import User
class UserUe(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ue = models.CharField(max_length=10)
```
Et nous aurions le fichier de migration suivant :
```python
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("pedagogy", "0003_alter_uv_language"),
]
operations = [
migrations.CreateModel(
name="UserUe",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("ue", models.CharField(max_length=10)),
(
"user",
models.ForeignKey(
on_delete=models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
```
On finit son travail, on soumet la PR.
Mais là, quelqu'un fait remarquer qu'il existe déjà
un modèle pour représenter une UE.
On modifie donc le modèle :
```python
from django.db import models
from core.models import User
from pedagogy.models import UV
class UserUe(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ue = models.ForeignKey(UV, on_delete=models.CASCADE)
```
On refait la commande `makemigrations` et on obtient :
```python
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pedagogy", "0004_userue"),
]
operations = [
migrations.AlterField(
model_name="userue",
name="ue",
field=models.ForeignKey(
on_delete=models.deletion.CASCADE, to="pedagogy.uv"
),
),
]
```
Sauf que maintenant, nous avons deux fichiers de migration,
alors qu'en réalité, on ne souhaite faire qu'une seule migration,
une fois qu'on aura expédié le code en prod.
Certes, ça fonctionnerait d'appliquer les deux, mais ça pose
un problème d'encombrement.
Plus il y a de fichiers de migrations, plus il y a de migrations
à résoudre au moment de l'installation du projet chez quelqu'un,
plus c'est embêtant à gérer et plus Django prendra du temps
à résoudre les migrations.
C'est pourquoi il est bon de respecter le principe :
une PR = un fichier de migration maximum par application.
Nous voulons donc fusionner les deux, pour n'en garder qu'une.
Pour ça, deux manières de procéder :
- le faire à la main
- utiliser la commande squashmigrations
Pour la méthode manuelle, on ne pourrait pas vous dire exhaustivement
comment faire.
Mais ne vous inquiétez pas, ce n'est pas très dur.
Regardez bien quelles sont les instructions utilisées par django
pour les opérations de migrations,
et avec un peu d'astuce et quelques copier-coller,
vous vous en sortirez comme des chefs.
Pour la méthode `squashmigrations`, exécutez la commande
```bash
python ./manage.py squasmigrations <app> <migration de début (incluse)> <migration de fin (incluse)>
```
Par exemple, dans notre cas, ça donnera :
```bash
python ./manage.py squasmigrations pedagogy 0004 0005
```
La commande vous donnera ceci :
```python
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [("pedagogy", "0004_userue"), ("pedagogy", "0005_alter_userue_ue")]
dependencies = [
("pedagogy", "0003_alter_uv_language"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="UserUe",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"ue",
models.ForeignKey(
on_delete=models.deletion.CASCADE, to="pedagogy.uv"
),
),
(
"user",
models.ForeignKey(
on_delete=models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
```
Vous pouvez alors supprimer les deux autres fichiers.
Vous remarquerez peut-être la présence de la ligne suivante :
```python
replaces = [("pedagogy", "0004_userue"), ("pedagogy", "0005_alter_userue_ue")]
```
Cela sert à dire que cette migration doit être appliquée
à la place des deux autres.
Une fois que vous aurez supprimé les deux fichiers,
supprimez également cette ligne.
!!!warning
Django sait quelles migrations ont été appliquées,
en les stockant dans une table de la db.
Si une migration est enregistrée en db, sans que le fichier
de migration correspondant existe,
la commande `migrate` échoue.
Quand vous faites un `squashmigrations`,
pensez donc à appliquer la commande `migrate`
juste après (mais avant la suppression des anciens fichiers),
pour que Django supprime de la base de données
les migrations devenues inutiles.

27
docs/howto/prod.md Normal file
View File

@ -0,0 +1,27 @@
## Configurer Sentry
Pour connecter l'application à une instance de sentry (ex: https://sentry.io),
il est nécessaire de configurer la variable `SENTRY_DSN`
dans le fichier `settings_custom.py`.
Cette variable est composée d'un lien complet vers votre projet sentry.
## Récupérer les statiques
Nous utilisons du SCSS dans le projet.
En environnement de développement (`DEBUG=True`),
le SCSS est compilé à chaque fois que le fichier est demandé.
Pour la production, le projet considère
que chacun des fichiers est déjà compilé et,
pour ce faire, il est nécessaire
d'utiliser les commandes suivantes dans l'ordre :
```bash
python ./manage.py collectstatic # Pour récupérer tous les fichiers statiques
python ./manage.py compilestatic # Pour compiler les fichiers SCSS qu'ils contiennent
```
!!!tip
Le dossier où seront enregistrés ces fichiers
statiques peut être changé en modifiant la variable
`STATIC_ROOT` dans les paramètres.

280
docs/howto/querysets.md Normal file
View File

@ -0,0 +1,280 @@
L'ORM de Django est puissant, très puissant, non par parce qu'il
est performant (après tout, ce n'est qu'une interface, le gros du boulot,
c'est la db qui le fait), mais parce qu'il permet d'écrire
de manière relativement simple un grand panel de requêtes.
De manière générale, puisqu'un ORM est un système
consistant à manipuler avec un code orienté-objet
une db relationnelle (c'est-à-dire deux paradigmes
qui ne fonctionnent absolument pas pareil),
on rencontre un des deux problèmes suivants :
- soit l'ORM n'offre pas assez d'abstraction,
auquel cas, quand on veut faire des requêtes
plus complexes qu'un `select` avec un `where`,
on s'emmêle les pinceaux et on se dit que
ça aurait été plus simple de le faire directement
en SQL.
- soit l'ORM offre trop d'abstraction,
auquel cas, on a tendance à ne pas prêter
assez attention aux requêtes envoyées en base
de données et on finit par se rendre compte
que les temps d'attente explosent
parce qu'on envoie trop de requêtes.
Django est dans ce deuxième cas.
C'est pourquoi nous ne parlerons pas ici
de son fonctionnement exact ni de toutes les fonctions
que l'on peut utiliser
(la doc officielle fait déjà ça mieux que nous),
mais plutôt des pièges courants
et des astuces pour les éviter.
## Les `N+1 queries`
### Le problème
Normalement, quand on veut récupérer une liste,
on fait une requête et c'est fini.
Mais des fois, ça n'est pas si simple.
Par exemple, supposons que nous voulons
récupérer les 100 utilisateurs les plus riches,
avec leurs informations client :
```python
from core.models import User
for user in User.objects.order_by("-customer__amount")[:100]:
print(user.customer.amount)
```
Combien de requêtes le bout de code suivant effectue-t-il ?
101\.
En deux pauvres lignes de code, nous avons demandé
à la base de données d'effectuer 101 requêtes.
Une requête toute seule n'est déjà une opération anodine,
alors je vous laisse imaginer ce que ça donne pour 101.
Si vous ne comprenez pourquoi ce nombre, c'est très simple :
- Une requête pour sélectionner nos 100 utilisateurs
- Une requête supplémentaire pour récupérer les informations
client de chaque utilisateur, soit 100 requêtes.
En effet, les informations client sont stockées dans une
autre table, mais le fait d'établir un lien de clef
étrangère permet de manipuler `customer`
comme si c'était un membre à part entière de `User`.
Il est à noter cependant, que Django n'effectue une requête
que pour le premier accès à un membre d'une relation
de clef étrangère.
Toutes les fois suivantes, l'objet est déjà là,
et django le récupère :
```python
from core.models import User
# l'utilisateur le plus riche
user = User.objects.order_by("-customer__amount").first() # <-- requête db
print(user.customer.amount) # <-- requête db
print(user.customer.account_id) # on a déjà récupéré `customer`, donc pas de requête
```
Ce n'est donc pas gravissime si vous faites cette
erreur quand vous manipulez un seul objet.
En revanche, quand vous en manipulez plusieurs,
il faut régler le problème.
Pour ça, il y a plusieurs méthodes, en fonction de votre cas.
### `select_related`
La méthode la plus basique consiste à annoter le queryset,
avec la méthode `select_related()`.
En faisant ça, Django fera une jointure sur l'autre table
et demandera des informations en plus
à la db lors de la requête.
De la sorte, lorsque vous appellerez le membre relié,
les informations seront déjà là.
```python
from core.models import User
richest = User.objects.order_by("-customer__amount")
for user in richest.select_related("customer")[:100]:
print(user.customer)
```
Le code ci-dessus effectue une seule requête.
Chaque fois qu'on veut accéder à `customer`, c'est bon,
ça a déjà été récupéré à travers le `annotate`.
### `prefetch_related`
Maintenant, un cas plus compliqué.
Supposons que vous ne vouliez pas récupérer des informations
reliées par une relation One-to-One,
mais par une relation One-to-Many.
Par exemple, un utilisateur a un seul compte client,
mais il peut avoir plusieurs cotisations à son actif.
Et dans ces cas-là, `annotate` ne marche plus.
En effet, s'il peut exister plusieurs cotisations,
comment savoir laquelle on veut ?
Il faut alors utiliser un `prefetch_related`.
C'est un mécanisme un peu différent :
au lieu de faire une jointure et d'ajouter les informations
voulues dans la même requête, Django va effectuer
une deuxième requête pour récupérer les éléments de l'autre table,
puis, à partir de ces éléments, peupler la relation
de son côté.
C'est un mécanisme qui peut être un peu coûteux en mémoire
et qui demande une deuxième requête,
mais qui reste quand même largement préférable
à faire N requêtes en plus.
```python
from core.models import User
for user in User.objects.prefetch_related("subscriptions")[:100]:
# c'est bon, la méthode prefetch a récupéré en avance les `subscriptions`
print(user.subscriptions.all())
```
!!! danger
La méthode `prefetch_related` ne marche que si vous
utilisez la méthode `all()` pour accéder au membre.
Si vous utilisez une autre méthode (comme `filter` ou `annotate`),
alors Django effectuera une nouvelle requête,
et vous retomberez dans le problème initial.
```python
from core.models import User
from django.db.models import Count
for user in User.objects.prefetch_related("subscriptions")[:100]:
# Le prefetch_related ne marche plus !
print(user.subscriptions.annotate(count=Count("*")))
```
### Récupérer ce dont vous avez besoin
Des fois (souvent, même), penser explicitement
à la jointure est le meilleur choix.
En effet, vous remarquerez que dans tous
les exemples précédents, nous n'utilisions
qu'une partie des informations
(par exemple, nous ne récupérions que la somme
d'argent sur les comptes, et éventuellement le numéro de compte).
Nous pouvons utiliser la méthode `annotate`
pour spécifier explicitement les données que l'on veut
joindre à notre requête.
Quand nous voulions récupérer les informations utilisateur,
nous aurions tout aussi bien pu écrire :
```python
from core.models import User
from django.db.models import F
richest = User.objects.order_by("-customer__amount")
for user in richest.annotate(amount=F("customer__amount"))[:100]:
print(user.amount)
```
On aurait même pu réorganiser ça :
```python
from core.models import User
from django.db.models import F
richest = User.objects.annotate(amount=F("customer__amount")).order_by("-amount")
for user in richest[:100]:
print(user.amount)
```
Ça peut sembler moins bien qu'un `select_related`, comme ça.
Des fois, c'est en effet moins bien, et des fois c'est mieux.
La comparaison est plus évidente avec le `prefetch_related`.
En effet, quand nous voulions récupérer
le nombre de cotisations des utilisateurs,
le `prefetch_related` ne marchait plus.
Pourtant, nous voulions récupérer une seule information.
Il aurait donc été suffisant d'écrire :
```python
from core.models import User
from django.db.models import Count
for user in User.objects.annotate(nb_subscriptions=Count("subscriptions"))[:100]:
# Et là ça marche, en une seule requête.
print(user.nb_subscriptions)
```
Faire une jointure, c'est normal en SQL.
Et pourtant avec Django on les oublie trop facilement.
Posez-vous toujours la question des données que vous pourriez
avoir besoin d'annoter, et vous éviterez beaucoup d'ennuis.
## Les aggrégations manquées
Il arrive souvent que l'on veuille une information qui
porte sur un ensemble d'objets de notre db.
Imaginons par exemple que nous voulons connaitre
la somme totale des ventes faites à un comptoir.
Nous avons tous suivi nos cours de programmation,
nous écrivons donc instinctivement :
```python
from counter.models import Counter
foyer = Counter.objects.get(name="Foyer")
total_amount = sum(
sale.amount * sale.unit_price
for sale in foyer.sellings.all()
)
```
On pourrait penser qu'il n'y a pas de problème.
Après tout, on ne fait qu'une seule requête.
Eh bien si, il y a un problème :
on fait beaucoup de choses en trop.
Concrètement, on demande à la base de données
de renvoyer toutes les informations,
ce qui rallonge inutilement la durée
de l'échange entre le serveur et la db,
puis on perd du temps à convertir ces informations
en objets Python (opération qui a un coût également),
et enfin on reperd du temps à calculer en Python
quelque chose que la db aurait pu calculer
à notre plus bien plus vite.
Nous aurions dû aggréger la requête,
avec la méthode `aggregate` :
```python
from counter.models import Counter
from django.db.models import Sum, F
foyer = Counter.objects.get(name="Foyer")
total_amount = (
foyer.sellings.aggregate(amount=Sum(F("amount") * F("unit_price"), default=0))
)["amount__sum"]
```
En effectuant cette requête, la base de données nous renverra exactement
l'information dont nous avons besoin.
Et de notre côté, nous n'aurons pas à faire de traitement en plus.

View File

@ -0,0 +1,46 @@
## Ajouter une nouvelle cotisation
Il peut arriver que le type de cotisation
proposé varie en prix et en durée.
Ces paramètres sont configurables directement dans les paramètres du projet.
Pour modifier les cotisations disponibles,
tout se gère dans la configuration avec la variable `SITH_SUBSCRIPTIONS`.
Par exemple, si nous voulons ajouter une nouvelle cotisation d'un mois,
voici ce que nous ajouterons :
```python title="settings.py"
from django.utils.translation import gettext_lazy as _
SITH_SUBSCRIPTIONS = {
# Voici un échantillon de la véritable configuration à l'heure de l'écriture.
# Celle-ci est donnée à titre d'exemple pour mieux comprendre comment cela fonctionne.
"un-semestre": {"name": _("One semester"), "price": 15, "duration": 1},
"deux-semestres": {"name": _("Two semesters"), "price": 28, "duration": 2},
"cursus-tronc-commun": {
"name": _("Common core cursus"),
"price": 45,
"duration": 4,
},
"cursus-branche": {"name": _("Branch cursus"), "price": 45, "duration": 6},
"cursus-alternant": {"name": _("Alternating cursus"), "price": 30, "duration": 6},
"membre-honoraire": {"name": _("Honorary member"), "price": 0, "duration": 666},
"un-jour": {"name": _("One day"), "price": 0, "duration": 0.00555333},
# On rajoute ici notre cotisation
# Elle se nomme "Un mois"
# Coûte 6€
# Dure 1 mois (on raisonne en semestre, ici, c'est 1/6 de semestre)
"un-mois": {"name": _("One month"), "price": 6, "duration": 0.166}
}
```
Une fois ceci fait, il faut créer une nouvelle migration :
```bash
python ./manage.py makemigrations subscription
python ./manage.py migrate
```
N'oubliez pas non plus les traductions (cf. [ici](./translation.md))

84
docs/howto/terminal.md Normal file
View File

@ -0,0 +1,84 @@
## Quel terminal utiliser ?
Quel que soit votre configuration, si vous avez réussi à installer
le projet, il y a de fortes chances que bash existe sur
votre ordinateur.
Certains d'entre vous utilisent peut-être un autre shell,
comme `zsh`.
En effet, `bash` est bien, il fait le taff ;
mais son ergonomie finit par montrer ses limites.
C'est pourquoi il existe des shells plus avancés,
qui peuvent améliorer l'ergonomie, la complétion des commandes,
et l'apparence.
C'est le cas de `zsh`.
Certains vont même plus loin et refont carrément la syntaxe.
C'est le cas de `nu`.
Pour choisir un terminal, demandez-vous juste quel
est votre usage du terminal :
- Si c'est juste quelques commandes basiques et
que vous ne voulez pas vous embêter à changer
votre configuration, `bash` convient parfaitement.
- Si vous commencez à utilisez le terminal
de manière plus intensive, à varier les commandes
que vous utilisez et/ou que vous voulez customiser
un peu votre expérience, `zsh` est parfait pour vous.
- Si vous aimez la programmation fonctionnelle,
que vous adorez les pipes et que vous voulez faire
des scripts complets mais qui restent lisibles,
`nu` vous plaira à coup sûr.
!!! note
Ce ne sont que des suggestions.
Le meilleur choix restera toujours celui
avec lequel vous êtes le plus confortable.
## Commandes utiles
### Compter le nombre de lignes du projet
=== "bash/zsh"
```bash
sudo apt install cloc
cloc --exclude-dir=doc,env .
```
Ok, c'est de la triche, on installe un package externe.
Mais bon, ça marche, et l'équivalent pur bash
serait carrément plus moche.
=== "nu"
Nombre de lignes, groupé par fichier :
```nu
ls **/*.py | insert linecount { get name | open | lines | length }
```
Nombre de lignes total :
```nu
ls **/*.py | insert linecount { get name | open | lines | length } | math sum
```
Vous pouvez aussi exlure les lignes vides
et les les lignes de commentaire :
```nu
ls **/*.py |
insert linecount {
get name |
open |
lines |
each { str trim } |
filter { |l| not ($l | str starts-with "#") } | # commentaires
filter { |l| ($l | str length) > 0 } | # lignes vides
length
} |
math sum
```

71
docs/howto/translation.md Normal file
View File

@ -0,0 +1,71 @@
Le code du site est entièrement écrit en anglais,
le texte affiché aux utilisateurs l'est également.
La traduction en français se fait
ultérieurement avec un fichier de traduction.
Voici un petit guide rapide pour apprendre à s'en servir.
## Dans le code du logiciel
Imaginons que nous souhaitons afficher "Hello"
et le traduire en français.
Voici comment signaler que ce mot doit être traduit.
Si le mot est dans le code Python :
```python
from django.utils.translation import gettext as _
help_text=_("Hello")
```
Si le mot apparaît dans le template Jinja :
```jinja
{% trans %}Hello{% endtrans %}
```
## Générer le fichier django.po
La traduction se fait en trois étapes.
Il faut d'abord générer un fichier de traductions,
l'éditer et enfin le compiler au format binaire pour qu'il soit lu par le serveur.
```bash
./manage.py makemessages --locale=fr --ignore "env/*" -e py,jinja
```
## Éditer le fichier django.po
```locale
# locale/fr/LC_MESSAGES/django.po
# ...
msgid "Hello"
msgstr "" # Ligne à modifier
# ...
```
!!!note
Si les commentaires suivants apparaissent, pensez à les supprimer.
Ils peuvent gêner votre traduction.
```
#, fuzzy
#| msgid "Bonjour"
```
## Générer le fichier django.mo
Il s'agit de la dernière étape.
Un fichier binaire est généré à partir du fichier django.mo.
```bash
./manage.py compilemessages
```
!!!tip
Pensez à redémarrer le serveur si les traductions ne s'affichent pas

71
docs/howto/weekmail.md Normal file
View File

@ -0,0 +1,71 @@
Le site est capable de générer des mails automatiques
contenant lagrégation d'articles écrits par les administrateurs de clubs.
Le contenu est inséré dans un template standardisé
et contrôlé directement dans le code.
Il arrive régulièrement que l'équipe communication souhaite modifier ce template.
Que ce soient les couleurs,
l'agencement ou encore la bannière ou le footer,
voici tout ce qu'il y a à savoir sur le fonctionnement
du weekmail en commençant par la classe qui le contrôle.
## Modifier la bannière et le footer
Ces éléments sont contrôlés par les méthodes `get_banner` et `get_footer`
de la classe `Weekmail`.
Les modifier est donc très simple,
il suffit de modifier le contenu de la fonction et
de rajouter les nouvelles images dans les statics.
Les images sont à ajouter dans `core/static/com/img`
et sont à nommer selon le type (banner ou footer), le semestre (Automne ou Printemps) et l'année.
Exemple : `weekmail_bannerA18.jpg` pour la bannière de l'automne 2018.
```python
from django.conf import settings
from django.templatetags.static import static
# Sélectionnez le fichier de bannière pour le weekmail de l'automne 2018
def get_banner(self):
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg")
```
!!!note
Penser à prendre les images au format **jpg** et à les compresser un peu,
pour qu'elles soient le plus léger possible,
c'est bien mieux pour l'utilisateur final.
!!!warning
Pensez à laisser les anciennes images dans le dossier
pour que les anciens weekmails ne soient pas affectés par les changements.
## Modifier le template
Il existe deux templates différents :
- Un en texte pur, qui sert pour le rendu dégradé des lecteurs
de mails ne supportant pas le HTML
- un qui fait du rendu html.
Ces deux templates sont respectivement accessibles aux emplacements suivants :
- `com/templates/com/weekmail_renderer_html.jinja`
- `com/templates/com/weekmail_renderer_text.jinja`
!!!note
Pour le rendu HTML, pensez à utiliser le CSS et le javascript
le plus simple possible pour que le rendu se fasse correctement
dans les clients mails qui sont souvent capricieux.
!!!note
Le CSS est inclus statiquement pour que toute modification
ultérieure de celui-ci n'affecte pas les versions précédemment envoyées.
!!!warning
Si vous souhaitez ajouter du contenu,
n'oubliez pas de bien inclure ce contenu dans les deux templates.