From 181e74b1d14343563129f11e25633b961ec33279 Mon Sep 17 00:00:00 2001 From: Sli Date: Fri, 2 Aug 2024 23:50:55 +0200 Subject: [PATCH 01/27] Add antispam app * update_spam_database command to update suspicious domains from an external provider * Add a AntiSpamEmailField that deny emails from suspicious domains * Update documentation --- .gitignore | 2 +- antispam/__init__.py | 0 antispam/admin.py | 10 + antispam/apps.py | 7 + antispam/forms.py | 18 + antispam/management/commands/__init__.py | 0 .../commands/update_spam_database.py | 69 ++ antispam/migrations/0001_initial.py | 35 + antispam/migrations/__init__.py | 0 antispam/models.py | 19 + core/tests.py | 21 +- core/views/forms.py | 6 +- docs/reference/antispam/forms.md | 1 + docs/reference/antispam/models.md | 1 + docs/tutorial/install_advanced.md | 10 + docs/tutorial/structure.md | 25 +- locale/fr/LC_MESSAGES/django.po | 661 ++++++++++-------- mkdocs.yml | 4 + sith/settings.py | 8 +- 19 files changed, 569 insertions(+), 328 deletions(-) create mode 100644 antispam/__init__.py create mode 100644 antispam/admin.py create mode 100644 antispam/apps.py create mode 100644 antispam/forms.py create mode 100644 antispam/management/commands/__init__.py create mode 100644 antispam/management/commands/update_spam_database.py create mode 100644 antispam/migrations/0001_initial.py create mode 100644 antispam/migrations/__init__.py create mode 100644 antispam/models.py create mode 100644 docs/reference/antispam/forms.md create mode 100644 docs/reference/antispam/models.md create mode 100644 docs/tutorial/install_advanced.md diff --git a/.gitignore b/.gitignore index e5651bb7..dbe81f32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -db.sqlite3 +*.sqlite3 *.log *.pyc *.mo diff --git a/antispam/__init__.py b/antispam/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/antispam/admin.py b/antispam/admin.py new file mode 100644 index 00000000..19dd817a --- /dev/null +++ b/antispam/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from antispam.models import ToxicDomain + + +@admin.register(ToxicDomain) +class ToxicDomainAdmin(admin.ModelAdmin): + list_display = ("domain", "is_externally_managed", "created") + search_fields = ("domain", "is_externally_managed", "created") + list_filter = ("is_externally_managed",) diff --git a/antispam/apps.py b/antispam/apps.py new file mode 100644 index 00000000..8a4857d1 --- /dev/null +++ b/antispam/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class AntispamConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + verbose_name = "antispam" + name = "antispam" diff --git a/antispam/forms.py b/antispam/forms.py new file mode 100644 index 00000000..a2f97124 --- /dev/null +++ b/antispam/forms.py @@ -0,0 +1,18 @@ +import re + +from django import forms +from django.core.validators import EmailValidator +from django.utils.translation import gettext_lazy as _ + +from antispam.models import ToxicDomain + + +class AntiSpamEmailField(forms.EmailField): + """An email field that email addresses with a known toxic domain.""" + + def run_validators(self, value: str): + super().run_validators(value) + # Domain part should exist since email validation is guaranteed to run first + domain = re.search(EmailValidator.domain_regex, value) + if ToxicDomain.objects.filter(domain=domain[0]).exists(): + raise forms.ValidationError(_("Email domain is not allowed.")) diff --git a/antispam/management/commands/__init__.py b/antispam/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/antispam/management/commands/update_spam_database.py b/antispam/management/commands/update_spam_database.py new file mode 100644 index 00000000..4e9d0ec8 --- /dev/null +++ b/antispam/management/commands/update_spam_database.py @@ -0,0 +1,69 @@ +import requests +from django.conf import settings +from django.core.management import BaseCommand +from django.db.models import Max +from django.utils import timezone + +from antispam.models import ToxicDomain + + +class Command(BaseCommand): + """Update blocked ips/mails database""" + + help = "Update blocked ips/mails database" + + def add_arguments(self, parser): + parser.add_argument( + "--force", action="store_true", help="Force re-creation even if up to date" + ) + + def _should_update(self, *, force: bool = False) -> bool: + if force: + return True + oldest = ToxicDomain.objects.filter(is_externally_managed=True).aggregate( + res=Max("created") + )["res"] + return not (oldest and timezone.now() < (oldest + timezone.timedelta(days=1))) + + def _download_domains(self, providers: list[str]) -> set[str]: + domains = set() + for provider in providers: + res = requests.get(provider) + if not res.ok: + self.stderr.write( + f"Source {provider} responded with code {res.status_code}" + ) + continue + domains |= set(res.content.decode().splitlines()) + return domains + + def _update_domains(self, domains: set[str]): + # Cleanup database + ToxicDomain.objects.filter(is_externally_managed=True).delete() + + # Create database + ToxicDomain.objects.bulk_create( + [ + ToxicDomain(domain=domain, is_externally_managed=True) + for domain in domains + ], + ignore_conflicts=True, + ) + self.stdout.write("Domain database updated") + + def handle(self, *args, **options): + if not self._should_update(force=options["force"]): + self.stdout.write("Domain database is up to date") + return + self.stdout.write("Updating domain database") + + domains = self._download_domains(settings.TOXIC_DOMAINS_PROVIDERS) + + if not domains: + self.stderr.write( + "No domains could be fetched from settings.TOXIC_DOMAINS_PROVIDERS. " + "Please, have a look at your settings." + ) + return + + self._update_domains(domains) diff --git a/antispam/migrations/0001_initial.py b/antispam/migrations/0001_initial.py new file mode 100644 index 00000000..80aeaaac --- /dev/null +++ b/antispam/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.14 on 2024-08-03 23:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ToxicDomain", + fields=[ + ( + "domain", + models.URLField( + max_length=253, + primary_key=True, + serialize=False, + verbose_name="domain", + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "is_externally_managed", + models.BooleanField( + default=False, + help_text="True if kept up-to-date using external toxic domain providers, else False", + verbose_name="is externally managed", + ), + ), + ], + ), + ] diff --git a/antispam/migrations/__init__.py b/antispam/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/antispam/models.py b/antispam/models.py new file mode 100644 index 00000000..44c81d1b --- /dev/null +++ b/antispam/models.py @@ -0,0 +1,19 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class ToxicDomain(models.Model): + """Domain marked as spam in public databases""" + + domain = models.URLField(_("domain"), max_length=253, primary_key=True) + created = models.DateTimeField(auto_now_add=True) + is_externally_managed = models.BooleanField( + _("is externally managed"), + default=False, + help_text=_( + "True if kept up-to-date using external toxic domain providers, else False" + ), + ) + + def __str__(self) -> str: + return self.domain diff --git a/core/tests.py b/core/tests.py index 1377a5ae..6c70521d 100644 --- a/core/tests.py +++ b/core/tests.py @@ -24,8 +24,10 @@ from django.core.mail import EmailMessage from django.test import Client, TestCase from django.urls import reverse from django.utils.timezone import now +from model_bakery import baker from pytest_django.asserts import assertInHTML, assertRedirects +from antispam.models import ToxicDomain from club.models import Membership from core.markdown import markdown from core.models import AnonymousUser, Group, Page, User @@ -48,6 +50,10 @@ class TestUserRegistration: "captcha_1": "PASSED", } + @pytest.fixture() + def scam_domains(self): + return [baker.make(ToxicDomain, domain="scammer.spam")] + def test_register_user_form_ok(self, client, valid_payload): """Should register a user correctly.""" assert not User.objects.filter(email=valid_payload["email"]).exists() @@ -64,14 +70,25 @@ class TestUserRegistration: {"password2": "not the same as password1"}, "Les deux mots de passe ne correspondent pas.", ), - ({"email": "not-an-email"}, "Saisissez une adresse de courriel valide."), + ( + {"email": "not-an-email"}, + "Saisissez une adresse de courriel valide.", + ), + ( + {"email": "not\\an@email.com"}, + "Saisissez une adresse de courriel valide.", + ), + ( + {"email": "legit@scammer.spam"}, + "Le domaine de l'addresse e-mail n'est pas autorisé.", + ), ({"first_name": ""}, "Ce champ est obligatoire."), ({"last_name": ""}, "Ce champ est obligatoire."), ({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"), ], ) def test_register_user_form_fail( - self, client, valid_payload, payload_edit, expected_error + self, client, scam_domains, valid_payload, payload_edit, expected_error ): """Should not register a user correctly.""" payload = valid_payload | payload_edit diff --git a/core/views/forms.py b/core/views/forms.py index 93feffe9..4408a057 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -16,7 +16,7 @@ # details. # # You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place - Suite 330, Boston, MA 02111-1307, USA. # # @@ -45,6 +45,7 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.widgets import RegionalPhoneNumberWidget from PIL import Image +from antispam.forms import AntiSpamEmailField from core.models import Gift, Page, SithFile, User from core.utils import resize_image @@ -194,6 +195,9 @@ class RegisteringForm(UserCreationForm): class Meta: model = User fields = ("first_name", "last_name", "email") + field_classes = { + "email": AntiSpamEmailField, + } class UserProfileForm(forms.ModelForm): diff --git a/docs/reference/antispam/forms.md b/docs/reference/antispam/forms.md new file mode 100644 index 00000000..49cc4400 --- /dev/null +++ b/docs/reference/antispam/forms.md @@ -0,0 +1 @@ +::: antispam.forms \ No newline at end of file diff --git a/docs/reference/antispam/models.md b/docs/reference/antispam/models.md new file mode 100644 index 00000000..a3c2c757 --- /dev/null +++ b/docs/reference/antispam/models.md @@ -0,0 +1 @@ +::: antispam.models \ No newline at end of file diff --git a/docs/tutorial/install_advanced.md b/docs/tutorial/install_advanced.md new file mode 100644 index 00000000..a5f2261e --- /dev/null +++ b/docs/tutorial/install_advanced.md @@ -0,0 +1,10 @@ +## Mettre à jour la base de données antispam + +L'anti spam nécessite d'être à jour par rapport à des bases de données externe. +Il existe une commande pour ça qu'il faut lancer régulièrement. +Lors de la mise en production, il est judicieux de configurer +un cron pour la mettre à jour au moins une fois par jour. + +```bash +python manage.py update_spam_database +``` diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index 9f96663f..465dfc6c 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -64,17 +64,19 @@ sith3/ │ └── ... ├── trombi/ (22) │ └── ... +├── antispam/ (23) +│ └── ... │ -├── .coveragerc (23) -├── .envrc (24) +├── .coveragerc (24) +├── .envrc (25) ├── .gitattributes ├── .gitignore ├── .mailmap ├── .env.exemple -├── manage.py (25) -├── mkdocs.yml (26) +├── manage.py (26) +├── mkdocs.yml (27) ├── poetry.lock -├── pyproject.toml (27) +├── pyproject.toml (28) └── README.md ``` @@ -112,15 +114,16 @@ sith3/ 19. Application principale du projet, contenant sa configuration. 20. Gestion des stocks des comptoirs. 21. Gestion des cotisations des utilisateurs du site. -22. Gestion des trombinoscopes. -23. Fichier de configuration de coverage. -24. Fichier de configuration de direnv. -25. Fichier généré automatiquement par Django. C'est lui +22. Fonctionalitées pour gérer le spam. +23. Gestion des trombinoscopes. +24. Fichier de configuration de coverage. +25. Fichier de configuration de direnv. +26. Fichier généré automatiquement par Django. C'est lui qui permet d'appeler des commandes de gestion du projet avec la syntaxe `python ./manage.py ` -26. Le fichier de configuration de la documentation, +27. Le fichier de configuration de la documentation, avec ses plugins et sa table des matières. -27. Le fichier où sont déclarés les dépendances et la configuration +28. Le fichier où sont déclarés les dépendances et la configuration de certaines d'entre elles. diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index def786ea..614745db 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-25 07:16+0200\n" +"POT-Creation-Date: 2024-08-04 01:05+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -41,7 +41,7 @@ msgstr "code postal" msgid "country" msgstr "pays" -#: accounting/models.py:55 core/models.py:364 +#: accounting/models.py:55 core/models.py:363 msgid "phone" msgstr "téléphone" @@ -127,8 +127,8 @@ msgstr "numéro" msgid "journal" msgstr "classeur" -#: accounting/models.py:261 core/models.py:902 core/models.py:1438 -#: core/models.py:1483 core/models.py:1512 core/models.py:1536 +#: accounting/models.py:261 core/models.py:903 core/models.py:1439 +#: core/models.py:1484 core/models.py:1513 core/models.py:1537 #: counter/models.py:571 counter/models.py:664 counter/models.py:877 #: eboutic/models.py:55 eboutic/models.py:214 forum/models.py:311 #: forum/models.py:412 stock/models.py:96 @@ -166,7 +166,7 @@ msgid "accounting type" msgstr "type comptable" #: accounting/models.py:299 accounting/models.py:438 accounting/models.py:471 -#: accounting/models.py:503 core/models.py:1511 core/models.py:1537 +#: accounting/models.py:503 core/models.py:1512 core/models.py:1538 #: counter/models.py:630 msgid "label" msgstr "étiquette" @@ -211,7 +211,7 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:310 core/views/user.py:278 +#: accounting/models.py:310 core/views/user.py:280 msgid "Account" msgstr "Compte" @@ -219,7 +219,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:312 core/models.py:311 sith/settings.py:405 +#: accounting/models.py:312 core/models.py:310 sith/settings.py:403 #: stock/templates/stock/shopping_list_items.jinja:37 msgid "Other" msgstr "Autre" @@ -379,11 +379,11 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:217 pedagogy/templates/pedagogy/guide.jinja:94 -#: pedagogy/templates/pedagogy/guide.jinja:109 +#: launderette/views.py:217 pedagogy/templates/pedagogy/guide.jinja:99 +#: pedagogy/templates/pedagogy/guide.jinja:114 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 #: sas/templates/sas/album.jinja:37 sas/templates/sas/main.jinja:63 -#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:64 +#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:73 #: stock/templates/stock/stock_shopping_list.jinja:43 #: stock/templates/stock/stock_shopping_list.jinja:69 #: trombi/templates/trombi/detail.jinja:35 @@ -392,7 +392,7 @@ msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:79 core/views/user.py:197 sas/templates/sas/picture.jinja:79 +#: club/views.py:79 core/views/user.py:199 sas/templates/sas/picture.jinja:88 msgid "Infos" msgstr "Infos" @@ -426,7 +426,7 @@ msgstr "Nouveau compte club" #: com/templates/com/weekmail.jinja:61 core/templates/core/file.jinja:38 #: core/templates/core/group_list.jinja:24 core/templates/core/page.jinja:35 #: core/templates/core/poster_list.jinja:40 -#: core/templates/core/user_tools.jinja:71 core/views/user.py:227 +#: core/templates/core/user_tools.jinja:71 core/views/user.py:229 #: counter/templates/counter/cash_summary_list.jinja:53 #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 @@ -435,8 +435,8 @@ msgstr "Nouveau compte club" #: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:62 #: forum/templates/forum/macros.jinja:128 #: launderette/templates/launderette/launderette_list.jinja:16 -#: pedagogy/templates/pedagogy/guide.jinja:93 -#: pedagogy/templates/pedagogy/guide.jinja:108 +#: pedagogy/templates/pedagogy/guide.jinja:98 +#: pedagogy/templates/pedagogy/guide.jinja:113 #: pedagogy/templates/pedagogy/uv_detail.jinja:184 #: sas/templates/sas/album.jinja:36 trombi/templates/trombi/detail.jinja:9 #: trombi/templates/trombi/edit_profile.jinja:34 @@ -528,7 +528,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:451 +#: sith/settings.py:449 msgid "Closed" msgstr "Fermé" @@ -627,7 +627,7 @@ msgstr "No" #: counter/templates/counter/last_ops.jinja:20 #: counter/templates/counter/last_ops.jinja:45 #: counter/templates/counter/refilling_list.jinja:16 -#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:357 +#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:351 #: stock/templates/stock/stock_shopping_list.jinja:25 #: stock/templates/stock/stock_shopping_list.jinja:54 #: trombi/templates/trombi/user_profile.jinja:40 @@ -651,7 +651,7 @@ msgid "Target" msgstr "Cible" #: accounting/templates/accounting/journal_details.jinja:38 -#: core/views/forms.py:86 +#: core/views/forms.py:87 msgid "Code" msgstr "Code" @@ -902,6 +902,23 @@ msgstr "Opérations sans étiquette" msgid "Refound this account" msgstr "Rembourser ce compte" +#: antispam/forms.py:16 +msgid "Email domain is not allowed." +msgstr "Le domaine de l'addresse e-mail n'est pas autorisé." + +#: antispam/models.py:8 +msgid "domain" +msgstr "domaine" + +#: antispam/models.py:11 +msgid "is externally managed" +msgstr "est géré de manière externe" + +#: antispam/models.py:14 +msgid "" +"True if kept up-to-date using external toxic domain providers, else False" +msgstr "True si gardé à jour par le biais d'un fournisseur externe de domains toxics, False sinon" + #: club/forms.py:55 club/forms.py:185 msgid "Users to add" msgstr "Utilisateurs à ajouter" @@ -992,7 +1009,7 @@ msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:283 sas/views.py:118 sas/views.py:185 sas/views.py:284 +#: club/forms.py:283 sas/views.py:118 sas/views.py:179 sas/views.py:278 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" @@ -1024,11 +1041,11 @@ msgstr "actif" msgid "short description" msgstr "description courte" -#: club/models.py:78 core/models.py:366 +#: club/models.py:78 core/models.py:365 msgid "address" msgstr "Adresse" -#: club/models.py:95 core/models.py:277 +#: club/models.py:95 core/models.py:276 msgid "home" msgstr "home" @@ -1042,17 +1059,17 @@ msgstr "Un club avec ce nom UNIX existe déjà." #: club/models.py:336 counter/models.py:832 counter/models.py:868 #: eboutic/models.py:51 eboutic/models.py:210 election/models.py:183 -#: launderette/models.py:136 launderette/models.py:198 sas/models.py:228 +#: launderette/models.py:136 launderette/models.py:198 sas/models.py:223 #: trombi/models.py:206 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:353 core/models.py:330 election/models.py:178 +#: club/models.py:353 core/models.py:329 election/models.py:178 #: election/models.py:212 trombi/models.py:211 msgid "role" msgstr "rôle" -#: club/models.py:358 core/models.py:88 counter/models.py:209 +#: club/models.py:358 core/models.py:87 counter/models.py:209 #: counter/models.py:240 election/models.py:13 election/models.py:115 #: election/models.py:188 forum/models.py:60 forum/models.py:244 msgid "description" @@ -1067,7 +1084,7 @@ msgid "Enter a valid address. Only the root of the address is needed." msgstr "" "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." -#: club/models.py:428 com/models.py:84 com/models.py:311 core/models.py:903 +#: club/models.py:428 com/models.py:84 com/models.py:311 core/models.py:904 msgid "is moderated" msgstr "est modéré" @@ -1145,7 +1162,7 @@ msgid "There are no members in this club." msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:80 -#: core/templates/core/file_detail.jinja:19 core/views/forms.py:328 +#: core/templates/core/file_detail.jinja:19 core/views/forms.py:332 #: launderette/views.py:217 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1353,8 +1370,8 @@ msgstr "Anciens membres" msgid "History" msgstr "Historique" -#: club/views.py:116 core/templates/core/base.jinja:95 core/views/user.py:220 -#: sas/templates/sas/picture.jinja:100 trombi/views.py:61 +#: club/views.py:116 core/templates/core/base.jinja:95 core/views/user.py:222 +#: sas/templates/sas/picture.jinja:109 trombi/views.py:61 msgid "Tools" msgstr "Outils" @@ -1424,7 +1441,7 @@ msgstr "résumé" msgid "content" msgstr "contenu" -#: com/models.py:73 core/models.py:1481 launderette/models.py:92 +#: com/models.py:73 core/models.py:1482 launderette/models.py:92 #: launderette/models.py:130 launderette/models.py:181 stock/models.py:74 #: stock/models.py:129 msgid "type" @@ -1475,7 +1492,7 @@ msgstr "weekmail" msgid "rank" msgstr "rang" -#: com/models.py:297 core/models.py:868 core/models.py:918 +#: com/models.py:297 core/models.py:869 core/models.py:919 msgid "file" msgstr "fichier" @@ -1500,7 +1517,7 @@ msgstr "Administration des mailing listes" #: com/templates/com/news_detail.jinja:39 #: core/templates/core/file_detail.jinja:65 #: core/templates/core/file_moderation.jinja:23 -#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:61 +#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:70 msgid "Moderate" msgstr "Modérer" @@ -1572,7 +1589,7 @@ msgstr "Type" #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 #: forum/templates/forum/forum.jinja:28 forum/templates/forum/forum.jinja:47 #: forum/templates/forum/main.jinja:30 forum/views.py:245 -#: pedagogy/templates/pedagogy/guide.jinja:87 +#: pedagogy/templates/pedagogy/guide.jinja:92 msgid "Title" msgstr "Titre" @@ -1662,7 +1679,7 @@ msgstr "Retour aux nouvelles" msgid "Author: " msgstr "Auteur : " -#: com/templates/com/news_detail.jinja:37 sas/templates/sas/picture.jinja:92 +#: com/templates/com/news_detail.jinja:37 sas/templates/sas/picture.jinja:101 msgid "Moderator: " msgstr "Modérateur : " @@ -1734,7 +1751,7 @@ msgstr "Anniversaires" msgid "%(age)s year old" msgstr "%(age)s ans" -#: com/templates/com/news_list.jinja:156 com/tests.py:101 com/tests.py:111 +#: com/templates/com/news_list.jinja:156 com/tests.py:103 com/tests.py:113 msgid "You need an up to date subscription to access this content" msgstr "Votre cotisation doit être à jour pour accéder à cette section" @@ -1960,30 +1977,30 @@ msgstr "" "Vous devez êtres un membre du bureau du club sélectionné pour poster dans le " "Weekmail." -#: core/models.py:83 +#: core/models.py:82 msgid "meta group status" msgstr "status du meta-groupe" -#: core/models.py:85 +#: core/models.py:84 msgid "Whether a group is a meta group or not" msgstr "Si un groupe est un meta-groupe ou pas" -#: core/models.py:171 +#: core/models.py:170 #, python-format msgid "%(value)s is not a valid promo (between 0 and %(end)s)" msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)" -#: core/models.py:230 +#: core/models.py:229 msgid "username" msgstr "nom d'utilisateur" -#: core/models.py:234 +#: core/models.py:233 msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." msgstr "" "Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:240 +#: core/models.py:239 msgid "" "Enter a valid username. This value may contain only letters, numbers and ./" "+/-/_ characters." @@ -1991,43 +2008,43 @@ msgstr "" "Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:246 +#: core/models.py:245 msgid "A user with that username already exists." msgstr "Un utilisateur de ce nom existe déjà" -#: core/models.py:248 +#: core/models.py:247 msgid "first name" msgstr "Prénom" -#: core/models.py:249 +#: core/models.py:248 msgid "last name" msgstr "Nom" -#: core/models.py:250 +#: core/models.py:249 msgid "email address" msgstr "adresse email" -#: core/models.py:251 +#: core/models.py:250 msgid "date of birth" msgstr "date de naissance" -#: core/models.py:252 +#: core/models.py:251 msgid "nick name" msgstr "surnom" -#: core/models.py:254 +#: core/models.py:253 msgid "staff status" msgstr "status \"staff\"" -#: core/models.py:256 +#: core/models.py:255 msgid "Designates whether the user can log into this admin site." msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site." -#: core/models.py:259 +#: core/models.py:258 msgid "active" msgstr "actif" -#: core/models.py:262 +#: core/models.py:261 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -2035,163 +2052,163 @@ msgstr "" "Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "lieu de supprimer les comptes." -#: core/models.py:266 +#: core/models.py:265 msgid "date joined" msgstr "date d'inscription" -#: core/models.py:267 +#: core/models.py:266 msgid "last update" msgstr "dernière mise à jour" -#: core/models.py:269 +#: core/models.py:268 msgid "superuser" msgstr "super-utilisateur" -#: core/models.py:271 +#: core/models.py:270 msgid "Designates whether this user is a superuser. " msgstr "Est-ce que l'utilisateur est super-utilisateur." -#: core/models.py:285 +#: core/models.py:284 msgid "profile" msgstr "profil" -#: core/models.py:293 +#: core/models.py:292 msgid "avatar" msgstr "avatar" -#: core/models.py:301 +#: core/models.py:300 msgid "scrub" msgstr "blouse" -#: core/models.py:307 +#: core/models.py:306 msgid "sex" msgstr "Genre" -#: core/models.py:311 +#: core/models.py:310 msgid "Man" msgstr "Homme" -#: core/models.py:311 +#: core/models.py:310 msgid "Woman" msgstr "Femme" -#: core/models.py:313 +#: core/models.py:312 msgid "pronouns" msgstr "pronoms" -#: core/models.py:315 +#: core/models.py:314 msgid "tshirt size" msgstr "taille de t-shirt" -#: core/models.py:318 +#: core/models.py:317 msgid "-" msgstr "-" -#: core/models.py:319 +#: core/models.py:318 msgid "XS" msgstr "XS" -#: core/models.py:320 +#: core/models.py:319 msgid "S" msgstr "S" -#: core/models.py:321 +#: core/models.py:320 msgid "M" msgstr "M" -#: core/models.py:322 +#: core/models.py:321 msgid "L" msgstr "L" -#: core/models.py:323 +#: core/models.py:322 msgid "XL" msgstr "XL" -#: core/models.py:324 +#: core/models.py:323 msgid "XXL" msgstr "XXL" -#: core/models.py:325 +#: core/models.py:324 msgid "XXXL" msgstr "XXXL" -#: core/models.py:333 +#: core/models.py:332 msgid "Student" msgstr "Étudiant" -#: core/models.py:334 +#: core/models.py:333 msgid "Administrative agent" msgstr "Personnel administratif" -#: core/models.py:335 +#: core/models.py:334 msgid "Teacher" msgstr "Enseignant" -#: core/models.py:336 +#: core/models.py:335 msgid "Agent" msgstr "Personnel" -#: core/models.py:337 +#: core/models.py:336 msgid "Doctor" msgstr "Doctorant" -#: core/models.py:338 +#: core/models.py:337 msgid "Former student" msgstr "Ancien étudiant" -#: core/models.py:339 +#: core/models.py:338 msgid "Service" msgstr "Service" -#: core/models.py:345 +#: core/models.py:344 msgid "department" msgstr "département" -#: core/models.py:352 +#: core/models.py:351 msgid "dpt option" msgstr "Filière" -#: core/models.py:354 pedagogy/models.py:69 pedagogy/models.py:293 +#: core/models.py:353 pedagogy/models.py:69 pedagogy/models.py:293 msgid "semester" msgstr "semestre" -#: core/models.py:355 +#: core/models.py:354 msgid "quote" msgstr "citation" -#: core/models.py:356 +#: core/models.py:355 msgid "school" msgstr "école" -#: core/models.py:358 +#: core/models.py:357 msgid "promo" msgstr "promo" -#: core/models.py:361 +#: core/models.py:360 msgid "forum signature" msgstr "signature du forum" -#: core/models.py:363 +#: core/models.py:362 msgid "second email address" msgstr "adresse email secondaire" -#: core/models.py:365 +#: core/models.py:364 msgid "parent phone" msgstr "téléphone des parents" -#: core/models.py:368 +#: core/models.py:367 msgid "parent address" msgstr "adresse des parents" -#: core/models.py:371 +#: core/models.py:370 msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:569 +#: core/models.py:570 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:699 core/templates/core/macros.jinja:75 +#: core/models.py:700 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/user_detail.jinja:104 #: core/templates/core/user_detail.jinja:105 @@ -2220,101 +2237,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:818 +#: core/models.py:819 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:825 +#: core/models.py:826 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:826 +#: core/models.py:827 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:828 +#: core/models.py:829 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:831 +#: core/models.py:832 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:857 +#: core/models.py:858 msgid "file name" msgstr "nom du fichier" -#: core/models.py:861 core/models.py:1230 +#: core/models.py:862 core/models.py:1231 msgid "parent" msgstr "parent" -#: core/models.py:875 +#: core/models.py:876 msgid "compressed file" msgstr "version allégée" -#: core/models.py:882 +#: core/models.py:883 msgid "thumbnail" msgstr "miniature" -#: core/models.py:890 core/models.py:907 +#: core/models.py:891 core/models.py:908 msgid "owner" msgstr "propriétaire" -#: core/models.py:894 core/models.py:1247 core/views/files.py:222 +#: core/models.py:895 core/models.py:1248 core/views/files.py:221 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:897 core/models.py:1250 core/views/files.py:225 +#: core/models.py:898 core/models.py:1251 core/views/files.py:224 msgid "view group" msgstr "groupe de vue" -#: core/models.py:899 +#: core/models.py:900 msgid "is folder" msgstr "est un dossier" -#: core/models.py:900 +#: core/models.py:901 msgid "mime type" msgstr "type mime" -#: core/models.py:901 +#: core/models.py:902 msgid "size" msgstr "taille" -#: core/models.py:912 +#: core/models.py:913 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:914 +#: core/models.py:915 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:1008 +#: core/models.py:1009 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:1010 core/models.py:1014 +#: core/models.py:1011 core/models.py:1015 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:1017 +#: core/models.py:1018 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:1028 +#: core/models.py:1029 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:1045 +#: core/models.py:1046 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1213 +#: core/models.py:1214 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1219 +#: core/models.py:1220 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2322,55 +2339,55 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1237 +#: core/models.py:1238 msgid "page name" msgstr "nom de la page" -#: core/models.py:1242 +#: core/models.py:1243 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1255 +#: core/models.py:1256 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1262 +#: core/models.py:1263 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1312 +#: core/models.py:1313 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1315 +#: core/models.py:1316 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" -#: core/models.py:1435 +#: core/models.py:1436 msgid "revision" msgstr "révision" -#: core/models.py:1436 +#: core/models.py:1437 msgid "page title" msgstr "titre de la page" -#: core/models.py:1437 +#: core/models.py:1438 msgid "page content" msgstr "contenu de la page" -#: core/models.py:1478 +#: core/models.py:1479 msgid "url" msgstr "url" -#: core/models.py:1479 +#: core/models.py:1480 msgid "param" msgstr "param" -#: core/models.py:1484 +#: core/models.py:1485 msgid "viewed" msgstr "vue" -#: core/models.py:1542 +#: core/models.py:1543 msgid "operation type" msgstr "type d'opération" @@ -2474,7 +2491,7 @@ msgstr "Photos" #: eboutic/templates/eboutic/eboutic_main.jinja:23 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:404 sith/settings.py:412 +#: sith/settings.py:402 sith/settings.py:410 msgid "Eboutic" msgstr "Eboutic" @@ -2494,7 +2511,7 @@ msgid "Launderette" msgstr "Laverie" #: core/templates/core/base.jinja:224 core/templates/core/file.jinja:20 -#: core/views/files.py:108 +#: core/views/files.py:107 msgid "Files" msgstr "Fichiers" @@ -2581,7 +2598,7 @@ msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:14 -#: counter/templates/counter/counter_click.jinja:121 +#: counter/templates/counter/counter_click.jinja:122 msgid "Cancel" msgstr "Annuler" @@ -2614,7 +2631,7 @@ msgstr "Propriétés" #: core/templates/core/file_detail.jinja:13 #: core/templates/core/file_moderation.jinja:20 -#: sas/templates/sas/picture.jinja:86 +#: sas/templates/sas/picture.jinja:95 msgid "Owner: " msgstr "Propriétaire : " @@ -2642,7 +2659,7 @@ msgstr "Nom réel : " #: core/templates/core/file_detail.jinja:54 #: core/templates/core/file_moderation.jinja:21 -#: sas/templates/sas/picture.jinja:82 +#: sas/templates/sas/picture.jinja:91 msgid "Date: " msgstr "Date : " @@ -2773,7 +2790,6 @@ msgid "Tokens" msgstr "Jetons" #: core/templates/core/macros.jinja:123 core/templates/core/macros.jinja:125 -#: pedagogy/templates/pedagogy/guide.jinja:116 msgid "Previous" msgstr "Précédent" @@ -2782,7 +2798,6 @@ msgid "current" msgstr "actuel" #: core/templates/core/macros.jinja:135 core/templates/core/macros.jinja:137 -#: pedagogy/templates/pedagogy/guide.jinja:120 msgid "Next" msgstr "Suivant" @@ -2937,20 +2952,30 @@ msgid "Password reset sent" msgstr "Réinitialisation de mot de passe envoyée" #: core/templates/core/password_reset_done.jinja:7 +#, fuzzy +#| msgid "" +#| "We've emailed you instructions for setting your password, if an account " +#| "exists with the email you entered. You should\n" +#| "receive them shortly." msgid "" "We've emailed you instructions for setting your password, if an account " "exists with the email you entered. You should\n" -"receive them shortly." +" receive them shortly." msgstr "" "Nous vous avons envoyé les instructions pour réinitialiser votre mot de " "passe par email, si un compte avec l'email entré existe effectivement.\n" "Vous devriez les recevoir rapidement." #: core/templates/core/password_reset_done.jinja:12 +#, fuzzy +#| msgid "" +#| "If you don't receive an email, please make sure you've entered the " +#| "address you registered with, and check your spam\n" +#| "folder." msgid "" "If you don't receive an email, please make sure you've entered the address " "you registered with, and check your spam\n" -"folder." +" folder." msgstr "" "Si vous ne recevez pas d'email, assurez-vous d'avoir correctement entré " "l'adresse email avec laquelle vous vous êtes inscrit, et vérifiez votre " @@ -2990,14 +3015,24 @@ msgstr "" "de l'Association des Étudiants de l'UTBM." #: core/templates/core/register_confirm_mail.jinja:6 +#, fuzzy +#| msgid "" +#| "\n" +#| "As this is the website of the students of the AE, by the students of the " +#| "AE,\n" +#| "for the students of the AE, you won't be able to do many things without " +#| "subscribing to the AE.\n" +#| "To make a contribution, contact a member of the association's board, " +#| "either directly or by email at ae@utbm.fr.\n" msgid "" "\n" -"As this is the website of the students of the AE, by the students of the " +" As this is the website of the students of the AE, by the students of the " "AE,\n" -"for the students of the AE, you won't be able to do many things without " +" for the students of the AE, you won't be able to do many things without " "subscribing to the AE.\n" -"To make a contribution, contact a member of the association's board, either " -"directly or by email at ae@utbm.fr.\n" +" To make a contribution, contact a member of the association's board, " +"either directly or by email at ae@utbm.fr.\n" +" " msgstr "" "\n" "Ceci étant le site des étudiants de l'AE, par les étudiants de l'AE, pour " @@ -3017,7 +3052,7 @@ msgstr "Résultat de la recherche" msgid "Users" msgstr "Utilisateurs" -#: core/templates/core/search.jinja:18 core/views/user.py:242 +#: core/templates/core/search.jinja:18 core/views/user.py:244 msgid "Clubs" msgstr "Clubs" @@ -3257,7 +3292,7 @@ msgstr "Voir l'arbre des ancêtres" msgid "No godfathers / godmothers" msgstr "Pas de famille" -#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:458 +#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:454 msgid "Godchildren" msgstr "Fillots / Fillotes" @@ -3317,25 +3352,25 @@ msgstr "Liste d'utilisateurs" msgid "%(user_name)s's pictures" msgstr "Photos de %(user_name)s" -#: core/templates/core/user_pictures.jinja:23 +#: core/templates/core/user_pictures.jinja:30 msgid "Download all my pictures" msgstr "Télécharger toutes mes photos" -#: core/templates/core/user_pictures.jinja:37 sas/templates/sas/album.jinja:68 +#: core/templates/core/user_pictures.jinja:48 sas/templates/sas/album.jinja:68 #: sas/templates/sas/album.jinja:96 msgid "To be moderated" msgstr "A modérer" -#: core/templates/core/user_pictures.jinja:46 +#: core/templates/core/user_pictures.jinja:57 msgid "Picture Unavailable" msgstr "Photo Indisponible" -#: core/templates/core/user_pictures.jinja:83 +#: core/templates/core/user_pictures.jinja:101 msgid "pictures" msgstr "photos" #: core/templates/core/user_preferences.jinja:8 -#: core/templates/core/user_preferences.jinja:13 core/views/user.py:234 +#: core/templates/core/user_preferences.jinja:13 core/views/user.py:236 msgid "Preferences" msgstr "Préférences" @@ -3375,8 +3410,8 @@ msgstr "Aucune carte étudiante enregistrée." msgid "" "You can add a card by asking at a counter or add it yourself here. If you " "want to manually\n" -" add a student card yourself, you'll need a NFC reader. " -"We store the UID of the card which is 14 characters long." +" add a student card yourself, you'll need a NFC reader. We store " +"the UID of the card which is 14 characters long." msgstr "" "Vous pouvez ajouter une carte en demandant à un comptoir ou en l'ajoutant " "vous même ici. Si vous voulez l'ajouter manuellement par vous même, vous " @@ -3417,7 +3452,7 @@ msgstr "Outils utilisateurs" msgid "Sith management" msgstr "Gestion de Sith" -#: core/templates/core/user_tools.jinja:21 core/views/user.py:250 +#: core/templates/core/user_tools.jinja:21 core/views/user.py:252 msgid "Groups" msgstr "Groupes" @@ -3472,7 +3507,7 @@ msgstr "Relevés de caisse" msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:72 core/views/user.py:269 +#: core/templates/core/user_tools.jinja:72 core/views/user.py:271 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:56 @@ -3532,12 +3567,12 @@ msgid "Moderate pictures" msgstr "Modérer les photos" #: core/templates/core/user_tools.jinja:173 -#: pedagogy/templates/pedagogy/guide.jinja:21 +#: pedagogy/templates/pedagogy/guide.jinja:25 msgid "Create UV" msgstr "Créer UV" #: core/templates/core/user_tools.jinja:174 -#: pedagogy/templates/pedagogy/guide.jinja:24 +#: pedagogy/templates/pedagogy/guide.jinja:28 #: trombi/templates/trombi/detail.jinja:10 msgid "Moderate comments" msgstr "Modérer les commentaires" @@ -3566,121 +3601,121 @@ msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" msgid "Trombi tools" msgstr "Outils Trombi" -#: core/templatetags/renderer.py:82 +#: core/templatetags/renderer.py:84 #, python-format msgid "%(nb_days)d day, %(remainder)s" msgid_plural "%(nb_days)d days, %(remainder)s" msgstr[0] "" msgstr[1] "" -#: core/views/files.py:105 +#: core/views/files.py:104 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" -#: core/views/files.py:125 +#: core/views/files.py:124 #, python-format msgid "Error creating folder %(folder_name)s: %(msg)s" msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s" -#: core/views/files.py:145 core/views/forms.py:293 core/views/forms.py:300 +#: core/views/files.py:144 core/views/forms.py:297 core/views/forms.py:304 #: sas/views.py:82 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" -#: core/views/files.py:227 sas/views.py:360 +#: core/views/files.py:226 sas/views.py:354 msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: core/views/forms.py:79 +#: core/views/forms.py:80 msgid "Heading" msgstr "Titre" -#: core/views/forms.py:80 +#: core/views/forms.py:81 msgid "Italic" msgstr "Italique" -#: core/views/forms.py:81 +#: core/views/forms.py:82 msgid "Bold" msgstr "Gras" -#: core/views/forms.py:82 +#: core/views/forms.py:83 msgid "Strikethrough" msgstr "Barré" -#: core/views/forms.py:83 +#: core/views/forms.py:84 msgid "Underline" msgstr "Souligné" -#: core/views/forms.py:84 +#: core/views/forms.py:85 msgid "Superscript" msgstr "Exposant" -#: core/views/forms.py:85 +#: core/views/forms.py:86 msgid "Subscript" msgstr "Indice" -#: core/views/forms.py:87 +#: core/views/forms.py:88 msgid "Quote" msgstr "Citation" -#: core/views/forms.py:88 +#: core/views/forms.py:89 msgid "Unordered list" msgstr "Liste non ordonnée" -#: core/views/forms.py:89 +#: core/views/forms.py:90 msgid "Ordered list" msgstr "Liste ordonnée" -#: core/views/forms.py:90 +#: core/views/forms.py:91 msgid "Insert image" msgstr "Insérer image" -#: core/views/forms.py:91 +#: core/views/forms.py:92 msgid "Insert link" msgstr "Insérer lien" -#: core/views/forms.py:92 +#: core/views/forms.py:93 msgid "Insert table" msgstr "Insérer tableau" -#: core/views/forms.py:93 +#: core/views/forms.py:94 msgid "Clean block" msgstr "Nettoyer bloc" -#: core/views/forms.py:94 +#: core/views/forms.py:95 msgid "Toggle preview" msgstr "Activer la prévisualisation" -#: core/views/forms.py:95 +#: core/views/forms.py:96 msgid "Toggle side by side" msgstr "Activer la vue côte à côte" -#: core/views/forms.py:96 +#: core/views/forms.py:97 msgid "Toggle fullscreen" msgstr "Activer le plein écran" -#: core/views/forms.py:97 +#: core/views/forms.py:98 msgid "Markdown guide" msgstr "Guide markdown" -#: core/views/forms.py:108 +#: core/views/forms.py:109 msgid "Unsupported NFC card" msgstr "Carte NFC non supportée" -#: core/views/forms.py:122 core/views/forms.py:130 +#: core/views/forms.py:123 core/views/forms.py:131 msgid "Choose file" msgstr "Choisir un fichier" -#: core/views/forms.py:146 core/views/forms.py:154 +#: core/views/forms.py:147 core/views/forms.py:155 msgid "Choose user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:186 +#: core/views/forms.py:187 msgid "Username, email, or account number" msgstr "Nom d'utilisateur, email, ou numéro de compte AE" -#: core/views/forms.py:245 +#: core/views/forms.py:249 msgid "" "Profile: you need to be visible on the picture, in order to be recognized (e." "g. by the barmen)" @@ -3688,36 +3723,36 @@ msgstr "" "Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "(par exemple par les barmen)" -#: core/views/forms.py:247 +#: core/views/forms.py:251 msgid "Avatar: used on the forum" msgstr "Avatar : utilisé sur le forum" -#: core/views/forms.py:248 +#: core/views/forms.py:252 msgid "Scrub: let other know how your scrub looks like!" msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !" -#: core/views/forms.py:304 +#: core/views/forms.py:308 msgid "Bad image format, only jpeg, png, and gif are accepted" msgstr "Mauvais format d'image, seuls les jpeg, png, et gif sont acceptés" -#: core/views/forms.py:325 +#: core/views/forms.py:329 msgid "Godfather / Godmother" msgstr "Parrain / Marraine" -#: core/views/forms.py:326 +#: core/views/forms.py:330 msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:331 counter/forms.py:67 trombi/views.py:149 +#: core/views/forms.py:335 counter/forms.py:67 trombi/views.py:149 msgid "Select user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:344 core/views/forms.py:362 election/models.py:22 +#: core/views/forms.py:348 core/views/forms.py:366 election/models.py:22 #: election/views.py:147 msgid "edit groups" msgstr "groupe d'édition" -#: core/views/forms.py:347 core/views/forms.py:365 election/models.py:29 +#: core/views/forms.py:351 core/views/forms.py:369 election/models.py:29 #: election/views.py:150 msgid "view groups" msgstr "groupe de vue" @@ -3730,25 +3765,25 @@ msgstr "Utilisateurs à retirer du groupe" msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/user.py:179 +#: core/views/user.py:181 msgid "We couldn't verify that this email actually exists" msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe." -#: core/views/user.py:202 core/views/user.py:460 core/views/user.py:462 +#: core/views/user.py:204 core/views/user.py:456 core/views/user.py:458 msgid "Family" msgstr "Famille" -#: core/views/user.py:207 sas/templates/sas/album.jinja:84 +#: core/views/user.py:209 sas/templates/sas/album.jinja:84 #: trombi/templates/trombi/export.jinja:25 #: trombi/templates/trombi/user_profile.jinja:11 msgid "Pictures" msgstr "Photos" -#: core/views/user.py:215 +#: core/views/user.py:217 msgid "Galaxy" msgstr "Galaxie" -#: core/views/user.py:599 +#: core/views/user.py:595 msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" @@ -3917,8 +3952,8 @@ msgstr "quantité" msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:668 sith/settings.py:397 sith/settings.py:402 -#: sith/settings.py:422 +#: counter/models.py:668 sith/settings.py:395 sith/settings.py:400 +#: sith/settings.py:420 msgid "Credit card" msgstr "Carte bancaire" @@ -4080,12 +4115,12 @@ msgid "This is not a valid student card UID" msgstr "Ce n'est pas un UID de carte étudiante valide" #: counter/templates/counter/counter_click.jinja:41 -#: counter/templates/counter/counter_click.jinja:67 -#: counter/templates/counter/counter_click.jinja:132 +#: counter/templates/counter/counter_click.jinja:68 +#: counter/templates/counter/counter_click.jinja:133 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 -#: sas/templates/sas/picture.jinja:140 +#: sas/templates/sas/picture.jinja:151 #: subscription/templates/subscription/stats.jinja:19 msgid "Go" msgstr "Valider" @@ -4094,25 +4129,25 @@ msgstr "Valider" msgid "Registered cards" msgstr "Cartes enregistrées" -#: counter/templates/counter/counter_click.jinja:51 +#: counter/templates/counter/counter_click.jinja:52 msgid "No card registered" msgstr "Aucune carte enregistrée" -#: counter/templates/counter/counter_click.jinja:56 +#: counter/templates/counter/counter_click.jinja:57 #: launderette/templates/launderette/launderette_admin.jinja:8 msgid "Selling" msgstr "Vente" -#: counter/templates/counter/counter_click.jinja:74 +#: counter/templates/counter/counter_click.jinja:75 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:20 msgid "Basket: " msgstr "Panier : " -#: counter/templates/counter/counter_click.jinja:115 +#: counter/templates/counter/counter_click.jinja:116 msgid "Finish" msgstr "Terminer" -#: counter/templates/counter/counter_click.jinja:125 +#: counter/templates/counter/counter_click.jinja:126 #: counter/templates/counter/refilling_list.jinja:9 msgid "Refilling" msgstr "Rechargement" @@ -4501,9 +4536,13 @@ msgid "Edit billing information" msgstr "Éditer les informations de facturation" #: eboutic/templates/eboutic/eboutic_makecommand.jinja:100 +#, fuzzy +#| msgid "" +#| "You must fill your billing infos if you want to pay with your credit\n" +#| " card" msgid "" "You must fill your billing infos if you want to pay with your credit\n" -" card" +" card" msgstr "" "Vous devez renseigner vos coordonnées de facturation si vous voulez payer " "par carte bancaire" @@ -4962,12 +5001,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:633 +#: sith/settings.py:631 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:633 +#: sith/settings.py:631 msgid "Drying" msgstr "Séchage" @@ -5163,30 +5202,30 @@ msgstr "signalant" msgid "reason" msgstr "raison" -#: pedagogy/templates/pedagogy/guide.jinja:5 +#: pedagogy/templates/pedagogy/guide.jinja:4 msgid "UV Guide" msgstr "Guide des UVs" -#: pedagogy/templates/pedagogy/guide.jinja:54 +#: pedagogy/templates/pedagogy/guide.jinja:59 #, python-format msgid "%(display_name)s" msgstr "%(display_name)s" -#: pedagogy/templates/pedagogy/guide.jinja:68 +#: pedagogy/templates/pedagogy/guide.jinja:73 #, python-format msgid "%(credit_type)s" msgstr "%(credit_type)s" -#: pedagogy/templates/pedagogy/guide.jinja:86 +#: pedagogy/templates/pedagogy/guide.jinja:91 #: pedagogy/templates/pedagogy/moderation.jinja:12 msgid "UV" msgstr "UE" -#: pedagogy/templates/pedagogy/guide.jinja:88 +#: pedagogy/templates/pedagogy/guide.jinja:93 msgid "Department" msgstr "Département" -#: pedagogy/templates/pedagogy/guide.jinja:89 +#: pedagogy/templates/pedagogy/guide.jinja:94 msgid "Credit type" msgstr "Type de crédit" @@ -5367,12 +5406,12 @@ msgstr "Utilisateur qui sera supprimé" msgid "User to be selected" msgstr "Utilisateur à sélectionner" -#: sas/models.py:236 +#: sas/models.py:231 msgid "picture" msgstr "photo" #: sas/templates/sas/album.jinja:9 sas/templates/sas/main.jinja:8 -#: sas/templates/sas/main.jinja:39 sas/templates/sas/picture.jinja:20 +#: sas/templates/sas/main.jinja:39 sas/templates/sas/picture.jinja:28 msgid "SAS" msgstr "SAS" @@ -5408,19 +5447,19 @@ msgstr "Toutes les catégories" msgid "SAS moderation" msgstr "Modération du SAS" -#: sas/templates/sas/picture.jinja:54 +#: sas/templates/sas/picture.jinja:63 msgid "Asked for removal" msgstr "Retrait demandé" -#: sas/templates/sas/picture.jinja:103 +#: sas/templates/sas/picture.jinja:113 msgid "HD version" msgstr "Version HD" -#: sas/templates/sas/picture.jinja:105 +#: sas/templates/sas/picture.jinja:116 msgid "Ask for removal" msgstr "Demander le retrait" -#: sas/templates/sas/picture.jinja:136 +#: sas/templates/sas/picture.jinja:147 msgid "People" msgstr "Personne(s)" @@ -5441,380 +5480,380 @@ msgstr "Erreur de création de l'album %(album)s : %(msg)s" msgid "Add user" msgstr "Ajouter une personne" -#: sith/settings.py:247 sith/settings.py:459 +#: sith/settings.py:246 sith/settings.py:457 msgid "English" msgstr "Anglais" -#: sith/settings.py:247 sith/settings.py:458 +#: sith/settings.py:246 sith/settings.py:456 msgid "French" msgstr "Français" -#: sith/settings.py:378 +#: sith/settings.py:376 msgid "TC" msgstr "TC" -#: sith/settings.py:379 +#: sith/settings.py:377 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:380 +#: sith/settings.py:378 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:381 +#: sith/settings.py:379 msgid "INFO" msgstr "INFO" -#: sith/settings.py:382 +#: sith/settings.py:380 msgid "GI" msgstr "GI" -#: sith/settings.py:383 sith/settings.py:469 +#: sith/settings.py:381 sith/settings.py:467 msgid "E" msgstr "E" -#: sith/settings.py:384 +#: sith/settings.py:382 msgid "EE" msgstr "EE" -#: sith/settings.py:385 +#: sith/settings.py:383 msgid "GESC" msgstr "GESC" -#: sith/settings.py:386 +#: sith/settings.py:384 msgid "GMC" msgstr "GMC" -#: sith/settings.py:387 +#: sith/settings.py:385 msgid "MC" msgstr "MC" -#: sith/settings.py:388 +#: sith/settings.py:386 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:389 +#: sith/settings.py:387 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:390 +#: sith/settings.py:388 msgid "N/A" msgstr "N/A" -#: sith/settings.py:394 sith/settings.py:401 sith/settings.py:420 +#: sith/settings.py:392 sith/settings.py:399 sith/settings.py:418 msgid "Check" msgstr "Chèque" -#: sith/settings.py:395 sith/settings.py:403 sith/settings.py:421 +#: sith/settings.py:393 sith/settings.py:401 sith/settings.py:419 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:396 +#: sith/settings.py:394 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:409 +#: sith/settings.py:407 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:410 +#: sith/settings.py:408 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:411 +#: sith/settings.py:409 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:439 +#: sith/settings.py:437 msgid "Free" msgstr "Libre" -#: sith/settings.py:440 +#: sith/settings.py:438 msgid "CS" msgstr "CS" -#: sith/settings.py:441 +#: sith/settings.py:439 msgid "TM" msgstr "TM" -#: sith/settings.py:442 +#: sith/settings.py:440 msgid "OM" msgstr "OM" -#: sith/settings.py:443 +#: sith/settings.py:441 msgid "QC" msgstr "QC" -#: sith/settings.py:444 +#: sith/settings.py:442 msgid "EC" msgstr "EC" -#: sith/settings.py:445 +#: sith/settings.py:443 msgid "RN" msgstr "RN" -#: sith/settings.py:446 +#: sith/settings.py:444 msgid "ST" msgstr "ST" -#: sith/settings.py:447 +#: sith/settings.py:445 msgid "EXT" msgstr "EXT" -#: sith/settings.py:452 +#: sith/settings.py:450 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:453 +#: sith/settings.py:451 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:454 +#: sith/settings.py:452 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:460 +#: sith/settings.py:458 msgid "German" msgstr "Allemand" -#: sith/settings.py:461 +#: sith/settings.py:459 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:465 +#: sith/settings.py:463 msgid "A" msgstr "A" -#: sith/settings.py:466 +#: sith/settings.py:464 msgid "B" msgstr "B" -#: sith/settings.py:467 +#: sith/settings.py:465 msgid "C" msgstr "C" -#: sith/settings.py:468 +#: sith/settings.py:466 msgid "D" msgstr "D" -#: sith/settings.py:470 +#: sith/settings.py:468 msgid "FX" msgstr "FX" -#: sith/settings.py:471 +#: sith/settings.py:469 msgid "F" msgstr "F" -#: sith/settings.py:472 +#: sith/settings.py:470 msgid "Abs" msgstr "Abs" -#: sith/settings.py:476 +#: sith/settings.py:474 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:477 +#: sith/settings.py:475 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:514 +#: sith/settings.py:512 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:515 +#: sith/settings.py:513 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:517 +#: sith/settings.py:515 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:521 +#: sith/settings.py:519 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:522 +#: sith/settings.py:520 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:523 +#: sith/settings.py:521 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:524 +#: sith/settings.py:522 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:525 +#: sith/settings.py:523 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:526 +#: sith/settings.py:524 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:527 +#: sith/settings.py:525 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:528 +#: sith/settings.py:526 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:530 +#: sith/settings.py:528 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:534 +#: sith/settings.py:532 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:535 +#: sith/settings.py:533 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:536 +#: sith/settings.py:534 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:538 +#: sith/settings.py:536 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:542 +#: sith/settings.py:540 msgid "One day" msgstr "Un jour" -#: sith/settings.py:543 +#: sith/settings.py:541 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:546 +#: sith/settings.py:544 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:551 +#: sith/settings.py:549 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:556 +#: sith/settings.py:554 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:561 +#: sith/settings.py:559 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:566 +#: sith/settings.py:564 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:572 +#: sith/settings.py:570 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:592 +#: sith/settings.py:590 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:593 +#: sith/settings.py:591 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:594 +#: sith/settings.py:592 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:595 +#: sith/settings.py:593 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:596 +#: sith/settings.py:594 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:597 +#: sith/settings.py:595 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:598 +#: sith/settings.py:596 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:599 +#: sith/settings.py:597 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:600 +#: sith/settings.py:598 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:637 +#: sith/settings.py:635 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:638 +#: sith/settings.py:636 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:641 +#: sith/settings.py:639 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:643 +#: sith/settings.py:641 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:644 +#: sith/settings.py:642 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:645 +#: sith/settings.py:643 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:646 +#: sith/settings.py:644 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:647 +#: sith/settings.py:645 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:648 +#: sith/settings.py:646 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:649 +#: sith/settings.py:647 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:661 +#: sith/settings.py:659 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:662 +#: sith/settings.py:660 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:663 +#: sith/settings.py:661 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:664 +#: sith/settings.py:662 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:665 +#: sith/settings.py:663 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:673 +#: sith/settings.py:671 msgid "AE tee-shirt" msgstr "Tee-shirt AE" diff --git a/mkdocs.yml b/mkdocs.yml index 41a0f144..349186a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - Gestion des permissions: tutorial/perms.md - Gestion des groupes: tutorial/groups.md - Etransactions: tutorial/etransaction.md + - Installer le projet (Avancé): tutorial/install_advanced.md - How-to: - L'ORM de Django: howto/querysets.md - Gérer les migrations: howto/migrations.md @@ -80,6 +81,9 @@ nav: - accounting: - reference/accounting/models.md - reference/accounting/views.md + - antispam: + - reference/antispam/models.md + - reference/antispam/forms.md - club: - reference/club/models.md - reference/club/views.md diff --git a/sith/settings.py b/sith/settings.py index 5c60cb12..841d4a42 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -98,6 +98,7 @@ INSTALLED_APPS = ( "matmat", "pedagogy", "galaxy", + "antispam", ) MIDDLEWARE = ( @@ -204,13 +205,12 @@ SASS_PRECISION = 8 WSGI_APPLICATION = "sith.wsgi.application" # Database -# https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", - } + }, } SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" @@ -673,6 +673,10 @@ SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))] SENTRY_DSN = "" SENTRY_ENV = "production" +TOXIC_DOMAINS_PROVIDERS = [ + "https://www.stopforumspam.com/downloads/toxic_domains_whole.txt", +] + try: from .settings_custom import * From cbd89320755ec1b11faabb2bc9590d49a064134c Mon Sep 17 00:00:00 2001 From: thomas girod Date: Sun, 4 Aug 2024 18:20:24 +0200 Subject: [PATCH 02/27] update psycopg v2 to psycopg v3 --- poetry.lock | 108 ++++++++++++++----------------------------------- pyproject.toml | 5 ++- 2 files changed, 34 insertions(+), 79 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1b3327f8..7f0c70d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1624,84 +1624,37 @@ files = [ wcwidth = "*" [[package]] -name = "psycopg2-binary" -version = "2.9.9" -description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg" +version = "3.2.1" +description = "PostgreSQL database adapter for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, + {file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"}, + {file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"}, +] + +[package.dependencies] +psycopg-c = {version = "3.2.1", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""} +typing-extensions = ">=4.4" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +binary = ["psycopg-binary (==3.2.1)"] +c = ["psycopg-c (==3.2.1)"] +dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.6)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] +docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] +pool = ["psycopg-pool"] +test = ["anyio (>=4.0)", "mypy (>=1.6)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] + +[[package]] +name = "psycopg-c" +version = "3.2.1" +description = "PostgreSQL database adapter for Python -- C optimisation distribution" +optional = false +python-versions = ">=3.8" +files = [ + {file = "psycopg_c-3.2.1.tar.gz", hash = "sha256:2d09943cc8a855c42c1e23b4298957b7ce8f27bf3683258c52fd139f601f7cda"}, ] [[package]] @@ -2002,7 +1955,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2632,4 +2584,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a9573a584420b00b0bd5bb85a0fb2daedb365bd1ff604b94ec23c187bc4dd991" +content-hash = "6f16726b7b25a6a6a2f387dce2b11014e45ca65202b142cceae5aff85dd76992" diff --git a/pyproject.toml b/pyproject.toml index 1bdb04a2..aae33bbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,10 @@ django-honeypot = "^1.2.0" [tool.poetry.group.prod.dependencies] # deps used in prod, but unnecessary for development -psycopg2-binary = "^2.9" + +# The C extra triggers compilation against sytem libs during install. +# Removing it would switch psycopg to a slower full-python implementation +psycopg = {extras = ["c"], version = "^3.2.1"} redis = {extras = ["hiredis"], version = "^5.0.8"} [tool.poetry.group.prod] From 12d316ebe46a778a67de3710ce59ac79d9ebf693 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Sun, 4 Aug 2024 18:29:52 +0200 Subject: [PATCH 03/27] doc: advanced install --- docs/tutorial/install-advanced.md | 152 ++++++++++++++++++++++++++++++ docs/tutorial/install_advanced.md | 10 -- mkdocs.yml | 2 +- 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 docs/tutorial/install-advanced.md delete mode 100644 docs/tutorial/install_advanced.md diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md new file mode 100644 index 00000000..5a40a725 --- /dev/null +++ b/docs/tutorial/install-advanced.md @@ -0,0 +1,152 @@ +Si le projet marche chez vous après avoir suivi les étapes +données dans la page précédente, alors vous pouvez développer. +Ce que nous nous vous avons présenté n'est absolument pas +la même configuration que celle du site, mais elle n'en +est pas moins fonctionnelle. + +Cependant, vous pourriez avoir envie de faire en sorte +que votre environnement de développement soit encore plus +proche de celui en production. +Voici les étapes à suivre pour ça. + +## Installer les dépendances manquantes + +Pour installer complètement le projet, il va falloir +quelques dépendances en plus. +Commencez par installer les dépendances système : + +=== "Linux" + + === "Debian/Ubuntu" + + ```bash + sudo apt install postgresql redis libq-dev + ``` + + === "Arch Linux" + + ```bash + sudo pacman -S postgresql redis + ``` + +=== "macOS" + + ```bash + brew install postgresql redis nginx lipbq + export PATH="/usr/local/opt/libpq/bin:$PATH" + source ~/.zshrc + ``` + +Puis, installez les dépendances poetry nécessaires en prod : + +```bash +poetry install --with prod +``` + +!!! info + + Certaines dépendances peuvent être un peu longues à installer + (notamment psycopg-c). + C'est parce que ces dépendances compilent certains modules + à l'installation. + +## Configurer Redis + +Redis est utilisé comme cache. +Assurez-vous qu'il tourne : + +```bash +sudo systemctl redis status +``` + +Et s'il ne tourne pas, démarrez-le : + +```bash +sudo systemctl start redis +sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot +``` + +Puis ajoutez le code suivant à la fin de votre fichier +`settings_custom.py` : + +```python +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": "redis://127.0.0.1:6379", + } +} +``` + +## Configurer PostgreSQL + +PostgreSQL est utilisé comme base de données. + +Passez sur le compte de l'utilisateur postgres +et lancez l'invite de commande sql : + +```bash +sudo su - postgres +psql +``` + +Puis configurez la base de données : + +```postgresql +CREATE DATABASE sith; +CREATE USER sith WITH PASSWORD 'password'; + +ALTER ROLE sith SET client_encoding TO 'utf8'; +ALTER ROLE sith SET default_transaction_isolation TO 'read committed'; +ALTER ROLE sith SET timezone TO 'UTC'; + +GRANT ALL PRIVILEGES ON DATABASE sith TO SITH; +\q +``` + +Si vous utilisez une version de PostgreSQL supérieure ou égale +à 15, vous devez exécuter une commande en plus, +en étant connecté en tant que postgres : + +```bash +psql -d sith -c "GRANT ALL PRIVILEGES ON SCHEMA public to sith"; +``` + +Puis ajoutez le code suivant à la fin de votre +`settings_custom.py` : + +```python +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "sith", + "USER": "sith", + "PASSWORD": "password", + "HOST": "localhost", + "PORT": "", # laissez ce champ vide pour que le choix du port soit automatique + } +} +``` + +Enfin, créez vos données : + +```bash +poetry run ./manage.py populate +``` + +!!! note + + N'oubliez de quitter la session de l'utilisateur + postgres après avoir configuré la db. + + +## Mettre à jour la base de données antispam + +L'anti spam nécessite d'être à jour par rapport à des bases de données externes. +Il existe une commande pour ça qu'il faut lancer régulièrement. +Lors de la mise en production, il est judicieux de configurer +un cron pour la mettre à jour au moins une fois par jour. + +```bash +python manage.py update_spam_database +``` diff --git a/docs/tutorial/install_advanced.md b/docs/tutorial/install_advanced.md deleted file mode 100644 index a5f2261e..00000000 --- a/docs/tutorial/install_advanced.md +++ /dev/null @@ -1,10 +0,0 @@ -## Mettre à jour la base de données antispam - -L'anti spam nécessite d'être à jour par rapport à des bases de données externe. -Il existe une commande pour ça qu'il faut lancer régulièrement. -Lors de la mise en production, il est judicieux de configurer -un cron pour la mettre à jour au moins une fois par jour. - -```bash -python manage.py update_spam_database -``` diff --git a/mkdocs.yml b/mkdocs.yml index 349186a8..822174ae 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,12 +61,12 @@ nav: - Archives: explanation/archives.md - Tutoriels: - Installer le projet: tutorial/install.md + - Installer le projet (avancé): tutorial/install-advanced.md - Configurer son éditeur: tutorial/devtools.md - Structure du projet: tutorial/structure.md - Gestion des permissions: tutorial/perms.md - Gestion des groupes: tutorial/groups.md - Etransactions: tutorial/etransaction.md - - Installer le projet (Avancé): tutorial/install_advanced.md - How-to: - L'ORM de Django: howto/querysets.md - Gérer les migrations: howto/migrations.md From 284f064cbf2c8c2a1dac3cb95d21a602a6cbaddf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 08:34:32 +0000 Subject: [PATCH 04/27] [UPDATE] Bump phonenumbers from 8.13.40 to 8.13.42 Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 8.13.40 to 8.13.42. - [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v8.13.40...v8.13.42) --- updated-dependencies: - dependency-name: phonenumbers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 9 +++++---- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7f0c70d9..89e1ebd4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1454,13 +1454,13 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.40" +version = "8.13.42" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.40-py2.py3-none-any.whl", hash = "sha256:9582752c20a1da5ec4449f7f97542bf8a793c8e2fec0ab57f767177bb8fc0b1d"}, - {file = "phonenumbers-8.13.40.tar.gz", hash = "sha256:f137c2848b8e83dd064b71881b65680584417efa202177fd330e2f7ff6c68113"}, + {file = "phonenumbers-8.13.42-py2.py3-none-any.whl", hash = "sha256:18acc22ee03116d27b26e990f53806a1770a3e05f05e1620bc09ad187f889456"}, + {file = "phonenumbers-8.13.42.tar.gz", hash = "sha256:7137904f2db3b991701e853174ce8e1cb8f540b8bfdf27617540de04c0b7bed5"}, ] [[package]] @@ -1955,6 +1955,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2584,4 +2585,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "6f16726b7b25a6a6a2f387dce2b11014e45ca65202b142cceae5aff85dd76992" +content-hash = "3d0851a2cf83b39a7aa4d06365546fb72e952ff5a6ff1ad45ec80c788831f474" diff --git a/pyproject.toml b/pyproject.toml index aae33bbf..5aad60c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ mistune = "^3.0.2" django-jinja = "^2.11" cryptography = "^43.0.0" django-phonenumber-field = "^8.0.0" -phonenumbers = "^8.12" +phonenumbers = "^8.13" django-ajax-selects = "^2.2.1" reportlab = "^4.2" django-haystack = "^3.2.1" From 7bc7af824566d7879cb79c249bc1e912f4f9d141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 08:51:26 +0000 Subject: [PATCH 05/27] [UPDATE] Bump faker from 26.0.0 to 26.1.0 Bumps [faker](https://github.com/joke2k/faker) from 26.0.0 to 26.1.0. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v26.0.0...v26.1.0) --- updated-dependencies: - dependency-name: faker dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89e1ebd4..35480794 100644 --- a/poetry.lock +++ b/poetry.lock @@ -754,13 +754,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "faker" -version = "26.0.0" +version = "26.1.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-26.0.0-py3-none-any.whl", hash = "sha256:886ee28219be96949cd21ecc96c4c742ee1680e77f687b095202c8def1a08f06"}, - {file = "Faker-26.0.0.tar.gz", hash = "sha256:0f60978314973de02c00474c2ae899785a42b2cf4f41b7987e93c132a2b8a4a9"}, + {file = "Faker-26.1.0-py3-none-any.whl", hash = "sha256:e8c5ef795223e945d9166aea3c0ecaf85ac54b4ade2af068d8e3c6524c2c0aa7"}, + {file = "Faker-26.1.0.tar.gz", hash = "sha256:33921b6fc3b83dd75fd42ec7f47ec87b50c00d3c5380fa7d8a507dab848b8229"}, ] [package.dependencies] @@ -2585,4 +2585,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3d0851a2cf83b39a7aa4d06365546fb72e952ff5a6ff1ad45ec80c788831f474" +content-hash = "f3f8b222cdb1a7d6199c10163c1bee6d5cfd0959af416944caa87a0e85f4d04c" diff --git a/pyproject.toml b/pyproject.toml index 5aad60c6..5df0162f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ ipython = "^8.26.0" pre-commit = "^3.8.0" ruff = "^0.5.5" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml djhtml = "^3.0.6" -faker = "^26.0.0" +faker = "^26.1.0" [tool.poetry.group.tests.dependencies] # deps used for testing purposes From a9f66e2cd90a59b49f8547d38d52be31d2f9a589 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Sun, 4 Aug 2024 22:30:54 +0200 Subject: [PATCH 06/27] extract `sent_from_logged_counter(request)` --- core/lookups.py | 11 ++--------- core/models.py | 8 +++----- core/views/files.py | 24 ++++++++++-------------- counter/utils.py | 36 ++++++++++++++++++++++++++++++++++++ counter/views.py | 43 +++++++++---------------------------------- 5 files changed, 60 insertions(+), 62 deletions(-) create mode 100644 counter/utils.py diff --git a/core/lookups.py b/core/lookups.py index ecd508a2..0e8af504 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -21,19 +21,12 @@ from club.models import Club from core.models import Group, SithFile, User from core.views.site import search_user from counter.models import Counter, Customer, Product - - -def check_token(request): - return ( - "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter(token=request.session["counter_token"]).exists() - ) +from counter.utils import sent_from_logged_counter class RightManagedLookupChannel(LookupChannel): def check_auth(self, request): - if not request.user.was_subscribed and not check_token(request): + if not request.user.was_subscribed and not sent_from_logged_counter(request): raise PermissionDenied diff --git a/core/models.py b/core/models.py index c9aceab6..ef85aded 100644 --- a/core/models.py +++ b/core/models.py @@ -1137,11 +1137,9 @@ class SithFile(models.Model): else: self._check_path_consistence() - def __getattribute__(self, attr): - if attr == "is_file": - return not self.is_folder - else: - return super().__getattribute__(attr) + @property + def is_file(self): + return not self.is_folder @cached_property def as_picture(self): diff --git a/core/views/files.py b/core/views/files.py index 161578d0..6bc76a5e 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -21,7 +21,7 @@ from django import forms from django.conf import settings from django.core.exceptions import PermissionDenied from django.forms.models import modelform_factory -from django.http import Http404, HttpResponse +from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.http import http_date @@ -37,27 +37,23 @@ from core.views import ( CanViewMixin, can_view, ) -from counter.models import Counter +from counter.utils import sent_from_logged_counter -def send_file(request, file_id, file_class=SithFile, file_attr="file"): +def send_file( + request: HttpRequest, + file_id: int, + file_class: type[SithFile] = SithFile, + file_attr: str = "file", +): """Send a file through Django without loading the whole file into memory at once. The FileWrapper will turn the file object into an iterator for chunks of 8KB. """ f = get_object_or_404(file_class, id=file_id) - if not ( - can_view(f, request.user) - or ( - "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ) - ): + if not can_view(f, request.user) and not sent_from_logged_counter(request): raise PermissionDenied - name = f.__getattribute__(file_attr).name + name = getattr(f, file_attr).name filepath = settings.MEDIA_ROOT / name # check if file exists on disk diff --git a/counter/utils.py b/counter/utils.py new file mode 100644 index 00000000..6196081f --- /dev/null +++ b/counter/utils.py @@ -0,0 +1,36 @@ +from urllib.parse import urlparse + +from django.http import HttpRequest +from django.urls import resolve + +from counter.models import Counter + + +def sent_from_logged_counter(request: HttpRequest) -> bool: + """Check if the request is sent from a device logged to a counter. + + The request must also be sent within the frame of a counter's activity. + Trying to use this function to manage access to non-sas + related resources probably won't work. + + A request is considered as coming from a logged counter if : + + - Its referer comes from the counter app + (eg. fetching user pictures from the click UI) + or the request path belongs to the counter app + (eg. the barman went back to the main by missclick and go back + to the counter) + - The current session has a counter token associated with it. + - A counter with this token exists. + """ + referer = urlparse(request.META["HTTP_REFERER"]).path + path_ok = ( + request.resolver_match.app_name == "counter" + or resolve(referer).app_name == "counter" + ) + return ( + path_ok + and "counter_token" in request.session + and request.session["counter_token"] + and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) diff --git a/counter/views.py b/counter/views.py index a426c6fd..aae2f183 100644 --- a/counter/views.py +++ b/counter/views.py @@ -80,6 +80,7 @@ from counter.models import ( Selling, StudentCard, ) +from counter.utils import sent_from_logged_counter class CounterAdminMixin(View): @@ -901,15 +902,9 @@ class RefillingDeleteView(DeleteView): def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() - if ( - timezone.now() - self.object.date - <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and sent_from_logged_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -932,15 +927,9 @@ class SellingDeleteView(DeleteView): def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() - if ( - timezone.now() - self.object.date - <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and sent_from_logged_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -1175,14 +1164,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if ( - self.object.barmen_list - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if sent_from_logged_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) @@ -1215,14 +1197,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if ( - self.object.barmen_list - and "counter_token" in request.session.keys() - and request.session["counter_token"] - and Counter.objects.filter( # check if not null for counters that have no token set - token=request.session["counter_token"] - ).exists() - ): + if sent_from_logged_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) From a5e4db99fba9a7c526ab1f9524e84409c720cdd5 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 5 Aug 2024 00:36:29 +0200 Subject: [PATCH 07/27] Use X-Accel-Redirect to send files in prod --- core/views/files.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/views/files.py b/core/views/files.py index 6bc76a5e..52474b55 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -12,6 +12,7 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +from urllib.parse import quote # This file contains all the views that concern the page model from wsgiref.util import FileWrapper @@ -45,10 +46,13 @@ def send_file( file_id: int, file_class: type[SithFile] = SithFile, file_attr: str = "file", -): - """Send a file through Django without loading the whole file into - memory at once. The FileWrapper will turn the file object into an - iterator for chunks of 8KB. +) -> HttpResponse: + """Send a protected file, if the user can see it. + + In prod, the server won't handle the download itself, + but set the appropriate headers in the response to make the reverse-proxy + deal with it. + In debug mode, the server will directly send the file. """ f = get_object_or_404(file_class, id=file_id) if not can_view(f, request.user) and not sent_from_logged_counter(request): @@ -60,6 +64,16 @@ def send_file( if not filepath.exists(): raise Http404 + if not settings.DEBUG: + # When receiving a response with the Accel-Redirect header, + # the reverse proxy will automatically handle the file sending. + # This is really hard to test (thus isn't tested) + # so please do not mess with this. + response = HttpResponse(status=200) + response["Content-Type"] = "" + response["X-Accel-Redirect"] = f"/data/{quote(name)}" + return response + with open(filepath, "rb") as filename: wrapper = FileWrapper(filename) response = HttpResponse(wrapper, content_type=f.mime_type) From a637742bb02abcad4ad22c7f3978001ee3256939 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 5 Aug 2024 10:46:15 +0200 Subject: [PATCH 08/27] apply review comment --- core/lookups.py | 4 ++-- core/views/files.py | 8 ++++---- counter/utils.py | 2 +- counter/views.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/lookups.py b/core/lookups.py index 0e8af504..9a555f1b 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -21,12 +21,12 @@ from club.models import Club from core.models import Group, SithFile, User from core.views.site import search_user from counter.models import Counter, Customer, Product -from counter.utils import sent_from_logged_counter +from counter.utils import is_logged_in_counter class RightManagedLookupChannel(LookupChannel): def check_auth(self, request): - if not request.user.was_subscribed and not sent_from_logged_counter(request): + if not request.user.was_subscribed and not is_logged_in_counter(request): raise PermissionDenied diff --git a/core/views/files.py b/core/views/files.py index 52474b55..064b9aaa 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -12,7 +12,7 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # -from urllib.parse import quote +from urllib.parse import quote, urljoin # This file contains all the views that concern the page model from wsgiref.util import FileWrapper @@ -38,7 +38,7 @@ from core.views import ( CanViewMixin, can_view, ) -from counter.utils import sent_from_logged_counter +from counter.utils import is_logged_in_counter def send_file( @@ -55,7 +55,7 @@ def send_file( In debug mode, the server will directly send the file. """ f = get_object_or_404(file_class, id=file_id) - if not can_view(f, request.user) and not sent_from_logged_counter(request): + if not can_view(f, request.user) and not is_logged_in_counter(request): raise PermissionDenied name = getattr(f, file_attr).name filepath = settings.MEDIA_ROOT / name @@ -71,7 +71,7 @@ def send_file( # so please do not mess with this. response = HttpResponse(status=200) response["Content-Type"] = "" - response["X-Accel-Redirect"] = f"/data/{quote(name)}" + response["X-Accel-Redirect"] = quote(urljoin(settings.MEDIA_URL, name)) return response with open(filepath, "rb") as filename: diff --git a/counter/utils.py b/counter/utils.py index 6196081f..08ae0490 100644 --- a/counter/utils.py +++ b/counter/utils.py @@ -6,7 +6,7 @@ from django.urls import resolve from counter.models import Counter -def sent_from_logged_counter(request: HttpRequest) -> bool: +def is_logged_in_counter(request: HttpRequest) -> bool: """Check if the request is sent from a device logged to a counter. The request must also be sent within the frame of a counter's activity. diff --git a/counter/views.py b/counter/views.py index aae2f183..373539bf 100644 --- a/counter/views.py +++ b/counter/views.py @@ -80,7 +80,7 @@ from counter.models import ( Selling, StudentCard, ) -from counter.utils import sent_from_logged_counter +from counter.utils import is_logged_in_counter class CounterAdminMixin(View): @@ -904,7 +904,7 @@ class RefillingDeleteView(DeleteView): self.object = self.get_object() if timezone.now() - self.object.date <= timedelta( minutes=settings.SITH_LAST_OPERATIONS_LIMIT - ) and sent_from_logged_counter(request): + ) and is_logged_in_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -929,7 +929,7 @@ class SellingDeleteView(DeleteView): self.object = self.get_object() if timezone.now() - self.object.date <= timedelta( minutes=settings.SITH_LAST_OPERATIONS_LIMIT - ) and sent_from_logged_counter(request): + ) and is_logged_in_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) @@ -1164,7 +1164,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if sent_from_logged_counter(request) and self.object.barmen_list: + if is_logged_in_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) @@ -1197,7 +1197,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): def dispatch(self, request, *args, **kwargs): """We have here again a very particular right handling.""" self.object = self.get_object() - if sent_from_logged_counter(request) and self.object.barmen_list: + if is_logged_in_counter(request) and self.object.barmen_list: return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) From 1dfd8711698c26ad9682de1426fa4f512f77fb77 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 5 Aug 2024 12:56:30 +0200 Subject: [PATCH 09/27] add doc for nginx configuration --- docs/tutorial/install-advanced.md | 129 +++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index 5a40a725..0c156f4f 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -9,6 +9,33 @@ que votre environnement de développement soit encore plus proche de celui en production. Voici les étapes à suivre pour ça. +!!!tip + + Configurer les dépendances du projet + peut demander beaucoup d'allers et retours entre + votre répertoire projet et divers autres emplacements. + + Vous pouvez gagner du temps en déclarant un alias : + + === "bash/zsh" + + ```bash + alias cdp="cd /repertoire/du/projet" + ``` + + === "nu" + + ```nu + alias cdp = cd /repertoire/du/projet + ``` + + Chaque fois qu'on vous demandera de retourner au répertoire + projet, vous aurez juste à faire : + + ```bash + cdp + ``` + ## Installer les dépendances manquantes Pour installer complètement le projet, il va falloir @@ -20,19 +47,19 @@ Commencez par installer les dépendances système : === "Debian/Ubuntu" ```bash - sudo apt install postgresql redis libq-dev + sudo apt install postgresql redis libq-dev nginx ``` === "Arch Linux" ```bash - sudo pacman -S postgresql redis + sudo pacman -S postgresql redis nginx ``` === "macOS" ```bash - brew install postgresql redis nginx lipbq + brew install postgresql redis lipbq nginx export PATH="/usr/local/opt/libpq/bin:$PATH" source ~/.zshrc ``` @@ -139,6 +166,102 @@ poetry run ./manage.py populate N'oubliez de quitter la session de l'utilisateur postgres après avoir configuré la db. +## Configurer nginx + +Nginx est utilisé comme reverse-proxy. + +!!!warning + + Nginx ne sert pas les fichiers de la même manière que Django. + Les fichiers statiques servis seront ceux du dossier `/static`, + tels que générés par les commandes `collectstatic` et + `compilestatic`. + Si vous changez du css ou du js sans faire tourner + ces commandes, ces changements ne seront pas reflétés. + + De manière générale, utiliser nginx en dev n'est pas très utile, + voire est gênant si vous travaillez sur le front. + Ne vous embêtez pas avec ça, sauf par curiosité intellectuelle, + ou bien si vous voulez tester spécifiquement + des interactions avec le reverse proxy. + + +Placez-vous dans le répertoire `/etc/nginx`, +et créez les dossiers et fichiers nécessaires : + +```bash +cd /etc/nginx/ +sudo mkdir sites-enabled sites-available +sudo touch sites-available/sith.conf +sudo ln -s /etc/nginx/sites-available/sith.conf sites-enabled/sith.conf +``` + +Puis ouvrez le fichier `sites-available/sith.conf` et mettez-y le contenu suivant : + +```nginx +server { + listen 8000; + + server_name _; + + location /static/; + root /repertoire/du/projet; + } + location ~ ^/data/(products|com|club_logos)/ { + root /repertoire/du/projet; + } + location ~ ^/data/(SAS|profiles|users|.compressed|.thumbnails)/ { + # https://nginx.org/en/docs/http/ngx_http_core_module.html#internal + internal; + root /repertoire/du/projet; + } + + location / { + proxy_pass http://127.0.0.1:8001; + include uwsgi_params; + } +} +``` + +Ouvrez le fichier `nginx.conf`, et ajoutez la configuration suivante : + +```nginx +http { + # Toute la configuration + # éventuellement déjà là + + include /etc/nginx/sites-enabled/sith.conf; +} +``` + +Vérifiez que votre configuration est bonne : + +```bash +sudo nginx -t +``` + +Si votre configuration n'est pas bonne, corrigez-la. +Puis lancez ou relancez nginx : + +```bash +sudo systemctl restart nginx +``` + +Dans votre `settings_custom.py`, remplacez `DEBUG=True` par `DEBUG=False`. + +Enfin, démarrez le serveur Django : + +```bash +cd /repertoire/du/projet +poetry run ./manage.py runserver 8001 +``` + +Et c'est bon, votre reverse-proxy est prêt à tourner devant votre serveur. +Nginx écoutera sur le port 8000. +Toutes les requêtes vers des fichiers statiques et les medias publiques +seront seront servies directement par nginx. +Toutes les autres requêtes seront transmises au serveur django. + ## Mettre à jour la base de données antispam From f6fbad84031ce3e907b689ff9aa3c79224a75053 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 5 Aug 2024 15:53:41 +0200 Subject: [PATCH 10/27] fix missing HTTP_REFERER --- counter/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/counter/utils.py b/counter/utils.py index 08ae0490..2b9b6fd6 100644 --- a/counter/utils.py +++ b/counter/utils.py @@ -23,13 +23,12 @@ def is_logged_in_counter(request: HttpRequest) -> bool: - The current session has a counter token associated with it. - A counter with this token exists. """ - referer = urlparse(request.META["HTTP_REFERER"]).path - path_ok = ( - request.resolver_match.app_name == "counter" - or resolve(referer).app_name == "counter" + referer_ok = ( + "HTTP_REFERER" in request.META + and resolve(urlparse(request.META["HTTP_REFERER"]).path).app_name == "counter" ) return ( - path_ok + (referer_ok or request.resolver_match.app_name == "counter") and "counter_token" in request.session and request.session["counter_token"] and Counter.objects.filter(token=request.session["counter_token"]).exists() From 29bb0f67125a2fef7a13c00b0d79dbf775233f84 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 5 Aug 2024 17:08:30 +0200 Subject: [PATCH 11/27] promote AlpineJS to global dependency --- core/templates/core/base.jinja | 3 +++ core/templates/core/user_detail.jinja | 4 ---- core/templates/core/user_pictures.jinja | 1 - counter/templates/counter/counter_click.jinja | 1 - eboutic/templates/eboutic/eboutic_main.jinja | 1 - eboutic/templates/eboutic/eboutic_makecommand.jinja | 1 - pedagogy/templates/pedagogy/guide.jinja | 4 ---- sas/templates/sas/picture.jinja | 4 ---- 8 files changed, 3 insertions(+), 16 deletions(-) diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 30d157ba..22b1ef7d 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -27,6 +27,9 @@ {% block additional_css %}{% endblock %} {% block additional_js %}{% endblock %} + + {# Alpine JS must be loaded after scripts that use it. #} + {% endblock %} diff --git a/core/templates/core/user_detail.jinja b/core/templates/core/user_detail.jinja index 9f5824fc..4eff73d3 100644 --- a/core/templates/core/user_detail.jinja +++ b/core/templates/core/user_detail.jinja @@ -9,10 +9,6 @@ {% trans user_name=profile.get_display_name() %}{{ user_name }}'s profile{% endtrans %} {% endblock %} -{% block additional_js %} - -{% endblock %} - {% block content %}